diff options
Diffstat (limited to 'src')
85 files changed, 16800 insertions, 9018 deletions
diff --git a/src/ABI/current b/src/ABI/current index 28715673..b617d997 100644 --- a/src/ABI/current +++ b/src/ABI/current @@ -1 +1 @@ -4.8.1
\ No newline at end of file +4.9.0
\ No newline at end of file diff --git a/src/ABI/libssh-4.9.0.symbols b/src/ABI/libssh-4.9.0.symbols new file mode 100644 index 00000000..a26e2c5e --- /dev/null +++ b/src/ABI/libssh-4.9.0.symbols @@ -0,0 +1,427 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char
\ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 621f8b35..93ecb5e7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,6 @@ set(LIBSSH_PUBLIC_INCLUDE_DIRS ${libssh_SOURCE_DIR}/include) set(LIBSSH_PRIVATE_INCLUDE_DIRS - ${libssh_BINARY_DIR}/include ${libssh_BINARY_DIR} ) @@ -9,24 +8,9 @@ set(LIBSSH_LINK_LIBRARIES ${LIBSSH_REQUIRED_LIBRARIES} ) -if (WIN32) - set(LIBSSH_LINK_LIBRARIES - ${LIBSSH_LINK_LIBRARIES} - ws2_32 - ) -endif (WIN32) - -if (OPENSSL_CRYPTO_LIBRARY) - set(LIBSSH_PRIVATE_INCLUDE_DIRS - ${LIBSSH_PRIVATE_INCLUDE_DIRS} - ${OPENSSL_INCLUDE_DIR} - ) - - set(LIBSSH_LINK_LIBRARIES - ${LIBSSH_LINK_LIBRARIES} - ${OPENSSL_CRYPTO_LIBRARY} - ) -endif (OPENSSL_CRYPTO_LIBRARY) +if (TARGET OpenSSL::Crypto) + list(APPEND LIBSSH_LINK_LIBRARIES OpenSSL::Crypto) +endif () if (MBEDTLS_CRYPTO_LIBRARY) set(LIBSSH_PRIVATE_INCLUDE_DIRS @@ -51,15 +35,7 @@ if (GCRYPT_LIBRARIES) endif() if (WITH_ZLIB) - set(LIBSSH_PRIVATE_INCLUDE_DIRS - ${LIBSSH_PRIVATE_INCLUDE_DIRS} - ${ZLIB_INCLUDE_DIR} - ) - - set(LIBSSH_LINK_LIBRARIES - ${LIBSSH_LINK_LIBRARIES} - ${ZLIB_LIBRARY} - ) + list(APPEND LIBSSH_LINK_LIBRARIES ZLIB::ZLIB) endif (WITH_ZLIB) if (WITH_GSSAPI AND GSSAPI_FOUND) @@ -93,6 +69,16 @@ if (MINGW AND Threads_FOUND) ) endif() +# The ws2_32 needs to be last for mingw to build +# https://gitlab.com/libssh/libssh-mirror/-/issues/84 +if (WIN32) + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + iphlpapi + ws2_32 + ) +endif (WIN32) + if (BUILD_STATIC_LIB) set(LIBSSH_STATIC_LIBRARY ssh_static @@ -112,6 +98,7 @@ set(libssh_SRCS config.c connect.c connector.c + crypto_common.c curve25519.c dh.c ecdh.c @@ -140,6 +127,7 @@ set(libssh_SRCS socket.c string.c threads.c + ttyopts.c wrapper.c external/bcrypt_pbkdf.c external/blowfish.c @@ -181,6 +169,8 @@ if (WITH_GCRYPT) gcrypt_missing.c pki_gcrypt.c ecdh_gcrypt.c + getrandom_gcrypt.c + md_gcrypt.c dh_key.c pki_ed25519.c external/ed25519.c @@ -204,52 +194,51 @@ elseif (WITH_MBEDTLS) mbedcrypto_missing.c pki_mbedcrypto.c ecdh_mbedcrypto.c + getrandom_mbedcrypto.c + md_mbedcrypto.c dh_key.c pki_ed25519.c external/ed25519.c external/fe25519.c external/ge25519.c external/sc25519.c - external/chacha.c - external/poly1305.c - chachapoly.c ) + if (NOT (HAVE_MBEDTLS_CHACHA20_H AND HAVE_MBEDTLS_POLY1305_H)) + set(libssh_SRCS + ${libssh_SRCS} + external/chacha.c + external/poly1305.c + chachapoly.c + ) + endif() + else (WITH_GCRYPT) set(libssh_SRCS ${libssh_SRCS} threads/libcrypto.c pki_crypto.c ecdh_crypto.c + getrandom_crypto.c + md_crypto.c libcrypto.c dh_crypto.c ) - if (NOT HAVE_OPENSSL_ED25519) - set(libssh_SRCS - ${libssh_SRCS} - pki_ed25519.c - external/ed25519.c - external/fe25519.c - external/ge25519.c - external/sc25519.c - ) - endif (NOT HAVE_OPENSSL_ED25519) - if (NOT (HAVE_OPENSSL_EVP_CHACHA20 AND HAVE_OPENSSL_EVP_POLY1305)) + if (NOT HAVE_OPENSSL_EVP_CHACHA20) set(libssh_SRCS ${libssh_SRCS} external/chacha.c external/poly1305.c chachapoly.c ) - endif (NOT (HAVE_OPENSSL_EVP_CHACHA20 AND HAVE_OPENSSL_EVP_POLY1305)) - if(OPENSSL_VERSION VERSION_LESS "1.1.0") - set(libssh_SRCS ${libssh_SRCS} libcrypto-compat.c) - endif() + endif (NOT HAVE_OPENSSL_EVP_CHACHA20) endif (WITH_GCRYPT) if (WITH_SFTP) set(libssh_SRCS ${libssh_SRCS} sftp.c + sftp_common.c + sftp_aio.c ) if (WITH_SERVER) @@ -291,12 +280,12 @@ if (WITH_GSSAPI AND GSSAPI_FOUND) endif (WITH_GSSAPI AND GSSAPI_FOUND) if (NOT WITH_NACL) - if (NOT HAVE_OPENSSL_ED25519) + if (NOT HAVE_LIBCRYPTO) set(libssh_SRCS ${libssh_SRCS} external/curve25519_ref.c ) - endif (NOT HAVE_OPENSSL_ED25519) + endif() endif (NOT WITH_NACL) # Set the path to the default map file @@ -335,11 +324,14 @@ endif (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT AND ABIMAP_FOUND) add_library(ssh ${libssh_SRCS}) target_compile_options(ssh PRIVATE - ${DEFAULT_C_COMPILE_FLAGS} - -D_GNU_SOURCE) + ${DEFAULT_C_COMPILE_FLAGS}) +if (CYGWIN) + target_compile_definitions(ssh PRIVATE _GNU_SOURCE) +endif () target_include_directories(ssh PUBLIC $<BUILD_INTERFACE:${libssh_SOURCE_DIR}/include> + $<BUILD_INTERFACE:${libssh_BINARY_DIR}/include> $<INSTALL_INTERFACE:include> PRIVATE ${LIBSSH_PRIVATE_INCLUDE_DIRS}) @@ -347,7 +339,7 @@ target_link_libraries(ssh PRIVATE ${LIBSSH_LINK_LIBRARIES}) if (WIN32 AND NOT BUILD_SHARED_LIBS) - set_target_properties(ssh PROPERTIES COMPILE_FLAGS "-DLIBSSH_STATIC") + target_compile_definitions(ssh PUBLIC "LIBSSH_STATIC") endif () add_library(ssh::ssh ALIAS ssh) @@ -358,9 +350,7 @@ if (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT) set(MAP_PATH "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_dev.map") endif (ABIMAP_FOUND) - set_target_properties(ssh - PROPERTIES LINK_FLAGS - "-Wl,--version-script,\"${MAP_PATH}\"") + target_link_libraries(ssh PRIVATE "-Wl,--version-script,\"${MAP_PATH}\"") endif (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT) set_target_properties(ssh @@ -374,13 +364,17 @@ set_target_properties(ssh ) if (WITH_VISIBILITY_HIDDEN) - set_target_properties(ssh PROPERTIES COMPILE_FLAGS "-fvisibility=hidden") + set_target_properties(ssh PROPERTIES C_VISIBILITY_PRESET hidden) endif (WITH_VISIBILITY_HIDDEN) if (MINGW) - set_target_properties(ssh PROPERTIES LINK_FLAGS "-Wl,--enable-stdcall-fixup") - set_target_properties(ssh PROPERTIES COMPILE_FLAGS "-D_POSIX_SOURCE") + target_link_libraries(ssh PRIVATE "-Wl,--enable-stdcall-fixup") + target_compile_definitions(ssh PRIVATE "_POSIX_SOURCE") endif () +if (WITH_COVERAGE) + include(CodeCoverage) + append_coverage_compiler_flags_to_target(ssh) +endif (WITH_COVERAGE) install(TARGETS ssh @@ -397,12 +391,15 @@ if (BUILD_STATIC_LIB) add_library(ssh-static STATIC ${libssh_SRCS}) target_compile_options(ssh-static PRIVATE - ${DEFAULT_C_COMPILE_FLAGS} - -D_GNU_SOURCE) + ${DEFAULT_C_COMPILE_FLAGS}) + if (CYGWIN) + target_compile_definitions(ssh-static PRIVATE _GNU_SOURCE) + endif () target_include_directories(ssh-static PUBLIC $<BUILD_INTERFACE:${libssh_SOURCE_DIR}/include> + $<BUILD_INTERFACE:${libssh_BINARY_DIR}/include> $<INSTALL_INTERFACE:include> PRIVATE ${LIBSSH_PRIVATE_INCLUDE_DIRS}) target_link_libraries(ssh-static @@ -428,13 +425,11 @@ if (BUILD_STATIC_LIB) ) if (WIN32) - set_target_properties( - ssh-static - PROPERTIES - COMPILE_FLAGS - "-DLIBSSH_STATIC" - ) + target_compile_definitions(ssh-static PUBLIC "LIBSSH_STATIC") endif (WIN32) + if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(ssh-static) + endif (WITH_COVERAGE) endif (BUILD_STATIC_LIB) message(STATUS "Threads_FOUND=${Threads_FOUND}") diff --git a/src/agent.c b/src/agent.c index 62b0093e..1c79c6eb 100644 --- a/src/agent.c +++ b/src/agent.c @@ -33,8 +33,6 @@ * the agent returns the signed data */ -#ifndef _WIN32 - #include "config.h" #include <stdlib.h> @@ -46,8 +44,14 @@ #include <unistd.h> #endif +#ifndef _WIN32 #include <netinet/in.h> #include <arpa/inet.h> +#include <sys/socket.h> +#else +#include <winsock2.h> +#include <ws2tcpip.h> +#endif #include "libssh/agent.h" #include "libssh/priv.h" @@ -63,9 +67,9 @@ (((x) == SSH_AGENT_FAILURE) || ((x) == SSH_COM_AGENT2_FAILURE) || \ ((x) == SSH2_AGENT_FAILURE)) -static size_t atomicio(struct ssh_agent_struct *agent, void *buf, size_t n, int do_read) { +static uint32_t atomicio(struct ssh_agent_struct *agent, void *buf, uint32_t n, int do_read) { char *b = buf; - size_t pos = 0; + uint32_t pos = 0; ssize_t res; ssh_pollfd_t pfd; ssh_channel channel = agent->channel; @@ -79,9 +83,9 @@ static size_t atomicio(struct ssh_agent_struct *agent, void *buf, size_t n, int while (n > pos) { if (do_read) { - res = read(fd, b + pos, n - pos); + res = recv(fd, b + pos, n - pos, 0); } else { - res = write(fd, b + pos, n - pos); + res = send(fd, b + pos, n - pos, 0); } switch (res) { case -1: @@ -102,7 +106,7 @@ static size_t atomicio(struct ssh_agent_struct *agent, void *buf, size_t n, int errno = do_read ? 0 : EPIPE; return pos; default: - pos += (size_t) res; + pos += (uint32_t) res; } } return pos; @@ -117,7 +121,7 @@ static size_t atomicio(struct ssh_agent_struct *agent, void *buf, size_t n, int continue; if (res == SSH_ERROR) return 0; - pos += (size_t)res; + pos += (uint32_t)res; } return pos; } @@ -146,11 +150,21 @@ static void agent_set_channel(struct ssh_agent_struct *agent, ssh_channel channe agent->channel = channel; } +/** + * @addtogroup libssh_auth + * + * @{ + */ + /** @brief sets the SSH agent channel. * The SSH agent channel will be used to authenticate this client using * an agent through a channel, from another session. The most likely use * is to implement SSH Agent forwarding into a SSH proxy. + * + * @param session the session + * * @param[in] channel a SSH channel from another session. + * * @returns SSH_OK in case of success * SSH_ERROR in case of an error */ @@ -185,6 +199,10 @@ int ssh_set_agent_socket(ssh_session session, socket_t fd){ return SSH_OK; } +/** + * @} + */ + void ssh_agent_close(struct ssh_agent_struct *agent) { if (agent == NULL) { return; @@ -216,7 +234,8 @@ static int agent_connect(ssh_session session) { if (session->agent->channel != NULL) return 0; - auth_sock = getenv("SSH_AUTH_SOCK"); + auth_sock = session->opts.agent_socket ? + session->opts.agent_socket : getenv("SSH_AUTH_SOCK"); if (auth_sock && *auth_sock) { if (ssh_socket_unix(session->agent->sock, auth_sock) < 0) { @@ -251,30 +270,32 @@ static int agent_decode_reply(struct ssh_session_struct *session, int type) { static int agent_talk(struct ssh_session_struct *session, struct ssh_buffer_struct *request, struct ssh_buffer_struct *reply) { uint32_t len = 0; - uint8_t payload[1024] = {0}; + uint8_t tmpbuf[4]; + uint8_t *payload = tmpbuf; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; len = ssh_buffer_get_len(request); - SSH_LOG(SSH_LOG_TRACE, "Request length: %u", len); + SSH_LOG(SSH_LOG_TRACE, "Request length: %" PRIu32, len); PUSH_BE_U32(payload, 0, len); /* send length and then the request packet */ if (atomicio(session->agent, payload, 4, 0) == 4) { if (atomicio(session->agent, ssh_buffer_get(request), len, 0) != len) { - SSH_LOG(SSH_LOG_WARN, "atomicio sending request failed: %s", + SSH_LOG(SSH_LOG_TRACE, "atomicio sending request failed: %s", strerror(errno)); return -1; } } else { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "atomicio sending request length failed: %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return -1; } /* wait for response, read the length of the response packet */ if (atomicio(session->agent, payload, 4, 1) != 4) { - SSH_LOG(SSH_LOG_WARN, "atomicio read response length failed: %s", + SSH_LOG(SSH_LOG_TRACE, "atomicio read response length failed: %s", strerror(errno)); return -1; } @@ -282,26 +303,23 @@ static int agent_talk(struct ssh_session_struct *session, len = PULL_BE_U32(payload, 0); if (len > 256 * 1024) { ssh_set_error(session, SSH_FATAL, - "Authentication response too long: %u", len); + "Authentication response too long: %" PRIu32, len); return -1; } - SSH_LOG(SSH_LOG_TRACE, "Response length: %u", len); + SSH_LOG(SSH_LOG_TRACE, "Response length: %" PRIu32, len); - while (len > 0) { - size_t n = len; - if (n > sizeof(payload)) { - n = sizeof(payload); - } - if (atomicio(session->agent, payload, n, 1) != n) { - SSH_LOG(SSH_LOG_WARN, - "Error reading response from authentication socket."); - return -1; - } - if (ssh_buffer_add_data(reply, payload, n) < 0) { - SSH_LOG(SSH_LOG_WARN, "Not enough space"); - return -1; - } - len -= n; + payload = ssh_buffer_allocate(reply, len); + if (payload == NULL) { + SSH_LOG(SSH_LOG_DEBUG, "Not enough space"); + return -1; + } + + if (atomicio(session->agent, payload, len, 1) != len) { + SSH_LOG(SSH_LOG_DEBUG, + "Error reading response from authentication socket."); + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(reply, len); + return -1; } return 0; @@ -313,7 +331,7 @@ uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session) ssh_buffer reply = NULL; unsigned int type = 0; uint32_t count = 0; - int rc; + uint32_t rc; /* send message to the agent requesting the list of identities */ request = ssh_buffer_new(); @@ -345,7 +363,7 @@ uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session) rc = ssh_buffer_get_u8(reply, (uint8_t *) &type); if (rc != sizeof(uint8_t)) { ssh_set_error(session, SSH_FATAL, - "Bad authentication reply size: %d", rc); + "Bad authentication reply size: %" PRIu32, rc); SSH_BUFFER_FREE(reply); return 0; } @@ -353,7 +371,7 @@ uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session) type = bswap_32(type); #endif - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Answer type: %d, expected answer: %d", type, SSH2_AGENT_IDENTITIES_ANSWER); @@ -386,15 +404,13 @@ uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session) return 0; } - if (session->agent->ident) { - ssh_buffer_reinit(session->agent->ident); - } + ssh_buffer_free(session->agent->ident); session->agent->ident = reply; return session->agent->count; } -/* caller has to free commment */ +/* caller has to free comment */ ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, char **comment) { if (ssh_agent_get_ident_count(session) > 0) { @@ -404,7 +420,7 @@ ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, return NULL; } -/* caller has to free commment */ +/* caller has to free comment */ ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session, char **comment) { struct ssh_key_struct *key; @@ -573,7 +589,7 @@ ssh_string ssh_agent_sign_data(ssh_session session, #endif if (agent_failed(type)) { - SSH_LOG(SSH_LOG_WARN, "Agent reports failure in signing the key"); + SSH_LOG(SSH_LOG_DEBUG, "Agent reports failure in signing the key"); SSH_BUFFER_FREE(reply); return NULL; } else if (type != SSH2_AGENT_SIGN_RESPONSE) { @@ -590,5 +606,3 @@ ssh_string ssh_agent_sign_data(ssh_session session, return sig_blob; } - -#endif /* _WIN32 */ @@ -47,7 +47,7 @@ #include "libssh/legacy.h" /** - * @defgroup libssh_auth The SSH authentication functions. + * @defgroup libssh_auth The SSH authentication functions * @ingroup libssh * * Functions to authenticate with a server. @@ -58,7 +58,7 @@ /** * @internal * - * @brief Ask access to the ssh-userauth service. + * @brief Ask for access to the ssh-userauth service. * * @param[in] session The SSH session handle. * @@ -66,19 +66,21 @@ * @returns SSH_AGAIN on nonblocking mode, if calling that function * again is necessary */ -static int ssh_userauth_request_service(ssh_session session) { +static int ssh_userauth_request_service(ssh_session session) +{ int rc; rc = ssh_service_request(session, "ssh-userauth"); if ((rc != SSH_OK) && (rc != SSH_AGAIN)) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Failed to request \"ssh-userauth\" service"); } return rc; } -static int ssh_auth_response_termination(void *user) { +static int ssh_auth_response_termination(void *user) +{ ssh_session session = (ssh_session)user; switch (session->auth.state) { case SSH_AUTH_STATE_NONE: @@ -139,7 +141,8 @@ static const char *ssh_auth_get_current_method(ssh_session session) * SSH_AUTH_AGAIN In nonblocking mode, call has to be made again * SSH_AUTH_ERROR Error during the process. */ -static int ssh_userauth_get_response(ssh_session session) { +static int ssh_userauth_get_response(ssh_session session) +{ int rc = SSH_AUTH_ERROR; rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, @@ -199,7 +202,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_banner) { banner = ssh_buffer_get_ssh_string(packet); if (banner == NULL) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Invalid SSH_USERAUTH_BANNER packet"); } else { SSH_LOG(SSH_LOG_DEBUG, @@ -237,7 +240,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_failure) { if (partial) { session->auth.state = SSH_AUTH_STATE_PARTIAL; - SSH_LOG(SSH_LOG_INFO, + SSH_LOG(SSH_LOG_DEBUG, "Partial success for '%s'. Authentication that can continue: %s", current_method, auth_methods); @@ -247,7 +250,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_failure) { "Access denied for '%s'. Authentication that can continue: %s", current_method, auth_methods); - SSH_LOG(SSH_LOG_INFO, + SSH_LOG(SSH_LOG_DEBUG, "%s", ssh_get_error(session)); @@ -364,7 +367,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok) { * * @param[in] username Deprecated, set to NULL. * - * @returns A bitfield of the fllowing values: + * @returns A bitfield of the following values: * - SSH_AUTH_METHOD_PASSWORD * - SSH_AUTH_METHOD_PUBLICKEY * - SSH_AUTH_METHOD_HOSTBASED @@ -403,10 +406,11 @@ int ssh_userauth_list(ssh_session session, const char *username) * authentication. The username should only be set with ssh_options_set() only * before you connect to the server. */ -int ssh_userauth_none(ssh_session session, const char *username) { +int ssh_userauth_none(ssh_session session, const char *username) +{ int rc; - switch(session->pending_call_state) { + switch (session->pending_call_state) { case SSH_PENDING_CALL_NONE: break; case SSH_PENDING_CALL_AUTH_NONE: @@ -492,6 +496,7 @@ int ssh_userauth_try_publickey(ssh_session session, { ssh_string pubkey_s = NULL; const char *sig_type_c = NULL; + bool allowed; int rc; if (session == NULL) { @@ -513,7 +518,7 @@ int ssh_userauth_try_publickey(ssh_session session, SSH_FATAL, "Wrong state (%d) during pending SSH call", session->pending_call_state); - return SSH_ERROR; + return SSH_AUTH_ERROR; } /* Check if the given public key algorithm is allowed */ @@ -523,13 +528,21 @@ int ssh_userauth_try_publickey(ssh_session session, "Invalid key type (unknown)"); return SSH_AUTH_DENIED; } - if (!ssh_key_algorithm_allowed(session, sig_type_c)) { + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { ssh_set_error(session, SSH_REQUEST_DENIED, "The key algorithm '%s' is not allowed to be used by" " PUBLICKEY_ACCEPTED_TYPES configuration option", sig_type_c); return SSH_AUTH_DENIED; } + allowed = ssh_key_size_allowed(session, pubkey); + if (!allowed) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", sig_type_c, ssh_key_size(pubkey)); + return SSH_AUTH_DENIED; + } rc = ssh_userauth_request_service(session); if (rc == SSH_AGAIN) { @@ -544,6 +557,7 @@ int ssh_userauth_try_publickey(ssh_session session, goto fail; } + SSH_LOG(SSH_LOG_TRACE, "Trying signature type %s", sig_type_c); /* request */ rc = ssh_buffer_pack(session->out_buffer, "bsssbsS", SSH2_MSG_USERAUTH_REQUEST, @@ -611,6 +625,7 @@ int ssh_userauth_publickey(ssh_session session, const ssh_key privkey) { ssh_string str = NULL; + bool allowed; int rc; const char *sig_type_c = NULL; enum ssh_keytypes_e key_type; @@ -647,13 +662,21 @@ int ssh_userauth_publickey(ssh_session session, "Invalid key type (unknown)"); return SSH_AUTH_DENIED; } - if (!ssh_key_algorithm_allowed(session, sig_type_c)) { + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { ssh_set_error(session, SSH_REQUEST_DENIED, "The key algorithm '%s' is not allowed to be used by" " PUBLICKEY_ACCEPTED_TYPES configuration option", sig_type_c); return SSH_AUTH_DENIED; } + allowed = ssh_key_size_allowed(session, privkey); + if (!allowed) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", sig_type_c, ssh_key_size(privkey)); + return SSH_AUTH_DENIED; + } rc = ssh_userauth_request_service(session); if (rc == SSH_AGAIN) { @@ -668,6 +691,7 @@ int ssh_userauth_publickey(ssh_session session, goto fail; } + SSH_LOG(SSH_LOG_TRACE, "Sending signature type %s", sig_type_c); /* request */ rc = ssh_buffer_pack(session->out_buffer, "bsssbsS", SSH2_MSG_USERAUTH_REQUEST, @@ -722,7 +746,6 @@ fail: return SSH_AUTH_ERROR; } -#ifndef _WIN32 static int ssh_userauth_agent_publickey(ssh_session session, const char *username, ssh_key pubkey) @@ -730,18 +753,20 @@ static int ssh_userauth_agent_publickey(ssh_session session, ssh_string pubkey_s = NULL; ssh_string sig_blob = NULL; const char *sig_type_c = NULL; + bool allowed; int rc; - switch(session->pending_call_state) { - case SSH_PENDING_CALL_NONE: - break; - case SSH_PENDING_CALL_AUTH_AGENT: - goto pending; - default: - ssh_set_error(session, - SSH_FATAL, - "Bad call during pending SSH call in ssh_userauth_try_publickey"); - return SSH_ERROR; + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_AGENT: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Bad call during pending SSH call in %s", + __func__); + return SSH_ERROR; } rc = ssh_userauth_request_service(session); @@ -765,7 +790,8 @@ static int ssh_userauth_agent_publickey(ssh_session session, SSH_STRING_FREE(pubkey_s); return SSH_AUTH_DENIED; } - if (!ssh_key_algorithm_allowed(session, sig_type_c)) { + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { ssh_set_error(session, SSH_REQUEST_DENIED, "The key algorithm '%s' is not allowed to be used by" " PUBLICKEY_ACCEPTED_TYPES configuration option", @@ -773,17 +799,25 @@ static int ssh_userauth_agent_publickey(ssh_session session, SSH_STRING_FREE(pubkey_s); return SSH_AUTH_DENIED; } + allowed = ssh_key_size_allowed(session, pubkey); + if (!allowed) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", sig_type_c, ssh_key_size(pubkey)); + SSH_STRING_FREE(pubkey_s); + return SSH_AUTH_DENIED; + } /* request */ rc = ssh_buffer_pack(session->out_buffer, "bsssbsS", - SSH2_MSG_USERAUTH_REQUEST, - username ? username : session->opts.username, - "ssh-connection", - "publickey", - 1, /* private key */ - sig_type_c, /* algo */ - pubkey_s /* public key */ - ); + SSH2_MSG_USERAUTH_REQUEST, + username ? username : session->opts.username, + "ssh-connection", + "publickey", + 1, /* private key */ + sig_type_c, /* algo */ + pubkey_s /* public key */ + ); SSH_STRING_FREE(pubkey_s); if (rc < 0) { goto fail; @@ -827,6 +861,7 @@ fail: enum ssh_agent_state_e { SSH_AGENT_STATE_NONE = 0, SSH_AGENT_STATE_PUBKEY, + SSH_AGENT_STATE_CERT, SSH_AGENT_STATE_AUTH }; @@ -837,7 +872,8 @@ struct ssh_agent_state_struct { }; /* Internal function */ -void ssh_agent_state_free(void *data) { +void ssh_agent_state_free(void *data) +{ struct ssh_agent_state_struct *state = data; if (state) { @@ -870,9 +906,15 @@ void ssh_agent_state_free(void *data) { * before you connect to the server. */ int ssh_userauth_agent(ssh_session session, - const char *username) { + const char *username) +{ int rc = SSH_AUTH_ERROR; - struct ssh_agent_state_struct *state; + struct ssh_agent_state_struct *state = NULL; + ssh_key *configKeys = NULL; + ssh_key *configCerts = NULL; + size_t configKeysCount = 0; + size_t configCertsCount = 0; + size_t i; if (session == NULL) { return SSH_AUTH_ERROR; @@ -889,7 +931,7 @@ int ssh_userauth_agent(ssh_session session, return SSH_AUTH_ERROR; } ZERO_STRUCTP(session->agent_state); - session->agent_state->state=SSH_AGENT_STATE_NONE; + session->agent_state->state = SSH_AGENT_STATE_NONE; } state = session->agent_state; @@ -901,70 +943,239 @@ int ssh_userauth_agent(ssh_session session, return SSH_AUTH_DENIED; } + if (session->opts.identities_only) { + /* + * Read keys mentioned in the config, so we can check if key from agent + * is in there. + */ + size_t identityLen = ssh_list_count(session->opts.identity); + size_t certsLen = ssh_list_count(session->opts.certificate); + struct ssh_iterator *it = ssh_list_get_iterator(session->opts.identity); + + configKeys = malloc(identityLen * sizeof(ssh_key)); + configCerts = malloc((certsLen + identityLen) * sizeof(ssh_key)); + if (configKeys == NULL || configCerts == NULL) { + free(configKeys); + free(configCerts); + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + + while (it != NULL && configKeysCount < identityLen) { + const char *privkeyFile = it->data; + size_t certPathLen; + char *certFile = NULL; + ssh_key pubkey = NULL; + ssh_key cert = NULL; + + /* + * Read the private key file listed in the config, but we're only + * interested in the public key. Don't try to decrypt private key. + */ + rc = ssh_pki_import_pubkey_file(privkeyFile, &pubkey); + if (rc == SSH_OK) { + configKeys[configKeysCount++] = pubkey; + } else { + char *pubkeyFile = NULL; + size_t pubkeyPathLen = strlen(privkeyFile) + sizeof(".pub"); + + SSH_KEY_FREE(pubkey); + + /* + * If we couldn't get the public key from the private key file, + * try a .pub file instead. + */ + pubkeyFile = malloc(pubkeyPathLen); + if (!pubkeyFile) { + ssh_set_error_oom(session); + rc = SSH_AUTH_ERROR; + goto done; + } + snprintf(pubkeyFile, pubkeyPathLen, "%s.pub", privkeyFile); + rc = ssh_pki_import_pubkey_file(pubkeyFile, &pubkey); + free(pubkeyFile); + if (rc == SSH_OK) { + configKeys[configKeysCount++] = pubkey; + } else if (pubkey) { + SSH_KEY_FREE(pubkey); + } + } + /* Now try to see if there is a certificate with default name + * do not merge it yet with the key as we need to try first the + * non-certified key */ + certPathLen = strlen(privkeyFile) + sizeof("-cert.pub"); + certFile = malloc(certPathLen); + if (!certFile) { + ssh_set_error_oom(session); + rc = SSH_AUTH_ERROR; + goto done; + } + snprintf(certFile, certPathLen, "%s-cert.pub", privkeyFile); + rc = ssh_pki_import_cert_file(certFile, &cert); + free(certFile); + if (rc == SSH_OK) { + configCerts[configCertsCount++] = cert; + } else if (cert) { + SSH_KEY_FREE(cert); + } + + it = it->next; + } + /* And now load separately-listed certificates. */ + it = ssh_list_get_iterator(session->opts.certificate); + while (it != NULL && configCertsCount < certsLen + identityLen) { + const char *certFile = it->data; + ssh_key cert = NULL; + + rc = ssh_pki_import_cert_file(certFile, &cert); + if (rc == SSH_OK) { + configCerts[configCertsCount++] = cert; + } else if (cert) { + SSH_KEY_FREE(cert); + } + + it = it->next; + } + } + while (state->pubkey != NULL) { if (state->state == SSH_AGENT_STATE_NONE) { SSH_LOG(SSH_LOG_DEBUG, - "Trying identity %s", state->comment); + "Trying identity %s", + state->comment); + if (session->opts.identities_only) { + /* Check if this key is one of the keys listed in the config */ + bool found_key = false; + for (i = 0; i < configKeysCount; i++) { + int cmp = ssh_key_cmp(state->pubkey, + configKeys[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + found_key = true; + break; + } + } + /* or in separate certificates */ + for (i = 0; i < configCertsCount; i++) { + int cmp = ssh_key_cmp(state->pubkey, + configCerts[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + found_key = true; + break; + } + } + + if (!found_key) { + SSH_LOG(SSH_LOG_DEBUG, + "Identities only is enabled and identity %s was " + "not listed in config, skipping", + state->comment); + SSH_STRING_FREE_CHAR(state->comment); + state->comment = NULL; + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_agent_get_next_ident( + session, &state->comment); + + if (state->pubkey == NULL) { + rc = SSH_AUTH_DENIED; + } + continue; + } + } } if (state->state == SSH_AGENT_STATE_NONE || - state->state == SSH_AGENT_STATE_PUBKEY) { + state->state == SSH_AGENT_STATE_PUBKEY || + state->state == SSH_AGENT_STATE_CERT) { rc = ssh_userauth_try_publickey(session, username, state->pubkey); if (rc == SSH_AUTH_ERROR) { - ssh_agent_state_free (state); + ssh_agent_state_free(state); session->agent_state = NULL; - return rc; + goto done; } else if (rc == SSH_AUTH_AGAIN) { - state->state = SSH_AGENT_STATE_PUBKEY; - return rc; + state->state = (state->state == SSH_AGENT_STATE_NONE ? + SSH_AGENT_STATE_PUBKEY : state->state); + goto done; } else if (rc != SSH_AUTH_SUCCESS) { SSH_LOG(SSH_LOG_DEBUG, - "Public key of %s refused by server", state->comment); + "Public key of %s refused by server", + state->comment); + if (state->state == SSH_AGENT_STATE_PUBKEY) { + for (i = 0; i < configCertsCount; i++) { + int cmp = ssh_key_cmp(state->pubkey, + configCerts[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + SSH_LOG(SSH_LOG_DEBUG, + "Retry with matching certificate"); + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_key_dup(configCerts[i]); + state->state = SSH_AGENT_STATE_CERT; + continue; + } + } + } SSH_STRING_FREE_CHAR(state->comment); state->comment = NULL; - ssh_key_free(state->pubkey); - state->pubkey = ssh_agent_get_next_ident(session, &state->comment); + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_agent_get_next_ident(session, + &state->comment); state->state = SSH_AGENT_STATE_NONE; continue; } SSH_LOG(SSH_LOG_DEBUG, - "Public key of %s accepted by server", state->comment); + "Public key of %s accepted by server", + state->comment); state->state = SSH_AGENT_STATE_AUTH; } if (state->state == SSH_AGENT_STATE_AUTH) { rc = ssh_userauth_agent_publickey(session, username, state->pubkey); - if (rc == SSH_AUTH_AGAIN) - return rc; + if (rc == SSH_AUTH_AGAIN) { + goto done; + } SSH_STRING_FREE_CHAR(state->comment); state->comment = NULL; if (rc == SSH_AUTH_ERROR || rc == SSH_AUTH_PARTIAL) { - ssh_agent_state_free (session->agent_state); + ssh_agent_state_free(session->agent_state); session->agent_state = NULL; - return rc; + goto done; } else if (rc != SSH_AUTH_SUCCESS) { - SSH_LOG(SSH_LOG_INFO, + SSH_LOG(SSH_LOG_DEBUG, "Server accepted public key but refused the signature"); - ssh_key_free(state->pubkey); - state->pubkey = ssh_agent_get_next_ident(session, &state->comment); + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_agent_get_next_ident(session, + &state->comment); state->state = SSH_AGENT_STATE_NONE; continue; } ssh_agent_state_free (session->agent_state); session->agent_state = NULL; - return SSH_AUTH_SUCCESS; + rc = SSH_AUTH_SUCCESS; + goto done; } } ssh_agent_state_free (session->agent_state); session->agent_state = NULL; +done: + for (i = 0; i < configKeysCount; i++) { + ssh_key_free(configKeys[i]); + } + free(configKeys); + for (i = 0; i < configCertsCount; i++) { + ssh_key_free(configCerts[i]); + } + free(configCerts); return rc; } -#endif enum ssh_auth_auto_state_e { SSH_AUTH_AUTO_STATE_NONE = 0, SSH_AUTH_AUTO_STATE_PUBKEY, SSH_AUTH_AUTO_STATE_KEY_IMPORTED, + SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE, + SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION, SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED }; @@ -973,9 +1184,61 @@ struct ssh_auth_auto_state_struct { struct ssh_iterator *it; ssh_key privkey; ssh_key pubkey; + ssh_key cert; + struct ssh_iterator *cert_it; }; /** + * @brief Get the identity that is currently being processed by + * ssh_userauth_publickey_auto() + * + * This is meant to be used by a callback that happens as part of the + * execution of ssh_userauth_publickey_auto(). The auth_function + * callback might want to know which key a passphrase is needed for, + * for example. + * + * @param[in] session The SSH session. + * + * @param[out] value The value to get into. As a char**, space will be + * allocated by the function for the value, it is + * your responsibility to free the memory using + * ssh_string_free_char(). + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int ssh_userauth_publickey_auto_get_current_identity(ssh_session session, + char** value) +{ + const char *id = NULL; + + if (session == NULL) { + return SSH_ERROR; + } + + if (value == NULL) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + + if (session->auth.auto_state != NULL && + session->auth.auto_state->it != NULL) { + id = session->auth.auto_state->it->data; + } + + if (id == NULL) { + return SSH_ERROR; + } + + *value = strdup(id); + if (*value == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + return SSH_OK; +} + +/** * @brief Tries to automatically authenticate with public key and "none" * * It may fail, for instance it doesn't ask for a password and uses a default @@ -995,14 +1258,16 @@ struct ssh_auth_auto_state_struct { * method.\n * SSH_AUTH_PARTIAL: You've been partially authenticated, you still * have to use another method.\n - * SSH_AUTH_SUCCESS: The public key is accepted, you want now to use - * ssh_userauth_publickey().\n + * SSH_AUTH_SUCCESS: Authentication success\n * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again * later. * * @note Most server implementations do not permit changing the username during * authentication. The username should only be set with ssh_options_set() only * before you connect to the server. + * + * The OpenSSH iterates over the identities and first try the plain public key + * and then the certificate if it is in place. */ int ssh_userauth_publickey_auto(ssh_session session, const char *username, @@ -1010,7 +1275,7 @@ int ssh_userauth_publickey_auto(ssh_session session, { ssh_auth_callback auth_fn = NULL; void *auth_data = NULL; - struct ssh_auth_auto_state_struct *state; + struct ssh_auth_auto_state_struct *state = NULL; int rc; if (session == NULL) { @@ -1037,15 +1302,13 @@ int ssh_userauth_publickey_auto(ssh_session session, } state = session->auth.auto_state; if (state->state == SSH_AUTH_AUTO_STATE_NONE) { -#ifndef _WIN32 /* Try authentication with ssh-agent first */ rc = ssh_userauth_agent(session, username); if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL || - rc == SSH_AUTH_AGAIN ) { + rc == SSH_AUTH_AGAIN) { return rc; } -#endif state->state = SSH_AUTH_AUTO_STATE_PUBKEY; } if (state->it == NULL) { @@ -1054,14 +1317,17 @@ int ssh_userauth_publickey_auto(ssh_session session, while (state->it != NULL) { const char *privkey_file = state->it->data; - char pubkey_file[1024] = {0}; + char pubkey_file[PATH_MAX] = {0}; if (state->state == SSH_AUTH_AUTO_STATE_PUBKEY) { SSH_LOG(SSH_LOG_DEBUG, - "Trying to authenticate with %s", privkey_file); + "Trying to authenticate with %s", + privkey_file); + state->cert = NULL; state->privkey = NULL; state->pubkey = NULL; +#ifdef WITH_PKCS11_URI if (ssh_pki_is_uri(privkey_file)) { char *pub_uri_from_priv = NULL; SSH_LOG(SSH_LOG_INFO, @@ -1070,84 +1336,165 @@ int ssh_userauth_publickey_auto(ssh_session session, if (pub_uri_from_priv == NULL) { return SSH_ERROR; } else { - snprintf(pubkey_file, sizeof(pubkey_file), "%s", + snprintf(pubkey_file, + sizeof(pubkey_file), + "%s", pub_uri_from_priv); SAFE_FREE(pub_uri_from_priv); } - } else { - snprintf(pubkey_file, sizeof(pubkey_file), "%s.pub", privkey_file); + } else +#endif /* WITH_PKCS11_URI */ + { + snprintf(pubkey_file, + sizeof(pubkey_file), + "%s.pub", + privkey_file); } rc = ssh_pki_import_pubkey_file(pubkey_file, &state->pubkey); if (rc == SSH_ERROR) { ssh_set_error(session, - SSH_FATAL, - "Failed to import public key: %s", - pubkey_file); + SSH_FATAL, + "Failed to import public key: %s", + pubkey_file); SAFE_FREE(session->auth.auto_state); return SSH_AUTH_ERROR; } else if (rc == SSH_EOF) { /* Read the private key and save the public key to file */ rc = ssh_pki_import_privkey_file(privkey_file, - passphrase, - auth_fn, - auth_data, - &state->privkey); + passphrase, + auth_fn, + auth_data, + &state->privkey); if (rc == SSH_ERROR) { ssh_set_error(session, - SSH_FATAL, - "Failed to read private key: %s", - privkey_file); - state->it=state->it->next; + SSH_FATAL, + "Failed to read private key: %s", + privkey_file); + state->it = state->it->next; continue; } else if (rc == SSH_EOF) { /* If the file doesn't exist, continue */ SSH_LOG(SSH_LOG_DEBUG, "Private key %s doesn't exist.", privkey_file); - state->it=state->it->next; + state->it = state->it->next; continue; } - rc = ssh_pki_export_privkey_to_pubkey(state->privkey, &state->pubkey); + rc = ssh_pki_export_privkey_to_pubkey(state->privkey, + &state->pubkey); if (rc == SSH_ERROR) { - ssh_key_free(state->privkey); + SSH_KEY_FREE(state->privkey); SAFE_FREE(session->auth.auto_state); return SSH_AUTH_ERROR; } rc = ssh_pki_export_pubkey_file(state->pubkey, pubkey_file); if (rc == SSH_ERROR) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Could not write public key to file: %s", pubkey_file); } } state->state = SSH_AUTH_AUTO_STATE_KEY_IMPORTED; } - if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED) { - rc = ssh_userauth_try_publickey(session, username, state->pubkey); + if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED || + state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE || + state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION) { + ssh_key k = state->pubkey; + if (state->state != SSH_AUTH_AUTO_STATE_KEY_IMPORTED) { + k = state->cert; + } + rc = ssh_userauth_try_publickey(session, username, k); if (rc == SSH_AUTH_ERROR) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Public key authentication error for %s", privkey_file); - ssh_key_free(state->privkey); - state->privkey = NULL; - ssh_key_free(state->pubkey); - state->pubkey = NULL; + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); SAFE_FREE(session->auth.auto_state); return rc; } else if (rc == SSH_AUTH_AGAIN) { return rc; } else if (rc != SSH_AUTH_SUCCESS) { + int r; /* do not reuse `rc` as it is used to return from here */ + SSH_KEY_FREE(state->cert); SSH_LOG(SSH_LOG_DEBUG, - "Public key for %s refused by server", - privkey_file); - ssh_key_free(state->privkey); - state->privkey = NULL; - ssh_key_free(state->pubkey); - state->pubkey = NULL; - state->it=state->it->next; + "Public key for %s%s refused by server", + privkey_file, + (state->state != SSH_AUTH_AUTO_STATE_KEY_IMPORTED + ? " (with certificate)" : "")); + /* Try certificate file by appending -cert.pub (if present) */ + if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED) { + char cert_file[PATH_MAX] = {0}; + ssh_key cert = NULL; + + snprintf(cert_file, + sizeof(cert_file), + "%s-cert.pub", + privkey_file); + SSH_LOG(SSH_LOG_TRACE, + "Trying to load the certificate %s (default path)", + cert_file); + r = ssh_pki_import_cert_file(cert_file, &cert); + if (r == SSH_OK) { + /* TODO check the pubkey and certs match */ + SSH_LOG(SSH_LOG_TRACE, + "Certificate loaded %s. Retry the authentication.", + cert_file); + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE; + SSH_KEY_FREE(state->cert); + state->cert = cert; + /* try to authenticate with this certificate */ + continue; + } + /* if the file does not exists, try configuration options */ + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION; + } + /* Try certificate files loaded through options */ + if (state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION) { + SSH_KEY_FREE(state->cert); + if (state->cert_it == NULL) { + state->cert_it = ssh_list_get_iterator(session->opts.certificate); + } + while (state->cert_it != NULL) { + const char *cert_file = state->cert_it->data; + ssh_key cert = NULL; + + SSH_LOG(SSH_LOG_TRACE, + "Trying to load the certificate %s (options)", + cert_file); + r = ssh_pki_import_cert_file(cert_file, &cert); + if (r == SSH_OK) { + int cmp = ssh_key_cmp(cert, + state->pubkey, + SSH_KEY_CMP_PUBLIC); + if (cmp != 0) { + state->cert_it = state->cert_it->next; + SSH_KEY_FREE(cert); + continue; /* with next cert */ + } + SSH_LOG(SSH_LOG_TRACE, + "Found matching certificate %s in options. Retry the authentication.", + cert_file); + state->cert = cert; + cert = NULL; + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION; + /* try to authenticate with this identity */ + break; /* try this cert */ + } + /* continue with next identity */ + } + if (state->cert != NULL) { + continue; /* retry with the certificate */ + } + } + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); + state->it = state->it->next; state->state = SSH_AUTH_AUTO_STATE_PUBKEY; continue; } @@ -1157,25 +1504,25 @@ int ssh_userauth_publickey_auto(ssh_session session, /* Public key has been accepted by the server */ if (state->privkey == NULL) { rc = ssh_pki_import_privkey_file(privkey_file, - passphrase, - auth_fn, - auth_data, - &state->privkey); + passphrase, + auth_fn, + auth_data, + &state->privkey); if (rc == SSH_ERROR) { - ssh_key_free(state->pubkey); - state->pubkey=NULL; + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->pubkey); ssh_set_error(session, - SSH_FATAL, - "Failed to read private key: %s", - privkey_file); - state->it=state->it->next; + SSH_FATAL, + "Failed to read private key: %s", + privkey_file); + state->it = state->it->next; state->state = SSH_AUTH_AUTO_STATE_PUBKEY; continue; } else if (rc == SSH_EOF) { /* If the file doesn't exist, continue */ - ssh_key_free(state->pubkey); - state->pubkey = NULL; - SSH_LOG(SSH_LOG_INFO, + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->pubkey); + SSH_LOG(SSH_LOG_DEBUG, "Private key %s doesn't exist.", privkey_file); state->it = state->it->next; @@ -1183,16 +1530,33 @@ int ssh_userauth_publickey_auto(ssh_session session, continue; } } + if (state->cert != NULL && !is_cert_type(state->privkey->cert_type)) { + rc = ssh_pki_copy_cert_to_privkey(state->cert, state->privkey); + if (rc != SSH_OK) { + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); + ssh_set_error(session, + SSH_FATAL, + "Failed to copy cert to private key"); + state->it = state->it->next; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + continue; + } + } rc = ssh_userauth_publickey(session, username, state->privkey); if (rc != SSH_AUTH_AGAIN && rc != SSH_AUTH_DENIED) { - ssh_key_free(state->privkey); - ssh_key_free(state->pubkey); + bool cert_used = (state->cert != NULL); + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); SAFE_FREE(session->auth.auto_state); if (rc == SSH_AUTH_SUCCESS) { - SSH_LOG(SSH_LOG_INFO, - "Successfully authenticated using %s", - privkey_file); + SSH_LOG(SSH_LOG_DEBUG, + "Successfully authenticated using %s%s", + privkey_file, + (cert_used ? " and certificate" : "")); } return rc; } @@ -1200,18 +1564,19 @@ int ssh_userauth_publickey_auto(ssh_session session, return rc; } - ssh_key_free(state->privkey); - ssh_key_free(state->pubkey); + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_DEBUG, "The server accepted the public key but refused the signature"); state->it = state->it->next; state->state = SSH_AUTH_AUTO_STATE_PUBKEY; /* continue */ } } - SSH_LOG(SSH_LOG_INFO, - "Tried every public key, none matched"); + SSH_LOG(SSH_LOG_WARN, + "Access denied: Tried every public key, none matched"); SAFE_FREE(session->auth.auto_state); return SSH_AUTH_DENIED; } @@ -1250,10 +1615,11 @@ int ssh_userauth_publickey_auto(ssh_session session, */ int ssh_userauth_password(ssh_session session, const char *username, - const char *password) { + const char *password) +{ int rc; - switch(session->pending_call_state) { + switch (session->pending_call_state) { case SSH_PENDING_CALL_NONE: break; case SSH_PENDING_CALL_AUTH_PASSWORD: @@ -1311,7 +1677,6 @@ fail: return SSH_AUTH_ERROR; } -#ifndef _WIN32 /* LEGACY */ int ssh_userauth_agent_pubkey(ssh_session session, const char *username, @@ -1328,20 +1693,26 @@ int ssh_userauth_agent_pubkey(ssh_session session, key->type = publickey->type; key->type_c = ssh_key_type_to_char(key->type); key->flags = SSH_KEY_FLAG_PUBLIC; - key->dsa = publickey->dsa_pub; +#ifndef HAVE_LIBCRYPTO key->rsa = publickey->rsa_pub; +#else + key->key = publickey->key_pub; +#endif /* HAVE_LIBCRYPTO */ rc = ssh_userauth_agent_publickey(session, username, key); - key->dsa = NULL; +#ifndef HAVE_LIBCRYPTO key->rsa = NULL; +#else + key->key = NULL; +#endif /* HAVE_LIBCRYPTO */ ssh_key_free(key); return rc; } -#endif /* _WIN32 */ -ssh_kbdint ssh_kbdint_new(void) { +ssh_kbdint ssh_kbdint_new(void) +{ ssh_kbdint kbd; kbd = calloc(1, sizeof(struct ssh_kbdint_struct)); @@ -1353,7 +1724,8 @@ ssh_kbdint ssh_kbdint_new(void) { } -void ssh_kbdint_free(ssh_kbdint kbd) { +void ssh_kbdint_free(ssh_kbdint kbd) +{ size_t i, n; if (kbd == NULL) { @@ -1389,7 +1761,8 @@ void ssh_kbdint_free(ssh_kbdint kbd) { SAFE_FREE(kbd); } -void ssh_kbdint_clean(ssh_kbdint kbd) { +void ssh_kbdint_clean(ssh_kbdint kbd) +{ size_t i, n; if (kbd == NULL) { @@ -1589,10 +1962,10 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request) { } SSH_LOG(SSH_LOG_DEBUG, - "%d keyboard-interactive prompts", nprompts); + "%" PRIu32 " keyboard-interactive prompts", nprompts); if (nprompts > KBDINT_MAX_PROMPT) { ssh_set_error(session, SSH_FATAL, - "Too much prompts requested by the server: %u (0x%.4x)", + "Too much prompts requested by the server: %" PRIu32 " (0x%.4" PRIx32 ")", nprompts, nprompts); ssh_kbdint_free(session->kbdint); session->kbdint = NULL; @@ -1667,7 +2040,8 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request) { * @see ssh_userauth_kbdint_setanswer() */ int ssh_userauth_kbdint(ssh_session session, const char *user, - const char *submethods) { + const char *submethods) +{ int rc = SSH_AUTH_ERROR; if (session == NULL) { @@ -1709,7 +2083,8 @@ int ssh_userauth_kbdint(ssh_session session, const char *user, * * @returns The number of prompts. */ -int ssh_userauth_kbdint_getnprompts(ssh_session session) { +int ssh_userauth_kbdint_getnprompts(ssh_session session) +{ if (session == NULL) { return SSH_ERROR; } @@ -1731,7 +2106,8 @@ int ssh_userauth_kbdint_getnprompts(ssh_session session) { * * @returns The name of the message block. Do not free it. */ -const char *ssh_userauth_kbdint_getname(ssh_session session) { +const char *ssh_userauth_kbdint_getname(ssh_session session) +{ if (session == NULL) { return NULL; } @@ -1754,7 +2130,8 @@ const char *ssh_userauth_kbdint_getname(ssh_session session) { * @returns The instruction of the message block. */ -const char *ssh_userauth_kbdint_getinstruction(ssh_session session) { +const char *ssh_userauth_kbdint_getinstruction(ssh_session session) +{ if (session == NULL) return NULL; if (session->kbdint == NULL) { @@ -1789,8 +2166,9 @@ const char *ssh_userauth_kbdint_getinstruction(ssh_session session) { * if (echo) ... * @endcode */ -const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, - char *echo) { +const char * +ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, char *echo) +{ if (session == NULL) return NULL; if (session->kbdint == NULL) { @@ -1817,7 +2195,8 @@ const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, * * @returns The number of answers. */ -int ssh_userauth_kbdint_getnanswers(ssh_session session) { +int ssh_userauth_kbdint_getnanswers(ssh_session session) +{ if (session == NULL || session->kbdint == NULL) { return SSH_ERROR; } @@ -1825,15 +2204,17 @@ int ssh_userauth_kbdint_getnanswers(ssh_session session) { } /** - * @brief Get the answer for a question from a message block. + * @brief Get the answer to a question from a message block. * * @param[in] session The ssh session to use. * * @param[in] i index The number of the ith answer. * - * @return 0 on success, < 0 on error. + * @return The answer string, or NULL if the answer is not + * available. Do not free the string. */ -const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) { +const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) +{ if (session == NULL || session->kbdint == NULL || session->kbdint->answers == NULL) { return NULL; @@ -1864,8 +2245,10 @@ const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) { * * @return 0 on success, < 0 on error. */ -int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, - const char *answer) { +int +ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, + const char *answer) +{ if (session == NULL) { return -1; } @@ -1911,7 +2294,8 @@ int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again * later. */ -int ssh_userauth_gssapi(ssh_session session) { +int ssh_userauth_gssapi(ssh_session session) +{ int rc = SSH_AUTH_DENIED; #ifdef WITH_GSSAPI switch(session->pending_call_state) { @@ -1933,7 +2317,7 @@ int ssh_userauth_gssapi(ssh_session session) { } else if (rc == SSH_ERROR) { return SSH_AUTH_ERROR; } - SSH_LOG(SSH_LOG_PROTOCOL, "Authenticating with gssapi-with-mic"); + SSH_LOG(SSH_LOG_DEBUG, "Authenticating with gssapi-with-mic"); session->auth.current_method = SSH_AUTH_METHOD_GSSAPI_MIC; session->auth.state = SSH_AUTH_STATE_NONE; diff --git a/src/bignum.c b/src/bignum.c index ef8de31f..72429460 100644 --- a/src/bignum.c +++ b/src/bignum.c @@ -44,7 +44,7 @@ ssh_string ssh_make_bignum_string(bignum num) { #ifdef DEBUG_CRYPTO SSH_LOG(SSH_LOG_TRACE, - "%zu bits, %zu bytes, %zu padding\n", + "%zu bits, %zu bytes, %zu padding", bits, len, pad); #endif /* DEBUG_CRYPTO */ @@ -70,7 +70,7 @@ bignum ssh_make_string_bn(ssh_string string) #ifdef DEBUG_CRYPTO SSH_LOG(SSH_LOG_TRACE, - "Importing a %zu bits, %zu bytes object ...\n", + "Importing a %zu bits, %zu bytes object ...", len * 8, len); #endif /* DEBUG_CRYPTO */ @@ -86,12 +86,7 @@ void ssh_print_bignum(const char *name, const_bignum num) if (num != NULL) { bignum_bn2hex(num, &hex); } - fprintf(stderr, "%s value: %s\n", name, (hex == NULL) ? "(null)" : (char *) hex); -#ifdef HAVE_LIBGCRYPT - SAFE_FREE(hex); -#elif defined HAVE_LIBCRYPTO - OPENSSL_free(hex); -#elif defined HAVE_LIBMBEDCRYPTO - SAFE_FREE(hex); -#endif + SSH_LOG(SSH_LOG_DEBUG, "%s value: %s", name, + (hex == NULL) ? "(null)" : (char *)hex); + ssh_crypto_free(hex); } @@ -79,6 +79,7 @@ static socket_t bind_socket(ssh_bind sshbind, const char *hostname, int opt = 1; socket_t s; int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; ZERO_STRUCT(hints); @@ -98,7 +99,8 @@ static socket_t bind_socket(ssh_bind sshbind, const char *hostname, ai->ai_socktype, ai->ai_protocol); if (s == SSH_INVALID_SOCKET) { - ssh_set_error(sshbind, SSH_FATAL, "%s", strerror(errno)); + ssh_set_error(sshbind, SSH_FATAL, "%s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); freeaddrinfo (ai); return -1; } @@ -108,7 +110,7 @@ static socket_t bind_socket(ssh_bind sshbind, const char *hostname, ssh_set_error(sshbind, SSH_FATAL, "Setting socket options failed: %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); freeaddrinfo (ai); CLOSE_SOCKET(s); return -1; @@ -120,7 +122,7 @@ static socket_t bind_socket(ssh_bind sshbind, const char *hostname, "Binding to %s:%d: %s", hostname, port, - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); freeaddrinfo (ai); CLOSE_SOCKET(s); return -1; @@ -147,15 +149,6 @@ ssh_bind ssh_bind_new(void) { static int ssh_bind_import_keys(ssh_bind sshbind) { int rc; - if (sshbind->ecdsakey == NULL && - sshbind->dsakey == NULL && - sshbind->rsakey == NULL && - sshbind->ed25519key == NULL) { - ssh_set_error(sshbind, SSH_FATAL, - "ECDSA, ED25519, DSA, or RSA host key file must be set"); - return SSH_ERROR; - } - #ifdef HAVE_ECC if (sshbind->ecdsa == NULL && sshbind->ecdsakey != NULL) { rc = ssh_pki_import_privkey_file(sshbind->ecdsakey, @@ -179,30 +172,6 @@ static int ssh_bind_import_keys(ssh_bind sshbind) { } #endif -#ifdef HAVE_DSA - if (sshbind->dsa == NULL && sshbind->dsakey != NULL) { - rc = ssh_pki_import_privkey_file(sshbind->dsakey, - NULL, - NULL, - NULL, - &sshbind->dsa); - if (rc == SSH_ERROR || rc == SSH_EOF) { - ssh_set_error(sshbind, SSH_FATAL, - "Failed to import private DSA host key"); - return SSH_ERROR; - } - - if (ssh_key_type(sshbind->dsa) != SSH_KEYTYPE_DSS) { - ssh_set_error(sshbind, SSH_FATAL, - "The DSA host key has the wrong type: %d", - ssh_key_type(sshbind->dsa)); - ssh_key_free(sshbind->dsa); - sshbind->dsa = NULL; - return SSH_ERROR; - } - } -#endif - if (sshbind->rsa == NULL && sshbind->rsakey != NULL) { rc = ssh_pki_import_privkey_file(sshbind->rsakey, NULL, @@ -249,97 +218,124 @@ static int ssh_bind_import_keys(ssh_bind sshbind) { } int ssh_bind_listen(ssh_bind sshbind) { - const char *host; - socket_t fd; - int rc; + const char *host; + socket_t fd; + int rc; - if (sshbind->rsa == NULL && - sshbind->dsa == NULL && - sshbind->ecdsa == NULL && - sshbind->ed25519 == NULL) { - rc = ssh_bind_import_keys(sshbind); - if (rc != SSH_OK) { - return SSH_ERROR; - } - } + /* Apply global bind configurations, if it hasn't been applied before */ + rc = ssh_bind_options_parse_config(sshbind, NULL); + if (rc != 0) { + ssh_set_error(sshbind, SSH_FATAL,"Could not parse global config"); + return SSH_ERROR; + } - if (sshbind->bindfd == SSH_INVALID_SOCKET) { - host = sshbind->bindaddr; - if (host == NULL) { - host = "0.0.0.0"; - } + /* Set default hostkey paths if no hostkey was found before */ + if (sshbind->ecdsakey == NULL && + sshbind->rsakey == NULL && + sshbind->ed25519key == NULL) { - fd = bind_socket(sshbind, host, sshbind->bindport); - if (fd == SSH_INVALID_SOCKET) { - ssh_key_free(sshbind->dsa); - sshbind->dsa = NULL; - ssh_key_free(sshbind->rsa); - sshbind->rsa = NULL; - /* XXX should this clear also other structures that were allocated */ - return -1; - } + sshbind->ecdsakey = strdup("/etc/ssh/ssh_host_ecdsa_key"); + sshbind->rsakey = strdup("/etc/ssh/ssh_host_rsa_key"); + sshbind->ed25519key = strdup("/etc/ssh/ssh_host_ed25519_key"); + } - if (listen(fd, 10) < 0) { - ssh_set_error(sshbind, SSH_FATAL, - "Listening to socket %d: %s", - fd, strerror(errno)); - CLOSE_SOCKET(fd); - ssh_key_free(sshbind->dsa); - sshbind->dsa = NULL; - ssh_key_free(sshbind->rsa); - sshbind->rsa = NULL; - /* XXX should this clear also other structures that were allocated */ - return -1; - } + /* Apply global bind configurations, if it hasn't been applied before */ + rc = ssh_bind_options_parse_config(sshbind, NULL); + if (rc != 0) { + ssh_set_error(sshbind, SSH_FATAL, "Could not parse global config"); + return SSH_ERROR; + } + + /* Set default hostkey paths if no hostkey was found before */ + if (sshbind->ecdsakey == NULL && + sshbind->rsakey == NULL && + sshbind->ed25519key == NULL) { + + sshbind->ecdsakey = strdup("/etc/ssh/ssh_host_ecdsa_key"); + sshbind->rsakey = strdup("/etc/ssh/ssh_host_rsa_key"); + sshbind->ed25519key = strdup("/etc/ssh/ssh_host_ed25519_key"); + } + + if (sshbind->rsa == NULL && + sshbind->ecdsa == NULL && + sshbind->ed25519 == NULL) { + rc = ssh_bind_import_keys(sshbind); + if (rc != SSH_OK) { + return SSH_ERROR; + } + } + + if (sshbind->bindfd == SSH_INVALID_SOCKET) { + host = sshbind->bindaddr; + if (host == NULL) { + host = "0.0.0.0"; + } + + fd = bind_socket(sshbind, host, sshbind->bindport); + if (fd == SSH_INVALID_SOCKET) { + return SSH_ERROR; + } + + if (listen(fd, 10) < 0) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(sshbind, + SSH_FATAL, + "Listening to socket %d: %s", + fd, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + CLOSE_SOCKET(fd); + return SSH_ERROR; + } - sshbind->bindfd = fd; + sshbind->bindfd = fd; } else { - SSH_LOG(SSH_LOG_INFO, "Using app-provided bind socket"); + SSH_LOG(SSH_LOG_DEBUG, "Using app-provided bind socket"); } return 0; } -int ssh_bind_set_callbacks(ssh_bind sshbind, ssh_bind_callbacks callbacks, - void *userdata){ - if (sshbind == NULL) { - return SSH_ERROR; - } - if (callbacks == NULL) { - ssh_set_error_invalid(sshbind); - return SSH_ERROR; - } - if(callbacks->size <= 0 || callbacks->size > 1024 * sizeof(void *)){ - ssh_set_error(sshbind,SSH_FATAL, - "Invalid callback passed in (badly initialized)"); - return SSH_ERROR; - } - sshbind->bind_callbacks = callbacks; - sshbind->bind_callbacks_userdata=userdata; - return 0; +int ssh_bind_set_callbacks(ssh_bind sshbind, ssh_bind_callbacks callbacks, void *userdata) +{ + if (sshbind == NULL) { + return SSH_ERROR; + } + if (callbacks == NULL) { + ssh_set_error_invalid(sshbind); + return SSH_ERROR; + } + if (callbacks->size <= 0 || callbacks->size > 1024 * sizeof(void *)) { + ssh_set_error(sshbind, + SSH_FATAL, + "Invalid callback passed in (badly initialized)"); + return SSH_ERROR; + } + sshbind->bind_callbacks = callbacks; + sshbind->bind_callbacks_userdata = userdata; + return 0; } /** @internal * @brief callback being called by poll when an event happens * */ -static int ssh_bind_poll_callback(ssh_poll_handle sshpoll, - socket_t fd, int revents, void *user){ - ssh_bind sshbind=(ssh_bind)user; - (void)sshpoll; - (void)fd; - - if(revents & POLLIN){ - /* new incoming connection */ - if(ssh_callbacks_exists(sshbind->bind_callbacks,incoming_connection)){ - sshbind->bind_callbacks->incoming_connection(sshbind, - sshbind->bind_callbacks_userdata); +static int ssh_bind_poll_callback(ssh_poll_handle sshpoll, socket_t fd, int revents, void *user) +{ + ssh_bind sshbind = (ssh_bind)user; + (void)sshpoll; + (void)fd; + + if (revents & POLLIN) { + /* new incoming connection */ + if (ssh_callbacks_exists(sshbind->bind_callbacks, incoming_connection)) { + sshbind->bind_callbacks->incoming_connection(sshbind, + sshbind->bind_callbacks_userdata); + } } - } - return 0; + return 0; } /** @internal - * @brief returns the current poll handle, or create it + * @brief returns the current poll handle, or creates it * @param sshbind the ssh_bind object * @returns a ssh_poll handle suitable for operation */ @@ -363,20 +359,24 @@ ssh_poll_handle ssh_bind_get_poll(ssh_bind sshbind) return sshbind->poll; } -void ssh_bind_set_blocking(ssh_bind sshbind, int blocking) { - sshbind->blocking = blocking ? 1 : 0; +void ssh_bind_set_blocking(ssh_bind sshbind, int blocking) +{ + sshbind->blocking = blocking ? 1 : 0; } -socket_t ssh_bind_get_fd(ssh_bind sshbind) { - return sshbind->bindfd; +socket_t ssh_bind_get_fd(ssh_bind sshbind) +{ + return sshbind->bindfd; } -void ssh_bind_set_fd(ssh_bind sshbind, socket_t fd) { - sshbind->bindfd = fd; +void ssh_bind_set_fd(ssh_bind sshbind, socket_t fd) +{ + sshbind->bindfd = fd; } -void ssh_bind_fd_toaccept(ssh_bind sshbind) { - sshbind->toaccept = 1; +void ssh_bind_fd_toaccept(ssh_bind sshbind) +{ + sshbind->toaccept = 1; } void ssh_bind_free(ssh_bind sshbind){ @@ -393,17 +393,15 @@ void ssh_bind_free(ssh_bind sshbind){ /* options */ SAFE_FREE(sshbind->banner); + SAFE_FREE(sshbind->moduli_file); SAFE_FREE(sshbind->bindaddr); SAFE_FREE(sshbind->config_dir); SAFE_FREE(sshbind->pubkey_accepted_key_types); - SAFE_FREE(sshbind->dsakey); SAFE_FREE(sshbind->rsakey); SAFE_FREE(sshbind->ecdsakey); SAFE_FREE(sshbind->ed25519key); - ssh_key_free(sshbind->dsa); - sshbind->dsa = NULL; ssh_key_free(sshbind->rsa); sshbind->rsa = NULL; ssh_key_free(sshbind->ecdsa); @@ -420,7 +418,9 @@ void ssh_bind_free(ssh_bind sshbind){ SAFE_FREE(sshbind); } -int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd){ +int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd) +{ + ssh_poll_handle handle = NULL; int i, rc; if (sshbind == NULL) { @@ -432,13 +432,6 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd){ return SSH_ERROR; } - /* Apply global bind configurations, if it hasn't been applied before */ - rc = ssh_bind_options_parse_config(sshbind, NULL); - if (rc != 0) { - ssh_set_error(sshbind, SSH_FATAL,"Could not parse global config"); - return SSH_ERROR; - } - session->server = 1; /* Copy options from bind to session */ @@ -485,8 +478,25 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd){ } session->common.log_verbosity = sshbind->common.log_verbosity; - if(sshbind->banner != NULL) - session->opts.custombanner = strdup(sshbind->banner); + + if (sshbind->banner != NULL) { + session->opts.custombanner = strdup(sshbind->banner); + if (session->opts.custombanner == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + + if (sshbind->moduli_file != NULL) { + session->opts.moduli_file = strdup(sshbind->moduli_file); + if (session->opts.moduli_file == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + + session->opts.rsa_min_size = sshbind->rsa_min_size; + ssh_socket_free(session->socket); session->socket = ssh_socket_new(session); if (session->socket == NULL) { @@ -495,7 +505,12 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd){ return SSH_ERROR; } ssh_socket_set_fd(session->socket, fd); - ssh_socket_get_poll_handle(session->socket); + handle = ssh_socket_get_poll_handle(session->socket); + if (handle == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + ssh_socket_set_connected(session->socket, handle); /* We must try to import any keys that could be imported in case * we are not using ssh_bind_listen (which is the other place @@ -503,7 +518,6 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd){ * only using ssh_bind_accept_fd to manage sockets ourselves. */ if (sshbind->rsa == NULL && - sshbind->dsa == NULL && sshbind->ecdsa == NULL && sshbind->ed25519 == NULL) { rc = ssh_bind_import_keys(sshbind); @@ -521,15 +535,6 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd){ } } #endif -#ifdef HAVE_DSA - if (sshbind->dsa) { - session->srv.dsa_key = ssh_key_dup(sshbind->dsa); - if (session->srv.dsa_key == NULL) { - ssh_set_error_oom(sshbind); - return SSH_ERROR; - } - } -#endif if (sshbind->rsa) { session->srv.rsa_key = ssh_key_dup(sshbind->rsa); if (session->srv.rsa_key == NULL) { @@ -568,9 +573,16 @@ int ssh_bind_accept(ssh_bind sshbind, ssh_session session) fd = accept(sshbind->bindfd, NULL, NULL); if (fd == SSH_INVALID_SOCKET) { - ssh_set_error(sshbind, SSH_FATAL, - "Accepting a new connection: %s", - strerror(errno)); + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + if (errno == EINTR) { + ssh_set_error(sshbind, SSH_EINTR, + "Accepting a new connection (child signal error): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } else { + ssh_set_error(sshbind, SSH_FATAL, + "Accepting a new connection: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } return SSH_ERROR; } rc = ssh_bind_accept_fd(sshbind, session, fd); diff --git a/src/bind_config.c b/src/bind_config.c index 14b84db0..a4c7a8d7 100644 --- a/src/bind_config.c +++ b/src/bind_config.c @@ -40,7 +40,9 @@ #include "libssh/server.h" #include "libssh/options.h" +#ifndef MAX_LINE_SIZE #define MAX_LINE_SIZE 1024 +#endif /* Flags used for the parser state */ #define PARSING 1 @@ -187,18 +189,29 @@ ssh_bind_config_parse_line(ssh_bind bind, const char *line, unsigned int count, uint32_t *parser_flags, - uint8_t *seen); - -static void local_parse_file(ssh_bind bind, - const char *filename, - uint32_t *parser_flags, - uint8_t *seen) + uint8_t *seen, + unsigned int depth); + +#define LIBSSH_BIND_CONF_MAX_DEPTH 16 +static void +local_parse_file(ssh_bind bind, + const char *filename, + uint32_t *parser_flags, + uint8_t *seen, + unsigned int depth) { FILE *f; char line[MAX_LINE_SIZE] = {0}; unsigned int count = 0; int rv; + if (depth > LIBSSH_BIND_CONF_MAX_DEPTH) { + ssh_set_error(bind, SSH_FATAL, + "ERROR - Too many levels of configuration includes " + "when processing file '%s'", filename); + return; + } + f = fopen(filename, "r"); if (f == NULL) { SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", @@ -211,7 +224,7 @@ static void local_parse_file(ssh_bind bind, while (fgets(line, sizeof(line), f)) { count++; - rv = ssh_bind_config_parse_line(bind, line, count, parser_flags, seen); + rv = ssh_bind_config_parse_line(bind, line, count, parser_flags, seen, depth); if (rv < 0) { fclose(f); return; @@ -226,7 +239,8 @@ static void local_parse_file(ssh_bind bind, static void local_parse_glob(ssh_bind bind, const char *fileglob, uint32_t *parser_flags, - uint8_t *seen) + uint8_t *seen, + unsigned int depth) { glob_t globbuf = { .gl_flags = 0, @@ -246,7 +260,7 @@ static void local_parse_glob(ssh_bind bind, } for (i = 0; i < globbuf.gl_pathc; i++) { - local_parse_file(bind, globbuf.gl_pathv[i], parser_flags, seen); + local_parse_file(bind, globbuf.gl_pathv[i], parser_flags, seen, depth); } globfree(&globbuf); @@ -272,7 +286,8 @@ ssh_bind_config_parse_line(ssh_bind bind, const char *line, unsigned int count, uint32_t *parser_flags, - uint8_t *seen) + uint8_t *seen, + unsigned int depth) { enum ssh_bind_config_opcode_e opcode; const char *p = NULL; @@ -286,7 +301,12 @@ ssh_bind_config_parse_line(ssh_bind bind, return -1; } - if ((line == NULL) || (parser_flags == NULL)) { + /* Ignore empty lines */ + if (line == NULL || *line == '\0') { + return 0; + } + + if (parser_flags == NULL) { ssh_set_error_invalid(bind); return -1; } @@ -331,9 +351,9 @@ ssh_bind_config_parse_line(ssh_bind bind, p = ssh_config_get_str_tok(&s, NULL); if (p && (*parser_flags & PARSING)) { #if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) - local_parse_glob(bind, p, parser_flags, seen); + local_parse_glob(bind, p, parser_flags, seen, depth + 1); #else - local_parse_file(bind, p, parser_flags, seen); + local_parse_file(bind, p, parser_flags, seen, depth + 1); #endif /* HAVE_GLOB */ } break; @@ -343,7 +363,7 @@ ssh_bind_config_parse_line(ssh_bind bind, if (p && (*parser_flags & PARSING)) { rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set Hostkey value '%s'", count, p); } @@ -354,7 +374,7 @@ ssh_bind_config_parse_line(ssh_bind bind, if (p && (*parser_flags & PARSING)) { rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDADDR, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set ListenAddress value '%s'", count, p); } @@ -365,7 +385,7 @@ ssh_bind_config_parse_line(ssh_bind bind, if (p && (*parser_flags & PARSING)) { rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set Port value '%s'", count, p); } @@ -376,7 +396,7 @@ ssh_bind_config_parse_line(ssh_bind bind, if (p && (*parser_flags & PARSING)) { rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_C_S, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set C->S Ciphers value '%s'", count, p); break; @@ -384,7 +404,7 @@ ssh_bind_config_parse_line(ssh_bind bind, rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_S_C, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set S->C Ciphers value '%s'", count, p); } @@ -395,7 +415,7 @@ ssh_bind_config_parse_line(ssh_bind bind, if (p && (*parser_flags & PARSING)) { rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_C_S, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set C->S MAC value '%s'", count, p); break; @@ -403,7 +423,7 @@ ssh_bind_config_parse_line(ssh_bind bind, rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_S_C, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set S->C MAC value '%s'", count, p); } @@ -417,10 +437,10 @@ ssh_bind_config_parse_line(ssh_bind bind, if (strcasecmp(p, "quiet") == 0) { value = SSH_LOG_NONE; } else if (strcasecmp(p, "fatal") == 0 || - strcasecmp(p, "error")== 0 || - strcasecmp(p, "info") == 0) { + strcasecmp(p, "error")== 0) { value = SSH_LOG_WARN; - } else if (strcasecmp(p, "verbose") == 0) { + } else if (strcasecmp(p, "verbose") == 0 || + strcasecmp(p, "info") == 0) { value = SSH_LOG_INFO; } else if (strcasecmp(p, "DEBUG") == 0 || strcasecmp(p, "DEBUG1") == 0) { @@ -433,7 +453,7 @@ ssh_bind_config_parse_line(ssh_bind bind, rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY, &value); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set LogLevel value '%s'", count, p); } @@ -445,7 +465,7 @@ ssh_bind_config_parse_line(ssh_bind bind, if (p && (*parser_flags & PARSING)) { rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_KEY_EXCHANGE, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set KexAlgorithms value '%s'", count, p); } @@ -520,13 +540,13 @@ ssh_bind_config_parse_line(ssh_bind bind, /* Skip one argument */ p = ssh_config_get_str_tok(&s, NULL); if (p == NULL || p[0] == '\0') { - SSH_LOG(SSH_LOG_WARN, "line %d: Match keyword " + SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword " "'%s' requires argument\n", count, p2); SAFE_FREE(x); return -1; } args++; - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_DEBUG, "line %d: Unsupported Match keyword '%s', ignoring\n", count, p2); @@ -556,7 +576,7 @@ ssh_bind_config_parse_line(ssh_bind bind, rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set PubKeyAcceptedKeyTypes value '%s'", count, p); } @@ -568,26 +588,26 @@ ssh_bind_config_parse_line(ssh_bind bind, rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set HostkeyAlgorithms value '%s'", count, p); } } break; case BIND_CFG_NOT_ALLOWED_IN_MATCH: - SSH_LOG(SSH_LOG_WARN, "Option not allowed in Match block: %s, line: %d", + SSH_LOG(SSH_LOG_DEBUG, "Option not allowed in Match block: %s, line: %d", keyword, count); break; case BIND_CFG_UNKNOWN: - SSH_LOG(SSH_LOG_WARN, "Unknown option: %s, line: %d", + SSH_LOG(SSH_LOG_TRACE, "Unknown option: %s, line: %d", keyword, count); break; case BIND_CFG_UNSUPPORTED: - SSH_LOG(SSH_LOG_WARN, "Unsupported option: %s, line: %d", + SSH_LOG(SSH_LOG_TRACE, "Unsupported option: %s, line: %d", keyword, count); break; case BIND_CFG_NA: - SSH_LOG(SSH_LOG_WARN, "Option not applicable: %s, line: %d", + SSH_LOG(SSH_LOG_TRACE, "Option not applicable: %s, line: %d", keyword, count); break; default: @@ -626,7 +646,7 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename) parser_flags = PARSING; while (fgets(line, sizeof(line), f)) { count++; - rv = ssh_bind_config_parse_line(bind, line, count, &parser_flags, seen); + rv = ssh_bind_config_parse_line(bind, line, count, &parser_flags, seen, 0); if (rv) { fclose(f); return -1; @@ -636,3 +656,64 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename) fclose(f); return 0; } + +/* @brief Parse configuration string and set the options to the given bind session + * + * @params[in] bind The ssh bind session + * @params[in] input Null terminated string containing the configuration + * + * @returns SSH_OK on successful parsing the configuration string, + * SSH_ERROR on error + */ +int ssh_bind_config_parse_string(ssh_bind bind, const char *input) +{ + char line[MAX_LINE_SIZE] = {0}; + const char *c = input, *line_start = input; + unsigned int line_num = 0, line_len; + uint32_t parser_flags; + int rv; + + /* This local table is used during the parsing of the current file (and + * files included recursively in this file) to prevent an option to be + * redefined, i.e. the first value set is kept. But this DO NOT prevent the + * option to be redefined later by another file. */ + uint8_t seen[BIND_CFG_MAX] = {0}; + + SSH_LOG(SSH_LOG_DEBUG, "Reading bind configuration data from string:"); + SSH_LOG(SSH_LOG_DEBUG, "START\n%s\nEND", input); + + parser_flags = PARSING; + while (1) { + line_num++; + line_start = c; + c = strchr(line_start, '\n'); + if (c == NULL) { + /* if there is no newline at the end of the string */ + c = strchr(line_start, '\0'); + } + if (c == NULL) { + /* should not happen, would mean a string without trailing '\0' */ + SSH_LOG(SSH_LOG_WARN, "No trailing '\\0' in config string"); + return SSH_ERROR; + } + line_len = c - line_start; + if (line_len > MAX_LINE_SIZE - 1) { + SSH_LOG(SSH_LOG_WARN, "Line %u too long: %u characters", + line_num, line_len); + return SSH_ERROR; + } + memcpy(line, line_start, line_len); + line[line_len] = '\0'; + SSH_LOG(SSH_LOG_DEBUG, "Line %u: %s", line_num, line); + rv = ssh_bind_config_parse_line(bind, line, line_num, &parser_flags, seen, 0); + if (rv < 0) { + return SSH_ERROR; + } + if (*c == '\0') { + break; + } + c++; + } + + return SSH_OK; +} diff --git a/src/buffer.c b/src/buffer.c index ce12f491..299eac54 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -26,6 +26,7 @@ #include <limits.h> #include <stdarg.h> #include <stdbool.h> +#include <stdio.h> #ifndef _WIN32 #include <netinet/in.h> @@ -46,9 +47,9 @@ */ struct ssh_buffer_struct { bool secure; - size_t used; - size_t allocated; - size_t pos; + uint32_t used; + uint32_t allocated; + uint32_t pos; uint8_t *data; }; @@ -56,7 +57,7 @@ struct ssh_buffer_struct { #define BUFFER_SIZE_MAX 0x10000000 /** - * @defgroup libssh_buffer The SSH buffer functions. + * @defgroup libssh_buffer The SSH buffer functions * @ingroup libssh * * Functions to handle SSH buffers. @@ -83,21 +84,21 @@ static void buffer_verify(ssh_buffer buf) if (buf->used > buf->allocated) { fprintf(stderr, - "BUFFER ERROR: allocated %zu, used %zu\n", + "BUFFER ERROR: allocated %u, used %u\n", buf->allocated, buf->used); do_abort = true; } if (buf->pos > buf->used) { fprintf(stderr, - "BUFFER ERROR: position %zu, used %zu\n", + "BUFFER ERROR: position %u, used %u\n", buf->pos, buf->used); do_abort = true; } if (buf->pos > buf->allocated) { fprintf(stderr, - "BUFFER ERROR: position %zu, allocated %zu\n", + "BUFFER ERROR: position %u, allocated %u\n", buf->pos, buf->allocated); do_abort = true; @@ -129,7 +130,7 @@ struct ssh_buffer_struct *ssh_buffer_new(void) /* * Always preallocate 64 bytes. * - * -1 for ralloc_buffer magic. + * -1 for realloc_buffer magic. */ rc = ssh_buffer_allocate_size(buf, 64 - 1); if (rc != 0) { @@ -178,9 +179,9 @@ void ssh_buffer_set_secure(ssh_buffer buffer) buffer->secure = true; } -static int realloc_buffer(struct ssh_buffer_struct *buffer, size_t needed) +static int realloc_buffer(struct ssh_buffer_struct *buffer, uint32_t needed) { - size_t smallest = 1; + uint32_t smallest = 1; uint8_t *new = NULL; buffer_verify(buffer); @@ -693,7 +694,7 @@ uint32_t ssh_buffer_get_data(struct ssh_buffer_struct *buffer, void *data, uint3 /** * @internal * - * @brief Get a 8 bits unsigned int out of the buffer and adjusts the read + * @brief Get a 8 bits unsigned int out of the buffer and adjust the read * pointer. * * @param[in] buffer The buffer to read. @@ -702,7 +703,7 @@ uint32_t ssh_buffer_get_data(struct ssh_buffer_struct *buffer, void *data, uint3 * * @returns 0 if there is not enough data in buffer, 1 otherwise. */ -int ssh_buffer_get_u8(struct ssh_buffer_struct *buffer, uint8_t *data){ +uint32_t ssh_buffer_get_u8(struct ssh_buffer_struct *buffer, uint8_t *data){ return ssh_buffer_get_data(buffer,data,sizeof(uint8_t)); } @@ -717,7 +718,7 @@ int ssh_buffer_get_u8(struct ssh_buffer_struct *buffer, uint8_t *data){ * * @returns 0 if there is not enough data in buffer, 4 otherwise. */ -int ssh_buffer_get_u32(struct ssh_buffer_struct *buffer, uint32_t *data){ +uint32_t ssh_buffer_get_u32(struct ssh_buffer_struct *buffer, uint32_t *data){ return ssh_buffer_get_data(buffer,data,sizeof(uint32_t)); } /** @@ -732,12 +733,12 @@ int ssh_buffer_get_u32(struct ssh_buffer_struct *buffer, uint32_t *data){ * * @returns 0 if there is not enough data in buffer, 8 otherwise. */ -int ssh_buffer_get_u64(struct ssh_buffer_struct *buffer, uint64_t *data){ +uint32_t ssh_buffer_get_u64(struct ssh_buffer_struct *buffer, uint64_t *data){ return ssh_buffer_get_data(buffer,data,sizeof(uint64_t)); } /** - * @brief Valdiates that the given length can be obtained from the buffer. + * @brief Validates that the given length can be obtained from the buffer. * * @param[in] buffer The buffer to read from. * @@ -747,7 +748,8 @@ int ssh_buffer_get_u64(struct ssh_buffer_struct *buffer, uint64_t *data){ */ int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len) { - if (buffer->pos + len < len || buffer->pos + len > buffer->used) { + if (buffer == NULL || buffer->pos + len < len || + buffer->pos + len > buffer->used) { return SSH_ERROR; } @@ -757,7 +759,7 @@ int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len) /** * @internal * - * @brief Get a SSH String out of the buffer and adjusts the read pointer. + * @brief Get an SSH String out of the buffer and adjust the read pointer. * * @param[in] buffer The buffer to read. * @@ -868,7 +870,7 @@ static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer, va_arg(ap, bignum); /* * Use a fixed size for a bignum - * (they should normaly be around 32) + * (they should normally be around 32) */ needed_size += 64; break; @@ -878,7 +880,7 @@ static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer, cstring = NULL; break; default: - SSH_LOG(SSH_LOG_WARN, "Invalid buffer format %c", *p); + SSH_LOG(SSH_LOG_TRACE, "Invalid buffer format %c", *p); rc = SSH_ERROR; } if (rc != SSH_OK){ @@ -900,7 +902,7 @@ static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer, } } - rc = ssh_buffer_allocate_size(buffer, needed_size); + rc = ssh_buffer_allocate_size(buffer, (uint32_t)needed_size); if (rc != 0) { return SSH_ERROR; } @@ -1007,7 +1009,7 @@ int ssh_buffer_pack_va(struct ssh_buffer_struct *buffer, cstring = NULL; break; default: - SSH_LOG(SSH_LOG_WARN, "Invalid buffer format %c", *p); + SSH_LOG(SSH_LOG_TRACE, "Invalid buffer format %c", *p); rc = SSH_ERROR; } if (rc != SSH_OK){ @@ -1102,7 +1104,8 @@ int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, bignum *bignum; void **data; } o; - size_t len, rlen, max_len; + size_t len; + uint32_t rlen, max_len; ssh_string tmp_string = NULL; va_list ap_copy; size_t count; @@ -1238,7 +1241,7 @@ int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, rc = SSH_OK; break; default: - SSH_LOG(SSH_LOG_WARN, "Invalid buffer format %c", *p); + SSH_LOG(SSH_LOG_TRACE, "Invalid buffer format %c", *p); } if (rc != SSH_OK) { break; diff --git a/src/chachapoly.c b/src/chachapoly.c index c90a1e97..2cd23854 100644 --- a/src/chachapoly.c +++ b/src/chachapoly.c @@ -65,7 +65,7 @@ static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, /** * @internal * - * @brief encrypts an outgoing packet with chacha20 and authenticate it + * @brief encrypts an outgoing packet with chacha20 and authenticates it * with poly1305. */ static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, @@ -164,7 +164,7 @@ static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, ssh_log_hexdump("received tag", mac, POLY1305_TAGLEN); #endif - cmp = memcmp(tag, mac, POLY1305_TAGLEN); + cmp = secure_memcmp(tag, mac, POLY1305_TAGLEN); if(cmp != 0) { /* mac error */ SSH_LOG(SSH_LOG_PACKET,"poly1305 verify error"); diff --git a/src/channels.c b/src/channels.c index 607bd568..9e613715 100644 --- a/src/channels.c +++ b/src/channels.c @@ -29,6 +29,10 @@ #include <errno.h> #include <time.h> #include <stdbool.h> +#include <string.h> +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif /* HAVE_SYS_TIME_H */ #ifndef _WIN32 #include <netinet/in.h> @@ -48,16 +52,19 @@ #include "libssh/server.h" #endif -#define WINDOWBASE 1280000 -#define WINDOWLIMIT (WINDOWBASE/2) - /* * All implementations MUST be able to process packets with an * uncompressed payload length of 32768 bytes or less and a total packet * size of 35000 bytes or less. */ #define CHANNEL_MAX_PACKET 32768 -#define CHANNEL_INITIAL_WINDOW 64000 + +/* + * WINDOW_DEFAULT matches the default OpenSSH session window size. + * This controls how much data the peer can send before needing to receive + * a round-trip SSH2_MSG_CHANNEL_WINDOW_ADJUST message that increases the window. + */ +#define WINDOW_DEFAULT (64*CHANNEL_MAX_PACKET) /** * @defgroup libssh_channel The SSH channel functions @@ -76,6 +83,9 @@ static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet); * @param[in] session The ssh session to use. * * @return A pointer to a newly allocated channel, NULL on error. + * The channel needs to be freed with ssh_channel_free(). + * + * @see ssh_channel_free() */ ssh_channel ssh_channel_new(ssh_session session) { @@ -117,6 +127,13 @@ ssh_channel ssh_channel_new(ssh_session session) if (session->channels == NULL) { session->channels = ssh_list_new(); + if (session->channels == NULL) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(channel->stdout_buffer); + SSH_BUFFER_FREE(channel->stderr_buffer); + SAFE_FREE(channel); + return NULL; + } } ssh_list_prepend(session->channels, channel); @@ -137,8 +154,9 @@ ssh_channel ssh_channel_new(ssh_session session) * * @return The new channel identifier. */ -uint32_t ssh_channel_new_id(ssh_session session) { - return ++(session->maxchannel); +uint32_t ssh_channel_new_id(ssh_session session) +{ + return ++(session->maxchannel); } /** @@ -163,7 +181,7 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf){ channel=ssh_channel_from_local(session,channelid); if(channel==NULL){ ssh_set_error(session, SSH_FATAL, - "Unknown channel id %"PRIu32, + "Unknown channel id %" PRIu32, (uint32_t) channelid); /* TODO: Set error marking in channel object */ @@ -177,8 +195,8 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf){ if (rc != SSH_OK) goto error; - SSH_LOG(SSH_LOG_PROTOCOL, - "Received a CHANNEL_OPEN_CONFIRMATION for channel %d:%d", + SSH_LOG(SSH_LOG_DEBUG, + "Received a CHANNEL_OPEN_CONFIRMATION for channel %" PRIu32 ":%" PRIu32, channel->local_channel, channel->remote_channel); @@ -190,13 +208,21 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf){ goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, - "Remote window : %"PRIu32", maxpacket : %"PRIu32, - (uint32_t) channel->remote_window, - (uint32_t) channel->remote_maxpacket); + SSH_LOG(SSH_LOG_DEBUG, + "Remote window : %" PRIu32 ", maxpacket : %" PRIu32, + channel->remote_window, + channel->remote_maxpacket); channel->state = SSH_CHANNEL_STATE_OPEN; channel->flags &= ~SSH_CHANNEL_FLAG_NOT_BOUND; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_open_response_function, + channel->session, + channel, + true /* is_success */); + return SSH_PACKET_USED; error: @@ -235,16 +261,25 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail){ "SSH2_MSG_CHANNEL_OPEN_FAILURE received in incorrect channel " "state %d", channel->state); + SAFE_FREE(error); goto error; } ssh_set_error(session, SSH_REQUEST_DENIED, - "Channel opening failure: channel %u error (%"PRIu32") %s", + "Channel opening failure: channel %" PRIu32 " error (%" PRIu32 ") %s", channel->local_channel, - (uint32_t) code, + code, error); SAFE_FREE(error); channel->state=SSH_CHANNEL_STATE_OPEN_DENIED; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_open_response_function, + channel->session, + channel, + false /* is_success */); + return SSH_PACKET_USED; error: @@ -252,7 +287,8 @@ error: return SSH_PACKET_USED; } -static int ssh_channel_open_termination(void *c){ +static int ssh_channel_open_termination(void *c) +{ ssh_channel channel = (ssh_channel) c; if (channel->state != SSH_CHANNEL_STATE_OPENING || channel->session->session_state == SSH_SESSION_STATE_ERROR) @@ -310,8 +346,8 @@ channel_open(ssh_channel channel, channel->local_maxpacket = maxpacket; channel->local_window = window; - SSH_LOG(SSH_LOG_PROTOCOL, - "Creating a channel %d with %d window and %d max packet", + SSH_LOG(SSH_LOG_DEBUG, + "Creating a channel %" PRIu32 " with %" PRIu32 " window and %" PRIu32 " max packet", channel->local_channel, window, maxpacket); rc = ssh_buffer_pack(session->out_buffer, @@ -339,7 +375,7 @@ channel_open(ssh_channel channel, } SSH_LOG(SSH_LOG_PACKET, - "Sent a SSH_MSG_CHANNEL_OPEN type %s for channel %d", + "Sent a SSH_MSG_CHANNEL_OPEN type %s for channel %" PRIu32, type, channel->local_channel); pending: @@ -359,7 +395,7 @@ end: if (channel->state == SSH_CHANNEL_STATE_OPEN) { err = SSH_OK; } else if (err != SSH_AGAIN) { - /* Messages were handled correctly, but he channel state is invalid */ + /* Messages were handled correctly, but the channel state is invalid */ err = SSH_ERROR; } @@ -386,35 +422,46 @@ ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id) { /** * @internal - * @brief grows the local window and send a packet to the other party + * @brief grows the local window and sends a packet to the other party * @param session SSH session * @param channel SSH channel - * @param minimumsize The minimum acceptable size for the new window. * @return SSH_OK if successful; SSH_ERROR otherwise. */ static int grow_window(ssh_session session, - ssh_channel channel, - uint32_t minimumsize) + ssh_channel channel) { - uint32_t new_window = minimumsize > WINDOWBASE ? minimumsize : WINDOWBASE; + uint32_t used; + uint32_t increment; int rc; - if(new_window <= channel->local_window){ - SSH_LOG(SSH_LOG_PROTOCOL, - "growing window (channel %d:%d) to %d bytes : not needed (%d bytes)", - channel->local_channel, channel->remote_channel, new_window, + /* Calculate the increment taking into account what the peer may still send + * (local_window) and what we've already buffered (stdout_buffer and + * stderr_buffer). + */ + used = channel->local_window; + if (channel->stdout_buffer != NULL) { + used += ssh_buffer_get_len(channel->stdout_buffer); + } + if (channel->stderr_buffer != NULL) { + used += ssh_buffer_get_len(channel->stderr_buffer); + } + /* Avoid a negative increment in case the peer sent more than the window allowed */ + increment = WINDOW_DEFAULT > used ? WINDOW_DEFAULT - used : 0; + /* Don't grow until we can request at least half a window */ + if (increment < (WINDOW_DEFAULT / 2)) { + SSH_LOG(SSH_LOG_DEBUG, + "growing window (channel %" PRIu32 ":%" PRIu32 ") to %" PRIu32 " bytes : not needed (%" PRIu32 " bytes)", + channel->local_channel, channel->remote_channel, WINDOW_DEFAULT, channel->local_window); return SSH_OK; } - /* WINDOW_ADJUST packet needs a relative increment rather than an absolute - * value, so we give here the missing bytes needed to reach new_window - */ + rc = ssh_buffer_pack(session->out_buffer, "bdd", SSH2_MSG_CHANNEL_WINDOW_ADJUST, channel->remote_channel, - new_window - channel->local_window); + increment); if (rc != SSH_OK) { ssh_set_error_oom(session); goto error; @@ -424,13 +471,13 @@ static int grow_window(ssh_session session, goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, - "growing window (channel %d:%d) to %d bytes", + SSH_LOG(SSH_LOG_DEBUG, + "growing window (channel %" PRIu32 ":%" PRIu32 ") by %" PRIu32 " bytes", channel->local_channel, channel->remote_channel, - new_window); + increment); - channel->local_window = new_window; + channel->local_window += increment; return SSH_OK; error: @@ -452,7 +499,8 @@ error: * @return The related ssh_channel, or NULL if the channel is * unknown or the packet is invalid. */ -static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet) { +static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet) +{ ssh_channel channel; uint32_t chan; int rc; @@ -467,7 +515,7 @@ static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet) { channel = ssh_channel_from_local(session, chan); if (channel == NULL) { ssh_set_error(session, SSH_FATAL, - "Server specified invalid channel %"PRIu32, + "Server specified invalid channel %" PRIu32, (uint32_t) chan); } @@ -478,6 +526,8 @@ SSH_PACKET_CALLBACK(channel_rcv_change_window) { ssh_channel channel; uint32_t bytes; int rc; + bool was_empty; + (void)user; (void)type; @@ -494,123 +544,153 @@ SSH_PACKET_CALLBACK(channel_rcv_change_window) { return SSH_PACKET_USED; } - SSH_LOG(SSH_LOG_PROTOCOL, - "Adding %d bytes to channel (%d:%d) (from %d bytes)", + SSH_LOG(SSH_LOG_DEBUG, + "Adding %" PRIu32 " bytes to channel (%" PRIu32 ":%" PRIu32 ") (from %" PRIu32 " bytes)", bytes, channel->local_channel, channel->remote_channel, channel->remote_window); + was_empty = channel->remote_window == 0; + channel->remote_window += bytes; + /* Writing to the channel is non-blocking until the receive window is empty. + When the receive window becomes non-zero again, call channel_write_wontblock_function. */ + if (was_empty && bytes > 0) { + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_write_wontblock_function, + session, + channel, + channel->remote_window); + } + return SSH_PACKET_USED; } /* is_stderr is set to 1 if the data are extended, ie stderr */ -SSH_PACKET_CALLBACK(channel_rcv_data){ - ssh_channel channel; - ssh_string str; - ssh_buffer buf; - size_t len; - int is_stderr; - int rest; - (void)user; +SSH_PACKET_CALLBACK(channel_rcv_data) +{ + ssh_channel channel = NULL; + ssh_string str = NULL; + ssh_buffer buf = NULL; + void *data = NULL; + uint32_t len; + int extended, is_stderr = 0; + int rest; - if(type==SSH2_MSG_CHANNEL_DATA) - is_stderr=0; - else - is_stderr=1; + (void)user; - channel = channel_from_msg(session,packet); - if (channel == NULL) { - SSH_LOG(SSH_LOG_FUNCTIONS, - "%s", ssh_get_error(session)); + if (type == SSH2_MSG_CHANNEL_DATA) { + extended = 0; + } else { /* SSH_MSG_CHANNEL_EXTENDED_DATA */ + extended = 1; + } - return SSH_PACKET_USED; - } + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); - if (is_stderr) { - uint32_t ignore; - /* uint32 data type code. we can ignore it */ - ssh_buffer_get_u32(packet, &ignore); - } + return SSH_PACKET_USED; + } - str = ssh_buffer_get_ssh_string(packet); - if (str == NULL) { - SSH_LOG(SSH_LOG_PACKET, "Invalid data packet!"); + if (extended) { + uint32_t data_type_code, rc; + rc = ssh_buffer_get_u32(packet, &data_type_code); + if (rc != sizeof(uint32_t)) { + SSH_LOG(SSH_LOG_PACKET, + "Failed to read data type code: rc = %" PRIu32, rc); - return SSH_PACKET_USED; - } - len = ssh_string_len(str); + return SSH_PACKET_USED; + } + is_stderr = 1; + data_type_code = ntohl(data_type_code); + if (data_type_code != SSH2_EXTENDED_DATA_STDERR) { + SSH_LOG(SSH_LOG_PACKET, "Invalid data type code %" PRIu32 "!", + data_type_code); + } + } - SSH_LOG(SSH_LOG_PACKET, - "Channel receiving %zu bytes data in %d (local win=%d remote win=%d)", - len, - is_stderr, - channel->local_window, - channel->remote_window); + str = ssh_buffer_get_ssh_string(packet); + if (str == NULL) { + SSH_LOG(SSH_LOG_PACKET, "Invalid data packet!"); - /* What shall we do in this case? Let's accept it anyway */ - if (len > channel->local_window) { - SSH_LOG(SSH_LOG_RARE, - "Data packet too big for our window(%zu vs %d)", - len, - channel->local_window); - } + return SSH_PACKET_USED; + } + len = ssh_string_len(str); - if (channel_default_bufferize(channel, ssh_string_data(str), len, - is_stderr) < 0) { - SSH_STRING_FREE(str); + SSH_LOG(SSH_LOG_PACKET, + "Channel receiving %" PRIu32 " bytes data%s (local win=%" PRIu32 + " remote win=%" PRIu32 ")", + len, + is_stderr ? " in stderr" : "", + channel->local_window, + channel->remote_window); - return SSH_PACKET_USED; - } + if (len > channel->local_window) { + SSH_LOG(SSH_LOG_RARE, + "Data packet too big for our window(%" PRIu32 " vs %" PRIu32 ")", + len, + channel->local_window); + + SSH_STRING_FREE(str); + + ssh_set_error(session, SSH_FATAL, "Window exceeded"); + + return SSH_PACKET_USED; + } + + data = ssh_string_data(str); + if (channel_default_bufferize(channel, data, len, is_stderr) < 0) { + SSH_STRING_FREE(str); + + return SSH_PACKET_USED; + } - if (len <= channel->local_window) { channel->local_window -= len; - } else { - channel->local_window = 0; /* buggy remote */ - } - SSH_LOG(SSH_LOG_PACKET, - "Channel windows are now (local win=%d remote win=%d)", - channel->local_window, - channel->remote_window); + SSH_LOG(SSH_LOG_PACKET, + "Channel windows are now (local win=%" PRIu32 " remote win=%" PRIu32 ")", + channel->local_window, + channel->remote_window); - SSH_STRING_FREE(str); + SSH_STRING_FREE(str); - if (is_stderr) { - buf = channel->stderr_buffer; - } else { - buf = channel->stdout_buffer; - } + if (is_stderr) { + buf = channel->stderr_buffer; + } else { + buf = channel->stdout_buffer; + } - ssh_callbacks_iterate(channel->callbacks, - ssh_channel_callbacks, - channel_data_function) { - if (ssh_buffer_get(buf) == NULL) { - break; - } - rest = ssh_callbacks_iterate_exec(channel_data_function, - channel->session, - channel, - ssh_buffer_get(buf), - ssh_buffer_get_len(buf), - is_stderr); - if (rest > 0) { - if (channel->counter != NULL) { - channel->counter->in_bytes += rest; - } - ssh_buffer_pass_bytes(buf, rest); - } - } - ssh_callbacks_iterate_end(); + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_data_function) { + if (ssh_buffer_get(buf) == NULL) { + break; + } + rest = ssh_callbacks_iterate_exec(channel_data_function, + channel->session, + channel, + ssh_buffer_get(buf), + ssh_buffer_get_len(buf), + is_stderr); + if (rest > 0) { + int rc; + if (channel->counter != NULL) { + channel->counter->in_bytes += rest; + } + ssh_buffer_pass_bytes(buf, rest); - if (channel->local_window + ssh_buffer_get_len(buf) < WINDOWLIMIT) { - if (grow_window(session, channel, 0) < 0) { - return -1; - } - } - return SSH_PACKET_USED; + rc = grow_window(session, channel); + if (rc == SSH_ERROR) { + return -1; + } + } + } + ssh_callbacks_iterate_end(); + + return SSH_PACKET_USED; } SSH_PACKET_CALLBACK(channel_rcv_eof) { @@ -626,7 +706,7 @@ SSH_PACKET_CALLBACK(channel_rcv_eof) { } SSH_LOG(SSH_LOG_PACKET, - "Received eof on channel (%d:%d)", + "Received eof on channel (%" PRIu32 ":%" PRIu32 ")", channel->local_channel, channel->remote_channel); /* channel->remote_window = 0; */ @@ -641,40 +721,55 @@ SSH_PACKET_CALLBACK(channel_rcv_eof) { return SSH_PACKET_USED; } +static bool ssh_channel_has_unread_data(ssh_channel channel) +{ + if (channel == NULL) { + return false; + } + + if ((channel->stdout_buffer && + ssh_buffer_get_len(channel->stdout_buffer) > 0) || + (channel->stderr_buffer && + ssh_buffer_get_len(channel->stderr_buffer) > 0)) + { + return true; + } + + return false; +} + SSH_PACKET_CALLBACK(channel_rcv_close) { - ssh_channel channel; - (void)user; - (void)type; + ssh_channel channel; + (void)user; + (void)type; - channel = channel_from_msg(session,packet); - if (channel == NULL) { - SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + channel = channel_from_msg(session,packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); - return SSH_PACKET_USED; - } + return SSH_PACKET_USED; + } - SSH_LOG(SSH_LOG_PACKET, - "Received close on channel (%d:%d)", - channel->local_channel, - channel->remote_channel); - - if ((channel->stdout_buffer && - ssh_buffer_get_len(channel->stdout_buffer) > 0) || - (channel->stderr_buffer && - ssh_buffer_get_len(channel->stderr_buffer) > 0)) { - channel->delayed_close = 1; - } else { - channel->state = SSH_CHANNEL_STATE_CLOSED; - } - if (channel->remote_eof == 0) { - SSH_LOG(SSH_LOG_PACKET, - "Remote host not polite enough to send an eof before close"); - } - channel->remote_eof = 1; - /* - * The remote eof doesn't break things if there was still data into read - * buffer because the eof is ignored until the buffer is empty. - */ + SSH_LOG(SSH_LOG_PACKET, + "Received close on channel (%" PRIu32 ":%" PRIu32 ")", + channel->local_channel, + channel->remote_channel); + + if (!ssh_channel_has_unread_data(channel)) { + channel->state = SSH_CHANNEL_STATE_CLOSED; + } else { + channel->delayed_close = 1; + } + + if (channel->remote_eof == 0) { + SSH_LOG(SSH_LOG_PACKET, + "Remote host not polite enough to send an eof before close"); + } + /* + * The remote eof doesn't break things if there was still data into read + * buffer because the eof is ignored until the buffer is empty. + */ + channel->remote_eof = 1; ssh_callbacks_execute_list(channel->callbacks, ssh_channel_callbacks, @@ -682,17 +777,17 @@ SSH_PACKET_CALLBACK(channel_rcv_close) { channel->session, channel); - channel->flags |= SSH_CHANNEL_FLAG_CLOSED_REMOTE; - if(channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL) - ssh_channel_do_free(channel); + channel->flags |= SSH_CHANNEL_FLAG_CLOSED_REMOTE; + if(channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL) + ssh_channel_do_free(channel); - return SSH_PACKET_USED; + return SSH_PACKET_USED; } SSH_PACKET_CALLBACK(channel_rcv_request) { ssh_channel channel; char *request=NULL; - uint8_t status; + uint8_t want_reply; int rc; (void)user; (void)type; @@ -705,7 +800,7 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { rc = ssh_buffer_unpack(packet, "sb", &request, - &status); + &want_reply); if (rc != SSH_OK) { SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); return SSH_PACKET_USED; @@ -798,7 +893,7 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { } if(strcmp(request,"keepalive@openssh.com")==0){ SAFE_FREE(request); - SSH_LOG(SSH_LOG_PROTOCOL,"Responding to Openssh's keepalive"); + SSH_LOG(SSH_LOG_DEBUG,"Responding to Openssh's keepalive"); rc = ssh_buffer_pack(session->out_buffer, "bd", @@ -813,13 +908,34 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { } if (strcmp(request, "auth-agent-req@openssh.com") == 0) { + int status; + SAFE_FREE(request); - SSH_LOG(SSH_LOG_PROTOCOL, "Received an auth-agent-req request"); - ssh_callbacks_execute_list(channel->callbacks, - ssh_channel_callbacks, - channel_auth_agent_req_function, - channel->session, - channel); + SSH_LOG(SSH_LOG_DEBUG, "Received an auth-agent-req request"); + + status = SSH2_MSG_CHANNEL_FAILURE; + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_auth_agent_req_function) { + ssh_callbacks_iterate_exec(channel_auth_agent_req_function, + channel->session, + channel); + /* in lieu of a return value, if the callback exists it's supported */ + status = SSH2_MSG_CHANNEL_SUCCESS; + break; + } + ssh_callbacks_iterate_end(); + + if (want_reply) { + rc = ssh_buffer_pack(session->out_buffer, + "bd", + status, + channel->remote_channel); + if (rc != SSH_OK) { + return SSH_PACKET_USED; + } + ssh_packet_send(session); + } return SSH_PACKET_USED; } @@ -828,11 +944,11 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { * client requests. That means we need to create a ssh message to be passed * to the user code handling ssh messages */ - ssh_message_handle_channel_request(session,channel,packet,request,status); + ssh_message_handle_channel_request(session,channel,packet,request,want_reply); #else - SSH_LOG(SSH_LOG_WARNING, "Unhandled channel request %s", request); + SSH_LOG(SSH_LOG_DEBUG, "Unhandled channel request %s", request); #endif - + SAFE_FREE(request); return SSH_PACKET_USED; @@ -845,7 +961,7 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { * FIXME is the window changed? */ int channel_default_bufferize(ssh_channel channel, - void *data, size_t len, + void *data, uint32_t len, bool is_stderr) { ssh_session session; @@ -862,7 +978,7 @@ int channel_default_bufferize(ssh_channel channel, } SSH_LOG(SSH_LOG_PACKET, - "placing %zu bytes into channel buffer (%s)", + "placing %" PRIu32 " bytes into channel buffer (%s)", len, is_stderr ? "stderr" : "stdout"); if (!is_stderr) { @@ -917,14 +1033,15 @@ int channel_default_bufferize(ssh_channel channel, * @see ssh_channel_request_shell() * @see ssh_channel_request_exec() */ -int ssh_channel_open_session(ssh_channel channel) { - if(channel == NULL) { +int ssh_channel_open_session(ssh_channel channel) +{ + if (channel == NULL) { return SSH_ERROR; } return channel_open(channel, "session", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, NULL); } @@ -944,14 +1061,15 @@ int ssh_channel_open_session(ssh_channel channel) { * * @see ssh_channel_open_forward() */ -int ssh_channel_open_auth_agent(ssh_channel channel){ - if(channel == NULL) { +int ssh_channel_open_auth_agent(ssh_channel channel) +{ + if (channel == NULL) { return SSH_ERROR; } return channel_open(channel, "auth-agent@openssh.com", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, NULL); } @@ -980,16 +1098,17 @@ int ssh_channel_open_auth_agent(ssh_channel channel){ * * @warning This function does not bind the local port and does not automatically * forward the content of a socket to the channel. You still have to - * use channel_read and channel_write for this. + * use ssh_channel_read and ssh_channel_write for this. */ int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, - int remoteport, const char *sourcehost, int localport) { + int remoteport, const char *sourcehost, int localport) +{ ssh_session session; ssh_buffer payload = NULL; ssh_string str = NULL; int rc = SSH_ERROR; - if(channel == NULL) { + if (channel == NULL) { return rc; } @@ -1019,7 +1138,7 @@ int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, rc = channel_open(channel, "direct-tcpip", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, payload); @@ -1051,7 +1170,7 @@ error: * * @warning This function does not bind the local port and does not * automatically forward the content of a socket to the channel. - * You still have to use channel_read and channel_write for this. + * You still have to use ssh_channel_read and ssh_channel_write for this. * @warning Requires support of OpenSSH for UNIX domain socket forwarding. */ int ssh_channel_open_forward_unix(ssh_channel channel, @@ -1102,7 +1221,7 @@ int ssh_channel_open_forward_unix(ssh_channel channel, rc = channel_open(channel, "direct-streamlocal@openssh.com", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, payload); @@ -1156,7 +1275,7 @@ void ssh_channel_free(ssh_channel channel) channel->flags |= SSH_CHANNEL_FLAG_FREED_LOCAL; /* The idea behind the flags is the following : it is well possible - * that a client closes a channel that stills exists on the server side. + * that a client closes a channel that still exists on the server side. * We definitively close the channel when we receive a close message *and* * the user closed it. */ @@ -1249,7 +1368,7 @@ int ssh_channel_send_eof(ssh_channel channel) rc = ssh_packet_send(session); SSH_LOG(SSH_LOG_PACKET, - "Sent a EOF on client channel (%d:%d)", + "Sent a EOF on client channel (%" PRIu32 ":%" PRIu32 ")", channel->local_channel, channel->remote_channel); if (rc != SSH_OK) { @@ -1314,7 +1433,7 @@ int ssh_channel_close(ssh_channel channel) rc = ssh_packet_send(session); SSH_LOG(SSH_LOG_PACKET, - "Sent a close on client channel (%d:%d)", + "Sent a close on client channel (%" PRIu32 ":%" PRIu32 ")", channel->local_channel, channel->remote_channel); @@ -1336,7 +1455,8 @@ error: } /* this termination function waits for a window growing condition */ -static int ssh_channel_waitwindow_termination(void *c){ +static int ssh_channel_waitwindow_termination(void *c) +{ ssh_channel channel = (ssh_channel) c; if (channel->remote_window > 0 || channel->session->session_state == SSH_SESSION_STATE_ERROR || @@ -1349,7 +1469,8 @@ static int ssh_channel_waitwindow_termination(void *c){ /* This termination function waits until the session is not in blocked status * anymore, e.g. because of a key re-exchange. */ -static int ssh_waitsession_unblocked(void *s){ +static int ssh_waitsession_unblocked(void *s) +{ ssh_session session = (ssh_session)s; switch (session->session_state){ case SSH_SESSION_STATE_DH: @@ -1369,8 +1490,9 @@ static int ssh_waitsession_unblocked(void *s){ * SSH_ERROR On error. * SSH_AGAIN Timeout elapsed (or in nonblocking mode). */ -int ssh_channel_flush(ssh_channel channel){ - return ssh_blocking_flush(channel->session, SSH_TIMEOUT_DEFAULT); +int ssh_channel_flush(ssh_channel channel) +{ + return ssh_blocking_flush(channel->session, SSH_TIMEOUT_DEFAULT); } static int channel_write_common(ssh_channel channel, @@ -1380,7 +1502,6 @@ static int channel_write_common(ssh_channel channel, ssh_session session; uint32_t origlen = len; size_t effectivelen; - size_t maxpacketlen; int rc; if(channel == NULL) { @@ -1393,20 +1514,14 @@ static int channel_write_common(ssh_channel channel, } if (len > INT_MAX) { - SSH_LOG(SSH_LOG_PROTOCOL, - "Length (%u) is bigger than INT_MAX", len); + SSH_LOG(SSH_LOG_TRACE, + "Length (%" PRIu32 ") is bigger than INT_MAX", len); return SSH_ERROR; } - /* - * Handle the max packet len from remote side, be nice - * 10 bytes for the headers - */ - maxpacketlen = channel->remote_maxpacket - 10; - if (channel->local_eof) { ssh_set_error(session, SSH_REQUEST_DENIED, - "Can't write to channel %d:%d after EOF was sent", + "Can't write to channel %" PRIu32 ":%" PRIu32 " after EOF was sent", channel->local_channel, channel->remote_channel); return -1; @@ -1430,14 +1545,14 @@ static int channel_write_common(ssh_channel channel, } while (len > 0) { if (channel->remote_window < len) { - SSH_LOG(SSH_LOG_PROTOCOL, - "Remote window is %d bytes. going to write %d bytes", + SSH_LOG(SSH_LOG_DEBUG, + "Remote window is %" PRIu32 " bytes. going to write %" PRIu32 " bytes", channel->remote_window, len); - /* What happens when the channel window is zero? */ + /* When the window is zero, wait for it to grow */ if(channel->remote_window == 0) { /* nothing can be written */ - SSH_LOG(SSH_LOG_PROTOCOL, + SSH_LOG(SSH_LOG_DEBUG, "Wait for a growing window message..."); rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_DEFAULT, ssh_channel_waitwindow_termination,channel); @@ -1448,12 +1563,17 @@ static int channel_write_common(ssh_channel channel, goto out; continue; } + /* When the window is non-zero, accept data up to the window size */ effectivelen = MIN(len, channel->remote_window); } else { effectivelen = len; } - effectivelen = MIN(effectivelen, maxpacketlen);; + /* + * Like OpenSSH, don't subtract bytes for the header fields + * and allow to send a payload of remote_maxpacket length. + */ + effectivelen = MIN(effectivelen, channel->remote_maxpacket); rc = ssh_buffer_pack(session->out_buffer, "bd", @@ -1491,7 +1611,7 @@ static int channel_write_common(ssh_channel channel, } SSH_LOG(SSH_LOG_PACKET, - "channel_write wrote %ld bytes", (long int) effectivelen); + "ssh_channel_write wrote %ld bytes", (long int) effectivelen); channel->remote_window -= effectivelen; len -= effectivelen; @@ -1519,7 +1639,7 @@ error: /** * @brief Get the remote window size. * - * This is the maximum amounts of bytes the remote side expects us to send + * This is the maximum amount of bytes the remote side expects us to send * before growing the window again. * * @param[in] channel The channel to query. @@ -1533,7 +1653,8 @@ error: * @warning A zero return value means ssh_channel_write (default settings) * will block until the window grows back. */ -uint32_t ssh_channel_window_size(ssh_channel channel) { +uint32_t ssh_channel_window_size(ssh_channel channel) +{ return channel->remote_window; } @@ -1550,8 +1671,9 @@ uint32_t ssh_channel_window_size(ssh_channel channel) { * * @see ssh_channel_read() */ -int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len) { - return channel_write_common(channel, data, len, 0); +int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len) +{ + return channel_write_common(channel, data, len, 0); } /** @@ -1563,8 +1685,9 @@ int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len) { * * @see ssh_channel_is_closed() */ -int ssh_channel_is_open(ssh_channel channel) { - if(channel == NULL) { +int ssh_channel_is_open(ssh_channel channel) +{ + if (channel == NULL) { return 0; } return (channel->state == SSH_CHANNEL_STATE_OPEN && channel->session->alive != 0); @@ -1579,8 +1702,9 @@ int ssh_channel_is_open(ssh_channel channel) { * * @see ssh_channel_is_open() */ -int ssh_channel_is_closed(ssh_channel channel) { - if(channel == NULL) { +int ssh_channel_is_closed(ssh_channel channel) +{ + if (channel == NULL) { return SSH_ERROR; } return (channel->state != SSH_CHANNEL_STATE_OPEN || channel->session->alive == 0); @@ -1593,18 +1717,16 @@ int ssh_channel_is_closed(ssh_channel channel) { * * @return 0 if there is no EOF, nonzero otherwise. */ -int ssh_channel_is_eof(ssh_channel channel) { - if(channel == NULL) { - return SSH_ERROR; - } - if ((channel->stdout_buffer && - ssh_buffer_get_len(channel->stdout_buffer) > 0) || - (channel->stderr_buffer && - ssh_buffer_get_len(channel->stderr_buffer) > 0)) { - return 0; - } +int ssh_channel_is_eof(ssh_channel channel) +{ + if (channel == NULL) { + return SSH_ERROR; + } + if (ssh_channel_has_unread_data(channel)) { + return 0; + } - return (channel->remote_eof != 0); + return (channel->remote_eof != 0); } /** @@ -1618,11 +1740,12 @@ int ssh_channel_is_eof(ssh_channel channel) { * in non-blocking mode. * @see ssh_set_blocking() */ -void ssh_channel_set_blocking(ssh_channel channel, int blocking) { - if(channel == NULL) { - return; - } - ssh_set_blocking(channel->session,blocking); +void ssh_channel_set_blocking(ssh_channel channel, int blocking) +{ + if (channel == NULL) { + return; + } + ssh_set_blocking(channel->session, blocking); } /** @@ -1642,7 +1765,7 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_success){ } SSH_LOG(SSH_LOG_PACKET, - "Received SSH_CHANNEL_SUCCESS on channel (%d:%d)", + "Received SSH_CHANNEL_SUCCESS on channel (%" PRIu32 ":%" PRIu32 ")", channel->local_channel, channel->remote_channel); if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING){ @@ -1650,6 +1773,12 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_success){ channel->request_state); } else { channel->request_state=SSH_CHANNEL_REQ_STATE_ACCEPTED; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_request_response_function, + channel->session, + channel); } return SSH_PACKET_USED; @@ -1673,7 +1802,7 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_failure){ } SSH_LOG(SSH_LOG_PACKET, - "Received SSH_CHANNEL_FAILURE on channel (%d:%d)", + "Received SSH_CHANNEL_FAILURE on channel (%" PRIu32 ":%" PRIu32 ")", channel->local_channel, channel->remote_channel); if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING){ @@ -1681,12 +1810,19 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_failure){ channel->request_state); } else { channel->request_state=SSH_CHANNEL_REQ_STATE_DENIED; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_request_response_function, + channel->session, + channel); } return SSH_PACKET_USED; } -static int ssh_channel_request_termination(void *c){ +static int ssh_channel_request_termination(void *c) +{ ssh_channel channel = (ssh_channel)c; if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING || channel->session->session_state == SSH_SESSION_STATE_ERROR) @@ -1696,7 +1832,8 @@ static int ssh_channel_request_termination(void *c){ } static int channel_request(ssh_channel channel, const char *request, - ssh_buffer buffer, int reply) { + ssh_buffer buffer, int reply) +{ ssh_session session = channel->session; int rc = SSH_ERROR; int ret; @@ -1757,7 +1894,7 @@ pending: rc=SSH_ERROR; break; case SSH_CHANNEL_REQ_STATE_ACCEPTED: - SSH_LOG(SSH_LOG_PROTOCOL, + SSH_LOG(SSH_LOG_DEBUG, "Channel request %s success",request); rc=SSH_OK; break; @@ -1782,7 +1919,7 @@ error: /** * @brief Request a pty with a specific type and size. * - * @param[in] channel The channel to sent the request. + * @param[in] channel The channel to send the request. * * @param[in] terminal The terminal type ("vt100, xterm,..."). * @@ -1790,13 +1927,18 @@ error: * * @param[in] row The number of rows. * + * @param[in] modes Encoded SSH terminal modes for the PTY + * + * @param[in] modes_len Number of bytes in 'modes' + * * @return SSH_OK on success, * SSH_ERROR if an error occurred, * SSH_AGAIN if in nonblocking mode and call has * to be done again. */ -int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, - int col, int row) { +int ssh_channel_request_pty_size_modes(ssh_channel channel, const char *terminal, + int col, int row, const unsigned char* modes, size_t modes_len) +{ ssh_session session; ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -1825,14 +1967,14 @@ int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, } rc = ssh_buffer_pack(buffer, - "sdddddb", + "sdddddP", terminal, col, row, 0, /* pix */ 0, /* pix */ - 1, /* add a 0byte string */ - 0); + (uint32_t)modes_len, + modes_len, modes); if (rc != SSH_OK) { ssh_set_error_oom(session); @@ -1846,6 +1988,23 @@ error: return rc; } +int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, + int col, int row) +{ + /* use modes from the current TTY */ + unsigned char modes_buf[SSH_TTY_MODES_MAX_BUFSIZE]; + int rc = encode_current_tty_opts(modes_buf, sizeof(modes_buf)); + if (rc < 0) { + return rc; + } + return ssh_channel_request_pty_size_modes(channel, + terminal, + col, + row, + modes_buf, + (size_t)rc); +} + /** * @brief Request a PTY. * @@ -1858,7 +2017,8 @@ error: * * @see ssh_channel_request_pty_size() */ -int ssh_channel_request_pty(ssh_channel channel) { +int ssh_channel_request_pty(ssh_channel channel) +{ return ssh_channel_request_pty_size(channel, "xterm", 80, 24); } @@ -1874,10 +2034,11 @@ int ssh_channel_request_pty(ssh_channel channel) { * @return SSH_OK on success, SSH_ERROR if an error occurred. * * @warning Do not call it from a signal handler if you are not sure any other - * libssh function using the same channel/session is running at same - * time (not 100% threadsafe). + * libssh function using the same channel/session is running at the + * same time (not 100% threadsafe). */ -int ssh_channel_change_pty_size(ssh_channel channel, int cols, int rows) { +int ssh_channel_change_pty_size(ssh_channel channel, int cols, int rows) +{ ssh_session session = channel->session; ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -1916,8 +2077,9 @@ error: * SSH_AGAIN if in nonblocking mode and call has * to be done again. */ -int ssh_channel_request_shell(ssh_channel channel) { - if(channel == NULL) { +int ssh_channel_request_shell(ssh_channel channel) +{ + if (channel == NULL) { return SSH_ERROR; } @@ -1938,7 +2100,8 @@ int ssh_channel_request_shell(ssh_channel channel) { * * @warning You normally don't have to call it for sftp, see sftp_new(). */ -int ssh_channel_request_subsystem(ssh_channel channel, const char *subsys) { +int ssh_channel_request_subsystem(ssh_channel channel, const char *subsys) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -1987,14 +2150,16 @@ error: * * @note You should use sftp_new() which does this for you. */ -int ssh_channel_request_sftp( ssh_channel channel){ +int ssh_channel_request_sftp( ssh_channel channel) +{ if(channel == NULL) { return SSH_ERROR; } return ssh_channel_request_subsystem(channel, "sftp"); } -static char *generate_cookie(void) { +static char *generate_cookie(void) +{ static const char *hex = "0123456789abcdef"; char s[36]; unsigned char rnd[16]; @@ -2018,7 +2183,7 @@ static char *generate_cookie(void) { * @brief Sends the "x11-req" channel request over an existing session channel. * * This will enable redirecting the display of the remote X11 applications to - * local X server over an secure tunnel. + * local X server over a secure tunnel. * * @param[in] channel An existing session channel where the remote X11 * applications are going to be executed. @@ -2040,7 +2205,8 @@ static char *generate_cookie(void) { * to be done again. */ int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, - const char *cookie, int screen_number) { + const char *cookie, int screen_number) +{ ssh_buffer buffer = NULL; char *c = NULL; int rc = SSH_ERROR; @@ -2091,7 +2257,8 @@ error: } static ssh_channel ssh_channel_accept(ssh_session session, int channeltype, - int timeout_ms, int *destination_port) { + int timeout_ms, int *destination_port, char **originator, int *originator_port) +{ #ifndef _WIN32 static const struct timespec ts = { .tv_sec = 0, @@ -2125,6 +2292,12 @@ static ssh_channel ssh_channel_accept(ssh_session session, int channeltype, if(destination_port) { *destination_port=msg->channel_request_open.destination_port; } + if(originator) { + *originator=strdup(msg->channel_request_open.originator); + } + if(originator_port) { + *originator_port=msg->channel_request_open.originator_port; + } ssh_message_free(msg); return channel; @@ -2155,8 +2328,9 @@ static ssh_channel ssh_channel_accept(ssh_session session, int channeltype, * @return A newly created channel, or NULL if no X11 request from * the server. */ -ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms) { - return ssh_channel_accept(channel->session, SSH_CHANNEL_X11, timeout_ms, NULL); +ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms) +{ + return ssh_channel_accept(channel->session, SSH_CHANNEL_X11, timeout_ms, NULL, NULL, NULL); } /** @@ -2225,7 +2399,8 @@ SSH_PACKET_CALLBACK(ssh_request_denied){ } -static int ssh_global_request_termination(void *s){ +static int ssh_global_request_termination(void *s) +{ ssh_session session = (ssh_session) s; if (session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING || session->session_state == SSH_SESSION_STATE_ERROR) @@ -2314,7 +2489,7 @@ pending: } switch(session->global_req_state){ case SSH_CHANNEL_REQ_STATE_ACCEPTED: - SSH_LOG(SSH_LOG_PROTOCOL, "Global request %s success",request); + SSH_LOG(SSH_LOG_DEBUG, "Global request %s success",request); rc=SSH_OK; break; case SSH_CHANNEL_REQ_STATE_DENIED: @@ -2404,18 +2579,21 @@ error: } /* DEPRECATED */ -int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port) { +int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port) +{ return ssh_channel_listen_forward(session, address, port, bound_port); } /* DEPRECATED */ -ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms) { - return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, NULL); +ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms) +{ + return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, NULL, NULL, NULL); } /** - * @brief Accept an incoming TCP/IP forwarding channel and get information - * about incomming connection + * @brief Accept an incoming TCP/IP forwarding channel and get some information + * about incoming connection + * * @param[in] session The ssh session to use. * * @param[in] timeout_ms A timeout in milliseconds. @@ -2426,7 +2604,31 @@ ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms) { * the server */ ssh_channel ssh_channel_accept_forward(ssh_session session, int timeout_ms, int* destination_port) { - return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, destination_port); + return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, destination_port, NULL, NULL); +} + +/** + * @brief Accept an incoming TCP/IP forwarding channel and get information + * about incoming connection + * + * @param[in] session The ssh session to use. + * + * @param[in] timeout_ms A timeout in milliseconds. + * + * @param[out] destination_port A pointer to destination port or NULL. + * + * @param[out] originator A pointer to a pointer to a string of originator host or NULL. + * That the caller is responsible for to ssh_string_free_char(). + * + * @param[out] originator_port A pointer to originator port or NULL. + * + * @return Newly created channel, or NULL if no incoming channel request from + * the server + * + * @see ssh_string_free_char() + */ +ssh_channel ssh_channel_open_forward_port(ssh_session session, int timeout_ms, int *destination_port, char **originator, int *originator_port) { + return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, destination_port, originator, originator_port); } /** @@ -2476,7 +2678,8 @@ error: } /* DEPRECATED */ -int ssh_forward_cancel(ssh_session session, const char *address, int port) { +int ssh_forward_cancel(ssh_session session, const char *address, int port) +{ return ssh_channel_cancel_forward(session, address, port); } @@ -2495,7 +2698,8 @@ int ssh_forward_cancel(ssh_session session, const char *address, int port) { * to be done again. * @warning Some environment variables may be refused by security reasons. */ -int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value) { +int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -2565,7 +2769,8 @@ error: * * @see ssh_channel_request_shell() */ -int ssh_channel_request_exec(ssh_channel channel, const char *cmd) { +int ssh_channel_request_exec(ssh_channel channel, const char *cmd) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -2609,10 +2814,6 @@ error: * Sends a signal 'sig' to the remote process. * Note, that remote system may not support signals concept. * In such a case this request will be silently ignored. - * Only SSH-v2 is supported (I'm not sure about SSH-v1). - * - * OpenSSH doesn't support signals yet, see: - * https://bugzilla.mindrot.org/show_bug.cgi?id=1424 * * @param[in] channel The channel to send signal. * @@ -2632,17 +2833,17 @@ error: * SIGUSR1 -> USR1 \n * SIGUSR2 -> USR2 \n * - * @return SSH_OK on success, SSH_ERROR if an error occurred - * (including attempts to send signal via SSH-v1 session). + * @return SSH_OK on success, SSH_ERROR if an error occurred. */ -int ssh_channel_request_send_signal(ssh_channel channel, const char *sig) { +int ssh_channel_request_send_signal(ssh_channel channel, const char *sig) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; - if(channel == NULL) { + if (channel == NULL) { return SSH_ERROR; } - if(sig == NULL) { + if (sig == NULL) { ssh_set_error_invalid(channel->session); return rc; } @@ -2672,16 +2873,15 @@ error: * Sends a break signal to the remote process. * Note, that remote system may not support breaks. * In such a case this request will be silently ignored. - * Only SSH-v2 is supported. * * @param[in] channel The channel to send the break to. * * @param[in] length The break-length in milliseconds to send. * * @return SSH_OK on success, SSH_ERROR if an error occurred - * (including attempts to send signal via SSH-v1 session). */ -int ssh_channel_request_send_break(ssh_channel channel, uint32_t length) { +int ssh_channel_request_send_break(ssh_channel channel, uint32_t length) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -2714,7 +2914,7 @@ error: * * @param[in] channel The channel to read from. * - * @param[in] buffer The buffer which will get the data. + * @param[out] buffer The buffer which will get the data. * * @param[in] count The count of bytes to be read. If it is bigger than 0, * the exact size will be read, else (bytes=0) it will @@ -2729,9 +2929,10 @@ error: * @see ssh_channel_read */ int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, - int is_stderr) { + int is_stderr) +{ ssh_session session; - char buffer_tmp[8192]; + char *buffer_tmp = NULL; int r; uint32_t total=0; @@ -2753,14 +2954,19 @@ int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, return r; } if(r > 0){ + count = r; + buffer_tmp = ssh_buffer_allocate(buffer, count); + if (buffer_tmp == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } r=ssh_channel_read(channel, buffer_tmp, r, is_stderr); if(r < 0){ + ssh_buffer_pass_bytes_end(buffer, count); return r; } - if(ssh_buffer_add_data(buffer,buffer_tmp,r) < 0){ - ssh_set_error_oom(session); - r = SSH_ERROR; - } + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(buffer, count - r); return r; } @@ -2770,19 +2976,23 @@ int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, ssh_handle_packets(channel->session, SSH_TIMEOUT_INFINITE); } while (r == 0); } + + buffer_tmp = ssh_buffer_allocate(buffer, count); + if (buffer_tmp == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } while(total < count){ - r=ssh_channel_read(channel, buffer_tmp, sizeof(buffer_tmp), is_stderr); + r=ssh_channel_read(channel, buffer_tmp, count - total, is_stderr); if(r<0){ + ssh_buffer_pass_bytes_end(buffer, count); return r; } if(r==0){ + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(buffer, count - total); return total; } - if (ssh_buffer_add_data(buffer,buffer_tmp,r) < 0) { - ssh_set_error_oom(session); - - return SSH_ERROR; - } total += r; } @@ -2791,13 +3001,13 @@ int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, struct ssh_channel_read_termination_struct { ssh_channel channel; - uint32_t count; ssh_buffer buffer; }; -static int ssh_channel_read_termination(void *s){ +static int ssh_channel_read_termination(void *s) +{ struct ssh_channel_read_termination_struct *ctx = s; - if (ssh_buffer_get_len(ctx->buffer) >= ctx->count || + if (ssh_buffer_get_len(ctx->buffer) >= 1 || ctx->channel->remote_eof || ctx->channel->session->session_state == SSH_SESSION_STATE_ERROR) return 1; @@ -2805,28 +3015,25 @@ static int ssh_channel_read_termination(void *s){ return 0; } -/* TODO FIXME Fix the delayed close thing */ -/* TODO FIXME Fix the blocking behaviours */ +/* TODO: FIXME Fix the blocking behaviours */ /** * @brief Reads data from a channel. * * @param[in] channel The channel to read from. * - * @param[in] dest The destination buffer which will get the data. + * @param[out] dest The destination buffer which will get the data. * * @param[in] count The count of bytes to be read. * * @param[in] is_stderr A boolean value to mark reading from the stderr flow. * * @return The number of bytes read, 0 on end of file or SSH_ERROR - * on error. In nonblocking mode it Can return 0 if no data + * on error. In nonblocking mode it can return 0 if no data * is available or SSH_AGAIN. * * @warning This function may return less than count bytes of data, and won't * block until count bytes have been read. - * @warning The read function using a buffer has been renamed to - * channel_read_buffer(). */ int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr) { @@ -2842,7 +3049,7 @@ int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_std * * @param[in] channel The channel to read from. * - * @param[in] dest The destination buffer which will get the data. + * @param[out] dest The destination buffer which will get the data. * * @param[in] count The count of bytes to be read. * @@ -2857,8 +3064,6 @@ int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_std * * @warning This function may return less than count bytes of data, and won't * block until count bytes have been read. - * @warning The read function using a buffer has been renamed to - * channel_read_buffer(). */ int ssh_channel_read_timeout(ssh_channel channel, void *dest, @@ -2891,28 +3096,17 @@ int ssh_channel_read_timeout(ssh_channel channel, stdbuf=channel->stderr_buffer; } - /* - * We may have problem if the window is too small to accept as much data - * as asked - */ SSH_LOG(SSH_LOG_PACKET, - "Read (%d) buffered : %d bytes. Window: %d", + "Read (%" PRIu32 ") buffered : %" PRIu32 " bytes. Window: %" PRIu32, count, ssh_buffer_get_len(stdbuf), channel->local_window); - if (count > ssh_buffer_get_len(stdbuf) + channel->local_window) { - if (grow_window(session, channel, count - ssh_buffer_get_len(stdbuf)) < 0) { - return -1; - } - } - /* block reading until at least one byte has been read * and ignore the trivial case count=0 */ ctx.channel = channel; ctx.buffer = stdbuf; - ctx.count = 1; if (timeout_ms < SSH_TIMEOUT_DEFAULT) { timeout_ms = SSH_TIMEOUT_INFINITE; @@ -2950,11 +3144,14 @@ int ssh_channel_read_timeout(ssh_channel channel, if (channel->counter != NULL) { channel->counter->in_bytes += len; } - /* Authorize some buffering while userapp is busy */ - if (channel->local_window < WINDOWLIMIT) { - if (grow_window(session, channel, 0) < 0) { - return -1; - } + /* Try completing the delayed_close */ + if (channel->delayed_close && !ssh_channel_has_unread_data(channel)) { + channel->state = SSH_CHANNEL_STATE_CLOSED; + } + + rc = grow_window(session, channel); + if (rc == SSH_ERROR) { + return -1; } return len; @@ -2964,20 +3161,18 @@ int ssh_channel_read_timeout(ssh_channel channel, * @brief Do a nonblocking read on the channel. * * A nonblocking read on the specified channel. it will return <= count bytes of - * data read atomically. + * data read atomically. It will also trigger any callbacks set on the channel. * * @param[in] channel The channel to read from. * - * @param[in] dest A pointer to a destination buffer. + * @param[out] dest A pointer to a destination buffer. * * @param[in] count The count of bytes of data to be read. * * @param[in] is_stderr A boolean to select the stderr stream. * - * @return The number of bytes read, 0 if nothing is available or - * SSH_ERROR on error. - * - * @warning Don't forget to check for EOF as it would return 0 here. + * @return The number of bytes read (0 if nothing is available), + * SSH_ERROR on error, and SSH_EOF if the channel is EOF. * * @see ssh_channel_is_eof() */ @@ -2987,7 +3182,7 @@ int ssh_channel_read_nonblocking(ssh_channel channel, int is_stderr) { ssh_session session; - ssize_t to_read; + uint32_t to_read; int rc; int blocking; @@ -3001,22 +3196,24 @@ int ssh_channel_read_nonblocking(ssh_channel channel, session = channel->session; - to_read = ssh_channel_poll(channel, is_stderr); + rc = ssh_channel_poll(channel, is_stderr); - if (to_read <= 0) { + if (rc <= 0) { if (session->session_state == SSH_SESSION_STATE_ERROR){ return SSH_ERROR; } - return to_read; /* may be an error code */ + return rc; /* may be an error code */ } - if ((size_t)to_read > count) { - to_read = (ssize_t)count; + to_read = (unsigned int)rc; + + if (to_read > count) { + to_read = count; } blocking = ssh_is_blocking(session); ssh_set_blocking(session, 0); - rc = ssh_channel_read(channel, dest, (uint32_t)to_read, is_stderr); + rc = ssh_channel_read(channel, dest, to_read, is_stderr); ssh_set_blocking(session,blocking); return rc; @@ -3031,15 +3228,18 @@ int ssh_channel_read_nonblocking(ssh_channel channel, * * @return The number of bytes available for reading, 0 if nothing * is available or SSH_ERROR on error. + * When a channel is freed the function returns + * SSH_ERROR immediately. * * @warning When the channel is in EOF state, the function returns SSH_EOF. * * @see ssh_channel_is_eof() */ -int ssh_channel_poll(ssh_channel channel, int is_stderr){ +int ssh_channel_poll(ssh_channel channel, int is_stderr) +{ ssh_buffer stdbuf; - if(channel == NULL) { + if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { return SSH_ERROR; } @@ -3085,6 +3285,7 @@ int ssh_channel_poll(ssh_channel channel, int is_stderr){ * SSH_ERROR on error. * * @warning When the channel is in EOF state, the function returns SSH_EOF. + * When a channel is freed the function returns SSH_ERROR immediately. * * @see ssh_channel_is_eof() */ @@ -3096,7 +3297,7 @@ int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr) size_t len; int rc; - if (channel == NULL) { + if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { return SSH_ERROR; } @@ -3108,7 +3309,6 @@ int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr) } ctx.buffer = stdbuf; ctx.channel = channel; - ctx.count = 1; rc = ssh_handle_packets_termination(channel->session, timeout, ssh_channel_read_termination, @@ -3148,15 +3348,17 @@ out: * * @return The session pointer. */ -ssh_session ssh_channel_get_session(ssh_channel channel) { - if(channel == NULL) { +ssh_session ssh_channel_get_session(ssh_channel channel) +{ + if (channel == NULL) { return NULL; } return channel->session; } -static int ssh_channel_exit_status_termination(void *c){ +static int ssh_channel_exit_status_termination(void *c) +{ ssh_channel channel = c; if(channel->exit_status != -1 || /* When a channel is closed, no exit status message can @@ -3178,15 +3380,18 @@ static int ssh_channel_exit_status_termination(void *c){ * (yet), or SSH_ERROR on error. * @warning This function may block until a timeout (or never) * if the other side is not willing to close the channel. + * When a channel is freed the function returns + * SSH_ERROR immediately. * * If you're looking for an async handling of this register a callback for the * exit status. * * @see ssh_channel_exit_status_callback */ -int ssh_channel_get_exit_status(ssh_channel channel) { +int ssh_channel_get_exit_status(ssh_channel channel) +{ int rc; - if(channel == NULL) { + if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { return SSH_ERROR; } rc = ssh_handle_packets_termination(channel->session, @@ -3208,8 +3413,11 @@ int ssh_channel_get_exit_status(ssh_channel channel) { * This is made in two parts: protocol select and network select. The protocol * select does not use the network functions at all */ -static int channel_protocol_select(ssh_channel *rchans, ssh_channel *wchans, - ssh_channel *echans, ssh_channel *rout, ssh_channel *wout, ssh_channel *eout) { +static int +channel_protocol_select(ssh_channel *rchans, ssh_channel *wchans, + ssh_channel *echans, ssh_channel *rout, + ssh_channel *wout, ssh_channel *eout) +{ ssh_channel chan; int i; int j = 0; @@ -3289,7 +3497,8 @@ static size_t count_ptrs(ssh_channel *ptrs) * function, or SSH_ERROR on error. */ int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, - ssh_channel *exceptchans, struct timeval * timeout) { + ssh_channel *exceptchans, struct timeval * timeout) +{ ssh_channel *rchans, *wchans, *echans; ssh_channel dummy = NULL; ssh_event event = NULL; @@ -3393,9 +3602,15 @@ int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, firstround=0; } while(1); - memcpy(readchans, rchans, (count_ptrs(rchans) + 1) * sizeof(ssh_channel )); - memcpy(writechans, wchans, (count_ptrs(wchans) + 1) * sizeof(ssh_channel )); - memcpy(exceptchans, echans, (count_ptrs(echans) + 1) * sizeof(ssh_channel )); + if (readchans != &dummy) { + memcpy(readchans, rchans, (count_ptrs(rchans) + 1) * sizeof(ssh_channel)); + } + if (writechans != &dummy) { + memcpy(writechans, wchans, (count_ptrs(wchans) + 1) * sizeof(ssh_channel)); + } + if (exceptchans != &dummy) { + memcpy(exceptchans, echans, (count_ptrs(echans) + 1) * sizeof(ssh_channel)); + } SAFE_FREE(rchans); SAFE_FREE(wchans); SAFE_FREE(echans); @@ -3423,7 +3638,8 @@ int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, * @param[in] counter Counter for bytes handled by the channel. */ void ssh_channel_set_counter(ssh_channel channel, - ssh_counter counter) { + ssh_counter counter) +{ if (channel != NULL) { channel->counter = counter; } @@ -3442,8 +3658,9 @@ void ssh_channel_set_counter(ssh_channel channel, * * @see ssh_channel_read() */ -int ssh_channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) { - return channel_write_common(channel, data, len, 1); +int ssh_channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) +{ + return channel_write_common(channel, data, len, 1); } #if WITH_SERVER @@ -3470,10 +3687,11 @@ int ssh_channel_write_stderr(ssh_channel channel, const void *data, uint32_t len * * @warning This function does not bind the local port and does not automatically * forward the content of a socket to the channel. You still have to - * use channel_read and channel_write for this. + * use ssh_channel_read and ssh_channel_write for this. */ int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost, - int remoteport, const char *sourcehost, int localport) { + int remoteport, const char *sourcehost, int localport) +{ ssh_session session; ssh_buffer payload = NULL; int rc = SSH_ERROR; @@ -3508,7 +3726,7 @@ int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost pending: rc = channel_open(channel, "forwarded-tcpip", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, payload); @@ -3533,10 +3751,11 @@ error: * to be done again. * @warning This function does not bind the local port and does not automatically * forward the content of a socket to the channel. You still have to - * use channel_read and channel_write for this. + * use shh_channel_read and ssh_channel_write for this. */ -int ssh_channel_open_x11(ssh_channel channel, - const char *orig_addr, int orig_port) { +int ssh_channel_open_x11(ssh_channel channel, + const char *orig_addr, int orig_port) +{ ssh_session session; ssh_buffer payload = NULL; int rc = SSH_ERROR; @@ -3570,7 +3789,7 @@ int ssh_channel_open_x11(ssh_channel channel, pending: rc = channel_open(channel, "x11", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, payload); @@ -3585,16 +3804,15 @@ error: * * Sends the exit status to the remote process (as described in RFC 4254, * section 6.10). - * Only SSH-v2 is supported (I'm not sure about SSH-v1). * * @param[in] channel The channel to send exit status. * * @param[in] exit_status The exit status to send * * @return SSH_OK on success, SSH_ERROR if an error occurred. - * (including attempts to send exit status via SSH-v1 session). */ -int ssh_channel_request_send_exit_status(ssh_channel channel, int exit_status) { +int ssh_channel_request_send_exit_status(ssh_channel channel, int exit_status) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -3626,7 +3844,6 @@ error: * This sends the exit status of the remote process. * Note, that remote system may not support signals concept. * In such a case this request will be silently ignored. - * Only SSH-v2 is supported (I'm not sure about SSH-v1). * * @param[in] channel The channel to send signal. * @@ -3637,10 +3854,10 @@ error: * @param[in] lang The language used in the message (format: RFC 3066) * * @return SSH_OK on success, SSH_ERROR if an error occurred - * (including attempts to send signal via SSH-v1 session). */ int ssh_channel_request_send_exit_signal(ssh_channel channel, const char *sig, - int core, const char *errmsg, const char *lang) { + int core, const char *errmsg, const char *lang) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -3677,4 +3894,4 @@ error: #endif -/* @} */ +/** @} */ diff --git a/src/client.c b/src/client.c index 4610b787..b3d0fe62 100644 --- a/src/client.c +++ b/src/client.c @@ -60,7 +60,8 @@ * @param code one of SSH_SOCKET_CONNECTED_OK or SSH_SOCKET_CONNECTED_ERROR * @param user is a pointer to session */ -static void socket_callback_connected(int code, int errno_code, void *user){ +static void socket_callback_connected(int code, int errno_code, void *user) +{ ssh_session session=(ssh_session)user; if (session->session_state != SSH_SESSION_STATE_CONNECTING && @@ -72,12 +73,14 @@ static void socket_callback_connected(int code, int errno_code, void *user){ return; } - SSH_LOG(SSH_LOG_RARE,"Socket connection callback: %d (%d)",code, errno_code); + SSH_LOG(SSH_LOG_TRACE,"Socket connection callback: %d (%d)",code, errno_code); if(code == SSH_SOCKET_CONNECTED_OK) session->session_state=SSH_SESSION_STATE_SOCKET_CONNECTED; else { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; session->session_state=SSH_SESSION_STATE_ERROR; - ssh_set_error(session,SSH_FATAL,"%s",strerror(errno_code)); + ssh_set_error(session,SSH_FATAL,"%s", + ssh_strerror(errno_code, err_msg, SSH_ERRNO_MSG_MAX)); } session->ssh_connection_callback(session); } @@ -93,12 +96,12 @@ static void socket_callback_connected(int code, int errno_code, void *user){ * @param user is a pointer to session * @returns Number of bytes processed, or zero if the banner is not complete. */ -static int callback_receive_banner(const void *data, size_t len, void *user) +static size_t callback_receive_banner(const void *data, size_t len, void *user) { char *buffer = (char *)data; - ssh_session session=(ssh_session) user; + ssh_session session = (ssh_session) user; char *str = NULL; - size_t i; + uint32_t i; int ret=0; if (session->session_state != SSH_SESSION_STATE_SOCKET_CONNECTED) { @@ -106,7 +109,7 @@ static int callback_receive_banner(const void *data, size_t len, void *user) "Wrong state in callback_receive_banner : %d", session->session_state); - return SSH_ERROR; + return 0; } for (i = 0; i < len; ++i) { #ifdef WITH_PCAP @@ -243,10 +246,13 @@ end: * @warning this function returning is no proof that DH handshake is * completed */ -static int dh_handshake(ssh_session session) { - +int dh_handshake(ssh_session session) +{ int rc = SSH_AGAIN; + SSH_LOG(SSH_LOG_TRACE, "dh_handshake_state = %d, kex_type = %d", + session->dh_handshake_state, session->next_crypto->kex_type); + switch (session->dh_handshake_state) { case DH_STATE_INIT: switch(session->next_crypto->kex_type){ @@ -299,18 +305,25 @@ static int dh_handshake(ssh_session session) { return rc; } -static int ssh_service_request_termination(void *s){ - ssh_session session = (ssh_session)s; - if(session->session_state == SSH_SESSION_STATE_ERROR || - session->auth.service_state != SSH_AUTH_SERVICE_SENT) - return 1; - else - return 0; +static int ssh_service_request_termination(void *s) +{ + ssh_session session = (ssh_session)s; + + if (session->session_state == SSH_SESSION_STATE_ERROR || + session->auth.service_state != SSH_AUTH_SERVICE_SENT) + return 1; + else + return 0; } /** - * @internal + * @addtogroup libssh_session * + * @{ + */ + +/** + * @internal * @brief Request a service from the SSH server. * * Service requests are for example: ssh-userauth, ssh-connection, etc. @@ -323,8 +336,9 @@ static int ssh_service_request_termination(void *s){ * @return SSH_AGAIN No response received yet * @bug actually only works with ssh-userauth */ -int ssh_service_request(ssh_session session, const char *service) { - int rc=SSH_ERROR; +int ssh_service_request(ssh_session session, const char *service) +{ + int rc = SSH_ERROR; if(session->auth.service_state != SSH_AUTH_SERVICE_NONE) goto pending; @@ -371,12 +385,6 @@ pending: } /** - * @addtogroup libssh_session - * - * @{ - */ - -/** * @internal * * @brief A function to be called each time a step has been done in the @@ -386,111 +394,117 @@ static void ssh_client_connection_callback(ssh_session session) { int rc; - switch(session->session_state) { - case SSH_SESSION_STATE_NONE: - case SSH_SESSION_STATE_CONNECTING: - break; - case SSH_SESSION_STATE_SOCKET_CONNECTED: - ssh_set_fd_towrite(session); - ssh_send_banner(session, 0); + SSH_LOG(SSH_LOG_DEBUG, "session_state=%d", session->session_state); - break; - case SSH_SESSION_STATE_BANNER_RECEIVED: - if (session->serverbanner == NULL) { - goto error; - } - set_status(session, 0.4f); - SSH_LOG(SSH_LOG_PROTOCOL, - "SSH server banner: %s", session->serverbanner); + switch (session->session_state) { + case SSH_SESSION_STATE_NONE: + case SSH_SESSION_STATE_CONNECTING: + break; + case SSH_SESSION_STATE_SOCKET_CONNECTED: + ssh_set_fd_towrite(session); + ssh_send_banner(session, 0); - /* Here we analyze the different protocols the server allows. */ - rc = ssh_analyze_banner(session, 0); - if (rc < 0) { - ssh_set_error(session, SSH_FATAL, - "No version of SSH protocol usable (banner: %s)", - session->serverbanner); - goto error; - } + break; + case SSH_SESSION_STATE_BANNER_RECEIVED: + if (session->serverbanner == NULL) { + goto error; + } + set_status(session, 0.4f); + SSH_LOG(SSH_LOG_DEBUG, "SSH server banner: %s", session->serverbanner); - ssh_packet_register_socket_callback(session, session->socket); + /* Here we analyze the different protocols the server allows. */ + rc = ssh_analyze_banner(session, 0); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "No version of SSH protocol usable (banner: %s)", + session->serverbanner); + goto error; + } + + ssh_packet_register_socket_callback(session, session->socket); + + ssh_packet_set_default_callbacks(session); + session->session_state = SSH_SESSION_STATE_INITIAL_KEX; + rc = ssh_set_client_kex(session); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_send_kex(session); + if (rc < 0) { + goto error; + } + set_status(session, 0.5f); - ssh_packet_set_default_callbacks(session); - session->session_state = SSH_SESSION_STATE_INITIAL_KEX; + break; + case SSH_SESSION_STATE_INITIAL_KEX: + /* TODO: This state should disappear in favor of get_key handle */ + break; + case SSH_SESSION_STATE_KEXINIT_RECEIVED: + set_status(session, 0.6f); + ssh_list_kex(&session->next_crypto->server_kex); + if ((session->flags & SSH_SESSION_FLAG_KEXINIT_SENT) == 0) { + /* in rekeying state if next_crypto client_kex might be empty */ rc = ssh_set_client_kex(session); if (rc != SSH_OK) { goto error; } - rc = ssh_send_kex(session, 0); + rc = ssh_send_kex(session); if (rc < 0) { goto error; } - set_status(session, 0.5f); - - break; - case SSH_SESSION_STATE_INITIAL_KEX: - /* TODO: This state should disappear in favor of get_key handle */ - break; - case SSH_SESSION_STATE_KEXINIT_RECEIVED: - set_status(session,0.6f); - ssh_list_kex(&session->next_crypto->server_kex); - if (session->next_crypto->client_kex.methods[0] == NULL) { - /* in rekeying state if next_crypto client_kex is empty */ - rc = ssh_set_client_kex(session); - if (rc != SSH_OK) { - goto error; - } - rc = ssh_send_kex(session, 0); - if (rc < 0) { - goto error; - } - } - if (ssh_kex_select_methods(session) == SSH_ERROR) - goto error; - set_status(session,0.8f); - session->session_state=SSH_SESSION_STATE_DH; - if (dh_handshake(session) == SSH_ERROR) { - goto error; - } - FALL_THROUGH; - case SSH_SESSION_STATE_DH: - if(session->dh_handshake_state==DH_STATE_FINISHED){ - set_status(session,1.0f); - session->connected = 1; - if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) - session->session_state = SSH_SESSION_STATE_AUTHENTICATED; - else - session->session_state=SSH_SESSION_STATE_AUTHENTICATING; - } - break; - case SSH_SESSION_STATE_AUTHENTICATING: - break; - case SSH_SESSION_STATE_ERROR: + } + if (ssh_kex_select_methods(session) == SSH_ERROR) goto error; - default: - ssh_set_error(session,SSH_FATAL,"Invalid state %d",session->session_state); + set_status(session, 0.8f); + session->session_state = SSH_SESSION_STATE_DH; + + /* If the init packet was already sent in previous step, this will be no + * operation */ + if (dh_handshake(session) == SSH_ERROR) { + goto error; + } + FALL_THROUGH; + case SSH_SESSION_STATE_DH: + if (session->dh_handshake_state == DH_STATE_FINISHED) { + set_status(session, 1.0f); + session->connected = 1; + if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) { + session->session_state = SSH_SESSION_STATE_AUTHENTICATED; + } else { + session->session_state = SSH_SESSION_STATE_AUTHENTICATING; + } + } + break; + case SSH_SESSION_STATE_AUTHENTICATING: + break; + case SSH_SESSION_STATE_ERROR: + goto error; + default: + ssh_set_error(session, SSH_FATAL, "Invalid state %d", + session->session_state); } return; error: - ssh_socket_close(session->socket); - session->alive = 0; - session->session_state=SSH_SESSION_STATE_ERROR; - + ssh_session_socket_close(session); + SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(session)); } /** @internal * @brief describe under which conditions the ssh_connect function may stop */ -static int ssh_connect_termination(void *user){ - ssh_session session = (ssh_session)user; - switch(session->session_state){ +static int ssh_connect_termination(void *user) +{ + ssh_session session = (ssh_session)user; + + switch (session->session_state) { case SSH_SESSION_STATE_ERROR: case SSH_SESSION_STATE_AUTHENTICATING: case SSH_SESSION_STATE_DISCONNECTED: - return 1; + return 1; default: - return 0; - } + return 0; + } } /** @@ -558,7 +572,7 @@ int ssh_connect(ssh_session session) return SSH_ERROR; } - SSH_LOG(SSH_LOG_PROTOCOL, + SSH_LOG(SSH_LOG_DEBUG, "libssh %s, using threading %s", ssh_copyright(), ssh_threads_get_type()); @@ -593,7 +607,7 @@ int ssh_connect(ssh_session session) set_status(session, 0.2f); session->alive = 1; - SSH_LOG(SSH_LOG_PROTOCOL, + SSH_LOG(SSH_LOG_DEBUG, "Socket connecting, now waiting for the callbacks to work"); pending: @@ -649,12 +663,13 @@ pending: * * @return A newly allocated string with the banner, NULL on error. */ -char *ssh_get_issue_banner(ssh_session session) { - if (session == NULL || session->banner == NULL) { - return NULL; - } +char *ssh_get_issue_banner(ssh_session session) +{ + if (session == NULL || session->banner == NULL) { + return NULL; + } - return ssh_string_to_char(session->banner); + return ssh_string_to_char(session->banner); } /** @@ -675,102 +690,185 @@ char *ssh_get_issue_banner(ssh_session session) { * } * @endcode */ -int ssh_get_openssh_version(ssh_session session) { - if (session == NULL) { - return 0; - } +int ssh_get_openssh_version(ssh_session session) +{ + if (session == NULL) { + return 0; + } + + return session->openssh; +} + +/** + * @brief Most SSH connections will only ever request a single session, but an + * attacker may abuse a running ssh client to surreptitiously open + * additional sessions under their control. OpenSSH provides a global + * request "no-more-sessions@openssh.com" to mitigate this attack. + * + * @param[in] session The SSH session to use. + * + * @returns SSH_OK on success, SSH_ERROR on error. + * @returns SSH_AGAIN, if the session is in nonblocking mode, + * and call must be done again. + */ +int ssh_request_no_more_sessions(ssh_session session) +{ + if (session == NULL) { + return SSH_ERROR; + } - return session->openssh; + return ssh_global_request(session, "no-more-sessions@openssh.com", NULL, 1); } /** + * @brief Add disconnect message when ssh_session is disconnected + * To add a disconnect message to give peer a better hint. + * @param session The SSH session to use. + * @param message The message to send after the session is disconnected. + * If no message is passed then a default message i.e + * "Bye Bye" will be sent. + */ +int +ssh_session_set_disconnect_message(ssh_session session, const char *message) +{ + if (session == NULL) { + return SSH_ERROR; + } + + if (message == NULL || strlen(message) == 0) { + SAFE_FREE(session->disconnect_message); //To free any message set earlier. + session->disconnect_message = strdup("Bye Bye") ; + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + return SSH_OK; + } + SAFE_FREE(session->disconnect_message); //To free any message set earlier. + session->disconnect_message = strdup(message); + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + return SSH_OK; +} + + +/** * @brief Disconnect from a session (client or server). + * * The session can then be reused to open a new session. * + * @note Note that this function won't close the socket if it was set with + * ssh_options_set and SSH_OPTIONS_FD. You're responsible for closing the + * socket. This is new behavior in libssh 0.10. + * * @param[in] session The SSH session to use. */ -void ssh_disconnect(ssh_session session) { - struct ssh_iterator *it; - int rc; +void +ssh_disconnect(ssh_session session) +{ + struct ssh_iterator *it; + int rc; - if (session == NULL) { - return; - } + if (session == NULL) { + return; + } - if (session->socket != NULL && ssh_socket_is_open(session->socket)) { - rc = ssh_buffer_pack(session->out_buffer, - "bdss", - SSH2_MSG_DISCONNECT, - SSH2_DISCONNECT_BY_APPLICATION, - "Bye Bye", - ""); /* language tag */ - if (rc != SSH_OK){ - ssh_set_error_oom(session); - goto error; + if (session->disconnect_message == NULL) { + session->disconnect_message = strdup("Bye Bye") ; + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + goto error; + } + } + + if (session->socket != NULL && ssh_socket_is_open(session->socket)) { + rc = ssh_buffer_pack(session->out_buffer, + "bdss", + SSH2_MSG_DISCONNECT, + SSH2_DISCONNECT_BY_APPLICATION, + session->disconnect_message, + ""); /* language tag */ + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + ssh_packet_send(session); + ssh_session_socket_close(session); } - ssh_packet_send(session); - ssh_socket_close(session->socket); - } error: - session->recv_seq = 0; - session->send_seq = 0; - session->alive = 0; - if (session->socket != NULL){ - ssh_socket_reset(session->socket); - } - session->opts.fd = SSH_INVALID_SOCKET; - session->session_state=SSH_SESSION_STATE_DISCONNECTED; + session->recv_seq = 0; + session->send_seq = 0; + session->alive = 0; + if (session->socket != NULL){ + ssh_socket_reset(session->socket); + } + session->opts.fd = SSH_INVALID_SOCKET; + session->session_state = SSH_SESSION_STATE_DISCONNECTED; + session->pending_call_state = SSH_PENDING_CALL_NONE; - while ((it=ssh_list_get_iterator(session->channels)) != NULL) { - ssh_channel_do_free(ssh_iterator_value(ssh_channel,it)); - ssh_list_remove(session->channels, it); - } - if(session->current_crypto){ - crypto_free(session->current_crypto); - session->current_crypto=NULL; - } - if (session->next_crypto) { - crypto_free(session->next_crypto); - session->next_crypto = crypto_new(); - if (session->next_crypto == NULL) { - ssh_set_error_oom(session); + while ((it = ssh_list_get_iterator(session->channels)) != NULL) { + ssh_channel_do_free(ssh_iterator_value(ssh_channel, it)); + ssh_list_remove(session->channels, it); } - } - if (session->in_buffer) { - ssh_buffer_reinit(session->in_buffer); - } - if (session->out_buffer) { - ssh_buffer_reinit(session->out_buffer); - } - if (session->in_hashbuf) { - ssh_buffer_reinit(session->in_hashbuf); - } - if (session->out_hashbuf) { - ssh_buffer_reinit(session->out_hashbuf); - } - session->auth.supported_methods = 0; - SAFE_FREE(session->serverbanner); - SAFE_FREE(session->clientbanner); - - if(session->ssh_message_list){ - ssh_message msg; - while((msg=ssh_list_pop_head(ssh_message ,session->ssh_message_list)) - != NULL){ - ssh_message_free(msg); - } - ssh_list_free(session->ssh_message_list); - session->ssh_message_list=NULL; - } + if (session->current_crypto) { + crypto_free(session->current_crypto); + session->current_crypto = NULL; + } + if (session->next_crypto) { + crypto_free(session->next_crypto); + session->next_crypto = crypto_new(); + if (session->next_crypto == NULL) { + ssh_set_error_oom(session); + } + } + if (session->in_buffer) { + ssh_buffer_reinit(session->in_buffer); + } + if (session->out_buffer) { + ssh_buffer_reinit(session->out_buffer); + } + if (session->in_hashbuf) { + ssh_buffer_reinit(session->in_hashbuf); + } + if (session->out_hashbuf) { + ssh_buffer_reinit(session->out_hashbuf); + } + session->auth.supported_methods = 0; + SAFE_FREE(session->serverbanner); + SAFE_FREE(session->clientbanner); + SAFE_FREE(session->disconnect_message); - if (session->packet_callbacks){ - ssh_list_free(session->packet_callbacks); - session->packet_callbacks=NULL; - } + if (session->ssh_message_list) { + ssh_message msg = NULL; + + while ((msg = ssh_list_pop_head(ssh_message, + session->ssh_message_list)) != NULL) { + ssh_message_free(msg); + } + ssh_list_free(session->ssh_message_list); + session->ssh_message_list = NULL; + } + + if (session->packet_callbacks) { + ssh_list_free(session->packet_callbacks); + session->packet_callbacks = NULL; + } } -const char *ssh_copyright(void) { - return SSH_STRINGIFY(LIBSSH_VERSION) " (c) 2003-2019 " +/** + * @brief Copyright information + * + * Returns copyright information + * + * @returns SSH_STRING copyright + */ +const char *ssh_copyright(void) +{ + return SSH_STRINGIFY(LIBSSH_VERSION) " (c) 2003-2024 " "Aris Adamantiadis, Andreas Schneider " "and libssh contributors. " "Distributed under the LGPL, please refer to COPYING " diff --git a/src/config.c b/src/config.c index 79a64339..7135c3b1 100644 --- a/src/config.c +++ b/src/config.c @@ -48,7 +48,9 @@ #include "libssh/misc.h" #include "libssh/options.h" +#ifndef MAX_LINE_SIZE #define MAX_LINE_SIZE 1024 +#endif struct ssh_config_keyword_table_s { const char *name; @@ -66,7 +68,6 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "macs", SOC_MACS }, { "compression", SOC_COMPRESSION }, { "connecttimeout", SOC_TIMEOUT }, - { "protocol", SOC_PROTOCOL }, { "stricthostkeychecking", SOC_STRICTHOSTKEYCHECK }, { "userknownhostsfile", SOC_KNOWNHOSTS }, { "proxycommand", SOC_PROXYCOMMAND }, @@ -79,7 +80,6 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "loglevel", SOC_LOGLEVEL}, { "hostkeyalgorithms", SOC_HOSTKEYALGORITHMS}, { "kexalgorithms", SOC_KEXALGORITHMS}, - { "mac", SOC_UNSUPPORTED}, /* SSHv1 */ { "gssapiauthentication", SOC_GSSAPIAUTHENTICATION}, { "kbdinteractiveauthentication", SOC_KBDINTERACTIVEAUTHENTICATION}, { "passwordauthentication", SOC_PASSWORDAUTHENTICATION}, @@ -92,24 +92,19 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "canonicalizehostname", SOC_UNSUPPORTED}, { "canonicalizemaxdots", SOC_UNSUPPORTED}, { "canonicalizepermittedcnames", SOC_UNSUPPORTED}, - { "certificatefile", SOC_UNSUPPORTED}, - { "challengeresponseauthentication", SOC_UNSUPPORTED}, + { "certificatefile", SOC_CERTIFICATE}, + { "kbdinteractiveauthentication", SOC_UNSUPPORTED}, { "checkhostip", SOC_UNSUPPORTED}, - { "cipher", SOC_UNSUPPORTED}, /* SSHv1 */ - { "compressionlevel", SOC_UNSUPPORTED}, /* SSHv1 */ { "connectionattempts", SOC_UNSUPPORTED}, { "enablesshkeysign", SOC_UNSUPPORTED}, { "fingerprinthash", SOC_UNSUPPORTED}, { "forwardagent", SOC_UNSUPPORTED}, - { "gssapikeyexchange", SOC_UNSUPPORTED}, - { "gssapirenewalforcesrekey", SOC_UNSUPPORTED}, - { "gssapitrustdns", SOC_UNSUPPORTED}, { "hashknownhosts", SOC_UNSUPPORTED}, { "hostbasedauthentication", SOC_UNSUPPORTED}, - { "hostbasedkeytypes", SOC_UNSUPPORTED}, + { "hostbasedacceptedalgorithms", SOC_UNSUPPORTED}, { "hostkeyalias", SOC_UNSUPPORTED}, - { "identitiesonly", SOC_UNSUPPORTED}, - { "identityagent", SOC_UNSUPPORTED}, + { "identitiesonly", SOC_IDENTITIESONLY}, + { "identityagent", SOC_IDENTITYAGENT}, { "ipqos", SOC_UNSUPPORTED}, { "kbdinteractivedevices", SOC_UNSUPPORTED}, { "nohostauthenticationforlocalhost", SOC_UNSUPPORTED}, @@ -118,12 +113,10 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "preferredauthentications", SOC_UNSUPPORTED}, { "proxyjump", SOC_PROXYJUMP}, { "proxyusefdpass", SOC_UNSUPPORTED}, - { "pubkeyacceptedtypes", SOC_PUBKEYACCEPTEDTYPES}, + { "pubkeyacceptedalgorithms", SOC_PUBKEYACCEPTEDKEYTYPES}, { "rekeylimit", SOC_REKEYLIMIT}, { "remotecommand", SOC_UNSUPPORTED}, { "revokedhostkeys", SOC_UNSUPPORTED}, - { "rhostsrsaauthentication", SOC_UNSUPPORTED}, - { "rsaauthentication", SOC_UNSUPPORTED}, /* SSHv1 */ { "serveralivecountmax", SOC_UNSUPPORTED}, { "serveraliveinterval", SOC_UNSUPPORTED}, { "streamlocalbindmask", SOC_UNSUPPORTED}, @@ -131,13 +124,12 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "syslogfacility", SOC_UNSUPPORTED}, { "tcpkeepalive", SOC_UNSUPPORTED}, { "updatehostkeys", SOC_UNSUPPORTED}, - { "useprivilegedport", SOC_UNSUPPORTED}, { "verifyhostkeydns", SOC_UNSUPPORTED}, { "visualhostkey", SOC_UNSUPPORTED}, { "clearallforwardings", SOC_NA}, - { "controlmaster", SOC_NA}, + { "controlmaster", SOC_CONTROLMASTER}, { "controlpersist", SOC_NA}, - { "controlpath", SOC_NA}, + { "controlpath", SOC_CONTROLPATH}, { "dynamicforward", SOC_NA}, { "escapechar", SOC_NA}, { "exitonforwardfailure", SOC_NA}, @@ -155,7 +147,7 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "tunnel", SOC_NA}, { "tunneldevice", SOC_NA}, { "xauthlocation", SOC_NA}, - { "pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDTYPES}, + { "pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDKEYTYPES}, { NULL, SOC_UNKNOWN } }; @@ -189,7 +181,7 @@ static struct ssh_config_match_keyword_table_s ssh_config_match_keyword_table[] }; static int ssh_config_parse_line(ssh_session session, const char *line, - unsigned int count, int *parsing); + unsigned int count, int *parsing, unsigned int depth, bool global); static enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) { int i; @@ -203,16 +195,26 @@ static enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) { return SOC_UNKNOWN; } +#define LIBSSH_CONF_MAX_DEPTH 16 static void local_parse_file(ssh_session session, const char *filename, - int *parsing) + int *parsing, + unsigned int depth, + bool global) { FILE *f; char line[MAX_LINE_SIZE] = {0}; unsigned int count = 0; int rv; + if (depth > LIBSSH_CONF_MAX_DEPTH) { + ssh_set_error(session, SSH_FATAL, + "ERROR - Too many levels of configuration includes " + "when processing file '%s'", filename); + return; + } + f = fopen(filename, "r"); if (f == NULL) { SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", @@ -223,7 +225,7 @@ local_parse_file(ssh_session session, SSH_LOG(SSH_LOG_PACKET, "Reading additional configuration data from %s", filename); while (fgets(line, sizeof(line), f)) { count++; - rv = ssh_config_parse_line(session, line, count, parsing); + rv = ssh_config_parse_line(session, line, count, parsing, depth, global); if (rv < 0) { fclose(f); return; @@ -237,7 +239,9 @@ local_parse_file(ssh_session session, #if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) static void local_parse_glob(ssh_session session, const char *fileglob, - int *parsing) + int *parsing, + unsigned int depth, + bool global) { glob_t globbuf = { .gl_flags = 0, @@ -257,7 +261,7 @@ static void local_parse_glob(ssh_session session, } for (i = 0; i < globbuf.gl_pathc; i++) { - local_parse_file(session, globbuf.gl_pathv[i], parsing); + local_parse_file(session, globbuf.gl_pathv[i], parsing, depth, global); } globfree(&globbuf); @@ -315,6 +319,7 @@ ssh_exec_shell(char *cmd) char *shell = NULL; pid_t pid; int status, devnull, rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; shell = getenv("SHELL"); if (shell == NULL || shell[0] == '\0') { @@ -330,7 +335,8 @@ ssh_exec_shell(char *cmd) /* Need this to redirect subprocess stdin/out */ devnull = open("/dev/null", O_RDWR); if (devnull == -1) { - SSH_LOG(SSH_LOG_WARN, "Failed to open(/dev/null): %s", strerror(errno)); + SSH_LOG(SSH_LOG_WARN, "Failed to open(/dev/null): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return -1; } @@ -342,13 +348,15 @@ ssh_exec_shell(char *cmd) /* Redirect child stdin and stdout. Leave stderr */ rc = dup2(devnull, STDIN_FILENO); if (rc == -1) { - SSH_LOG(SSH_LOG_WARN, "dup2: %s", strerror(errno)); + SSH_LOG(SSH_LOG_WARN, "dup2: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); exit(1); } rc = dup2(devnull, STDOUT_FILENO); if (rc == -1) { - SSH_LOG(SSH_LOG_WARN, "dup2: %s", strerror(errno)); - exit(1); + SSH_LOG(SSH_LOG_WARN, "dup2: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + exit(1); } if (devnull > STDERR_FILENO) { close(devnull); @@ -362,7 +370,7 @@ ssh_exec_shell(char *cmd) rc = execv(argv[0], argv); if (rc == -1) { SSH_LOG(SSH_LOG_WARN, "Failed to execute command '%s': %s", cmd, - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); /* Die with signal to make this error apparent to parent. */ signal(SIGTERM, SIG_DFL); kill(getpid(), SIGTERM); @@ -373,19 +381,21 @@ ssh_exec_shell(char *cmd) /* Parent */ close(devnull); if (pid == -1) { /* Error */ - SSH_LOG(SSH_LOG_WARN, "Failed to fork child: %s", strerror(errno)); + SSH_LOG(SSH_LOG_WARN, "Failed to fork child: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return -1; } while (waitpid(pid, &status, 0) == -1) { if (errno != EINTR) { - SSH_LOG(SSH_LOG_WARN, "waitpid failed: %s", strerror(errno)); + SSH_LOG(SSH_LOG_WARN, "waitpid failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return -1; } } if (!WIFEXITED(status)) { - SSH_LOG(SSH_LOG_WARN, "Command %s exitted abnormally", cmd); + SSH_LOG(SSH_LOG_WARN, "Command %s exited abnormally", cmd); return -1; } SSH_LOG(SSH_LOG_TRACE, "Command '%s' returned %d", cmd, WEXITSTATUS(status)); @@ -454,7 +464,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) } if (parse_entry) { /* We actually care only about the first item */ - rv = ssh_config_parse_uri(cp, &username, &hostname, &port); + rv = ssh_config_parse_uri(cp, &username, &hostname, &port, false); /* The rest of the list needs to be passed on */ if (endp != NULL) { next = strdup(endp + 1); @@ -465,7 +475,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) } } else { /* The rest is just sanity-checked to avoid failures later */ - rv = ssh_config_parse_uri(cp, NULL, NULL, NULL); + rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false); } if (rv != SSH_OK) { goto out; @@ -481,7 +491,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) if (hostname != NULL && do_parsing) { char com[512] = {0}; - rv = snprintf(com, sizeof(com), "ssh%s%s%s%s%s%s -W [%%h]:%%p %s", + rv = snprintf(com, sizeof(com), "ssh%s%s%s%s%s%s -W '[%%h]:%%p' %s", username ? " -l " : "", username ? username : "", port ? " -p " : "", @@ -490,7 +500,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) next ? next : "", hostname); if (rv < 0 || rv >= (int)sizeof(com)) { - SSH_LOG(SSH_LOG_WARN, "Too long ProxyJump configuration line"); + SSH_LOG(SSH_LOG_TRACE, "Too long ProxyJump configuration line"); rv = SSH_ERROR; goto out; } @@ -507,11 +517,68 @@ out: return rv; } +static char * +ssh_config_make_absolute(ssh_session session, + const char *path, + bool global) +{ + size_t outlen = 0; + char *out = NULL; + int rv; + + /* Looks like absolute path */ + if (path[0] == '/') { + return strdup(path); + } + + /* relative path */ + if (global) { + /* Parsing global config */ + outlen = strlen(path) + strlen("/etc/ssh/") + 1; + out = malloc(outlen); + if (out == NULL) { + ssh_set_error_oom(session); + return NULL; + } + rv = snprintf(out, outlen, "/etc/ssh/%s", path); + if (rv < 1) { + free(out); + return NULL; + } + return out; + } + + /* paths starting with tilde are already absolute */ + if (path[0] == '~') { + return ssh_path_expand_tilde(path); + } + + /* Parsing user config relative to home directory (generally ~/.ssh) */ + if (session->opts.sshdir == NULL) { + ssh_set_error_invalid(session); + return NULL; + } + outlen = strlen(path) + strlen(session->opts.sshdir) + 1 + 1; + out = malloc(outlen); + if (out == NULL) { + ssh_set_error_oom(session); + return NULL; + } + rv = snprintf(out, outlen, "%s/%s", session->opts.sshdir, path); + if (rv < 1) { + free(out); + return NULL; + } + return out; +} + static int ssh_config_parse_line(ssh_session session, const char *line, unsigned int count, - int *parsing) + int *parsing, + unsigned int depth, + bool global) { enum ssh_config_opcode_e opcode; const char *p = NULL, *p2 = NULL; @@ -555,7 +622,10 @@ ssh_config_parse_line(ssh_session session, opcode != SOC_HOST && opcode != SOC_MATCH && opcode != SOC_INCLUDE && - opcode > SOC_UNSUPPORTED) { /* Ignore all unknown types here */ + opcode != SOC_IDENTITY && + opcode != SOC_CERTIFICATE && + opcode > SOC_UNSUPPORTED && + opcode < SOC_MAX) { /* Ignore all unknown types here */ /* Skip all the options that were already applied */ if (seen[opcode] != 0) { SAFE_FREE(x); @@ -569,11 +639,19 @@ ssh_config_parse_line(ssh_session session, p = ssh_config_get_str_tok(&s, NULL); if (p && *parsing) { + char *path = ssh_config_make_absolute(session, p, global); + if (path == NULL) { + SSH_LOG(SSH_LOG_WARN, "line %d: Failed to allocate memory " + "for the include path expansion", count); + SAFE_FREE(x); + return -1; + } #if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) - local_parse_glob(session, p, parsing); + local_parse_glob(session, path, parsing, depth + 1, global); #else - local_parse_file(session, p, parsing); + local_parse_file(session, path, parsing, depth + 1, global); #endif /* HAVE_GLOB */ + free(path); } break; @@ -591,7 +669,7 @@ ssh_config_parse_line(ssh_session session, break; } args++; - SSH_LOG(SSH_LOG_TRACE, "line %d: Processing Match keyword '%s'", + SSH_LOG(SSH_LOG_DEBUG, "line %d: Processing Match keyword '%s'", count, p); /* If the option is prefixed with ! the result should be negated */ @@ -623,7 +701,7 @@ ssh_config_parse_line(ssh_session session, case MATCH_FINAL: case MATCH_CANONICAL: - SSH_LOG(SSH_LOG_INFO, + SSH_LOG(SSH_LOG_DEBUG, "line %d: Unsupported Match keyword '%s', skipping", count, p); @@ -635,13 +713,13 @@ ssh_config_parse_line(ssh_session session, /* Skip one argument (including in quotes) */ p = ssh_config_get_token(&s); if (p == NULL || p[0] == '\0') { - SSH_LOG(SSH_LOG_WARN, "line %d: Match keyword " + SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword " "'%s' requires argument", count, p2); SAFE_FREE(x); return -1; } if (result != 1) { - SSH_LOG(SSH_LOG_INFO, "line %d: Skipped match exec " + SSH_LOG(SSH_LOG_DEBUG, "line %d: Skipped match exec " "'%s' as previous conditions already failed.", count, p2); continue; @@ -662,7 +740,7 @@ ssh_config_parse_line(ssh_session session, } localuser = ssh_get_local_username(); if (localuser == NULL) { - SSH_LOG(SSH_LOG_WARN, "line %d: Can not get local username " + SSH_LOG(SSH_LOG_TRACE, "line %d: Can not get local username " "for conditional matching.", count); SAFE_FREE(x); return -1; @@ -676,13 +754,13 @@ ssh_config_parse_line(ssh_session session, /* Skip one argument */ p = ssh_config_get_str_tok(&s, NULL); if (p == NULL || p[0] == '\0') { - SSH_LOG(SSH_LOG_WARN, "line %d: Match keyword " + SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword " "'%s' requires argument", count, p2); SAFE_FREE(x); return -1; } args++; - SSH_LOG(SSH_LOG_INFO, + SSH_LOG(SSH_LOG_TRACE, "line %d: Unsupported Match keyword '%s', ignoring", count, p2); @@ -812,34 +890,6 @@ ssh_config_parse_line(ssh_session session, } } break; - case SOC_PROTOCOL: - p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { - char *a, *b; - b = strdup(p); - if (b == NULL) { - SAFE_FREE(x); - ssh_set_error_oom(session); - return -1; - } - i = 0; - ssh_options_set(session, SSH_OPTIONS_SSH2, &i); - - for (a = strtok(b, ","); a; a = strtok(NULL, ",")) { - switch (atoi(a)) { - case 1: - break; - case 2: - i = 1; - ssh_options_set(session, SSH_OPTIONS_SSH2, &i); - break; - default: - break; - } - } - SAFE_FREE(b); - } - break; case SOC_TIMEOUT: l = ssh_config_get_long(&s, -1); if (l >= 0 && *parsing) { @@ -917,10 +967,10 @@ ssh_config_parse_line(ssh_session session, if (strcasecmp(p, "quiet") == 0) { value = SSH_LOG_NONE; } else if (strcasecmp(p, "fatal") == 0 || - strcasecmp(p, "error")== 0 || - strcasecmp(p, "info") == 0) { + strcasecmp(p, "error")== 0) { value = SSH_LOG_WARN; - } else if (strcasecmp(p, "verbose") == 0) { + } else if (strcasecmp(p, "verbose") == 0 || + strcasecmp(p, "info") == 0) { value = SSH_LOG_INFO; } else if (strcasecmp(p, "DEBUG") == 0 || strcasecmp(p, "DEBUG1") == 0) { @@ -940,7 +990,7 @@ ssh_config_parse_line(ssh_session session, ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, p); } break; - case SOC_PUBKEYACCEPTEDTYPES: + case SOC_PUBKEYACCEPTEDKEYTYPES: p = ssh_config_get_str_tok(&s, NULL); if (p && *parsing) { ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, p); @@ -958,20 +1008,20 @@ ssh_config_parse_line(ssh_session session, if (p == NULL) { break; } else if (strcmp(p, "default") == 0) { - /* Default rekey limits enforced automaticaly */ + /* Default rekey limits enforced automatically */ ll = 0; } else { char *endp = NULL; ll = strtoll(p, &endp, 10); if (p == endp || ll < 0) { /* No number or negative */ - SSH_LOG(SSH_LOG_WARN, "Invalid argument to rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Invalid argument to rekey limit"); break; } switch (*endp) { case 'G': if (ll > LLONG_MAX / 1024) { - SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); ll = -1; break; } @@ -979,7 +1029,7 @@ ssh_config_parse_line(ssh_session session, FALL_THROUGH; case 'M': if (ll > LLONG_MAX / 1024) { - SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); ll = -1; break; } @@ -987,7 +1037,7 @@ ssh_config_parse_line(ssh_session session, FALL_THROUGH; case 'K': if (ll > LLONG_MAX / 1024) { - SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); ll = -1; break; } @@ -1003,7 +1053,7 @@ ssh_config_parse_line(ssh_session session, break; } if (*endp != ' ' && *endp != '\0') { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Invalid trailing characters after the rekey limit: %s", endp); break; @@ -1024,14 +1074,14 @@ ssh_config_parse_line(ssh_session session, ll = strtoll(p, &endp, 10); if (p == endp || ll < 0) { /* No number or negative */ - SSH_LOG(SSH_LOG_WARN, "Invalid argument to rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Invalid argument to rekey limit"); break; } switch (*endp) { case 'w': case 'W': if (ll > LLONG_MAX / 7) { - SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); ll = -1; break; } @@ -1040,7 +1090,7 @@ ssh_config_parse_line(ssh_session session, case 'd': case 'D': if (ll > LLONG_MAX / 24) { - SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); ll = -1; break; } @@ -1049,7 +1099,7 @@ ssh_config_parse_line(ssh_session session, case 'h': case 'H': if (ll > LLONG_MAX / 60) { - SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); ll = -1; break; } @@ -1058,7 +1108,7 @@ ssh_config_parse_line(ssh_session session, case 'm': case 'M': if (ll > LLONG_MAX / 60) { - SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); ll = -1; break; } @@ -1077,7 +1127,7 @@ ssh_config_parse_line(ssh_session session, break; } if (*endp != '\0') { - SSH_LOG(SSH_LOG_WARN, "Invalid trailing characters after the" + SSH_LOG(SSH_LOG_TRACE, "Invalid trailing characters after the" " rekey limit: %s", endp); break; } @@ -1113,17 +1163,68 @@ ssh_config_parse_line(ssh_session session, } break; case SOC_NA: - SSH_LOG(SSH_LOG_INFO, "Unapplicable option: %s, line: %d", + SSH_LOG(SSH_LOG_TRACE, "Unapplicable option: %s, line: %d", keyword, count); break; case SOC_UNSUPPORTED: - SSH_LOG(SSH_LOG_INFO, "Unsupported option: %s, line: %d", + SSH_LOG(SSH_LOG_RARE, "Unsupported option: %s, line: %d", keyword, count); break; case SOC_UNKNOWN: - SSH_LOG(SSH_LOG_INFO, "Unknown option: %s, line: %d", + SSH_LOG(SSH_LOG_TRACE, "Unknown option: %s, line: %d", keyword, count); break; + case SOC_IDENTITYAGENT: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_IDENTITY_AGENT, p); + } + break; + case SOC_IDENTITIESONLY: + i = ssh_config_get_yesno(&s, -1); + if (i >= 0 && *parsing) { + bool b = i; + ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &b); + } + break; + case SOC_CONTROLMASTER: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + int value = -1; + + if (strcasecmp(p, "auto") == 0) { + value = SSH_CONTROL_MASTER_AUTO; + } else if (strcasecmp(p, "yes") == 0) { + value = SSH_CONTROL_MASTER_YES; + } else if (strcasecmp(p, "no") == 0) { + value = SSH_CONTROL_MASTER_NO; + } else if (strcasecmp(p, "autoask") == 0) { + value = SSH_CONTROL_MASTER_AUTOASK; + } else if (strcasecmp(p, "ask") == 0) { + value = SSH_CONTROL_MASTER_ASK; + } + + if (value != -1) { + ssh_options_set(session, SSH_OPTIONS_CONTROL_MASTER, &value); + } + } + break; + case SOC_CONTROLPATH: + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL) { + SAFE_FREE(x); + return -1; + } + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_CONTROL_PATH, p); + } + break; + case SOC_CERTIFICATE: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, p); + } + break; default: ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d", opcode); @@ -1149,18 +1250,24 @@ int ssh_config_parse_file(ssh_session session, const char *filename) unsigned int count = 0; FILE *f; int parsing, rv; + bool global = 0; f = fopen(filename, "r"); if (f == NULL) { return 0; } + rv = strcmp(filename, GLOBAL_CLIENT_CONFIG); + if (rv == 0) { + global = true; + } + SSH_LOG(SSH_LOG_PACKET, "Reading configuration data from %s", filename); parsing = 1; while (fgets(line, sizeof(line), f)) { count++; - rv = ssh_config_parse_line(session, line, count, &parsing); + rv = ssh_config_parse_line(session, line, count, &parsing, 0, global); if (rv < 0) { fclose(f); return -1; @@ -1170,3 +1277,57 @@ int ssh_config_parse_file(ssh_session session, const char *filename) fclose(f); return 0; } + +/* @brief Parse configuration string and set the options to the given session + * + * @params[in] session The ssh session + * @params[in] input Null terminated string containing the configuration + * + * @returns SSH_OK on successful parsing the configuration string, + * SSH_ERROR on error + */ +int ssh_config_parse_string(ssh_session session, const char *input) +{ + char line[MAX_LINE_SIZE] = {0}; + const char *c = input, *line_start = input; + unsigned int line_num = 0, line_len; + int parsing, rv; + + SSH_LOG(SSH_LOG_DEBUG, "Reading configuration data from string:"); + SSH_LOG(SSH_LOG_DEBUG, "START\n%s\nEND", input); + + parsing = 1; + while (1) { + line_num++; + line_start = c; + c = strchr(line_start, '\n'); + if (c == NULL) { + /* if there is no newline at the end of the string */ + c = strchr(line_start, '\0'); + } + if (c == NULL) { + /* should not happen, would mean a string without trailing '\0' */ + SSH_LOG(SSH_LOG_TRACE, "No trailing '\\0' in config string"); + return SSH_ERROR; + } + line_len = c - line_start; + if (line_len > MAX_LINE_SIZE - 1) { + SSH_LOG(SSH_LOG_TRACE, "Line %u too long: %u characters", + line_num, line_len); + return SSH_ERROR; + } + memcpy(line, line_start, line_len); + line[line_len] = '\0'; + SSH_LOG(SSH_LOG_DEBUG, "Line %u: %s", line_num, line); + rv = ssh_config_parse_line(session, line, line_num, &parsing, 0, false); + if (rv < 0) { + return SSH_ERROR; + } + if (*c == '\0') { + break; + } + c++; + } + + return SSH_OK; +} diff --git a/src/config_parser.c b/src/config_parser.c index 2f91d39f..bd6ab9d7 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -30,9 +30,10 @@ #include "libssh/config_parser.h" #include "libssh/priv.h" +#include "libssh/misc.h" /* Returns the original string after skipping the leading whitespace - * and optional quotes. + * until finding LF. * This is useful in case we need to get the rest of the line (for example * external command). */ @@ -48,15 +49,6 @@ char *ssh_config_get_cmd(char **str) } } - if (*c == '\"') { - for (r = ++c; *c; c++) { - if (*c == '\"') { - *c = '\0'; - goto out; - } - } - } - for (r = c; *c; c++) { if (*c == '\n') { *c = '\0'; @@ -170,12 +162,14 @@ int ssh_config_get_yesno(char **str, int notfound) } int ssh_config_parse_uri(const char *tok, - char **username, - char **hostname, - char **port) + char **username, + char **hostname, + char **port, + bool ignore_port) { char *endp = NULL; long port_n; + int rc; /* Sanitize inputs */ if (username != NULL) { @@ -189,7 +183,7 @@ int ssh_config_parse_uri(const char *tok, } /* Username part (optional) */ - endp = strchr(tok, '@'); + endp = strrchr(tok, '@'); if (endp != NULL) { /* Zero-length username is not valid */ if (tok == endp) { @@ -200,6 +194,10 @@ int ssh_config_parse_uri(const char *tok, if (*username == NULL) { goto error; } + rc = ssh_check_username_syntax(*username); + if (rc != SSH_OK) { + goto error; + } } tok = endp + 1; /* If there is second @ character, this does not look like our URI */ @@ -217,12 +215,17 @@ int ssh_config_parse_uri(const char *tok, if (endp == NULL) { goto error; } - } else { - /* Hostnames or aliases expand to the last colon or to the end */ + } else if (!ignore_port) { + /* Hostnames or aliases expand to the last colon (if port is requested) + * or to the end */ endp = strrchr(tok, ':'); if (endp == NULL) { endp = strchr(tok, '\0'); } + } else { + /* If no port is requested, expand to the end of line + * (to accommodate the IPv6 addresses) */ + endp = strchr(tok, '\0'); } if (tok == endp) { /* Zero-length hostnames are not valid */ @@ -233,6 +236,14 @@ int ssh_config_parse_uri(const char *tok, if (*hostname == NULL) { goto error; } + /* if not an ip, check syntax */ + rc = ssh_is_ipaddr(*hostname); + if (rc == 0) { + rc = ssh_check_hostname_syntax(*hostname); + if (rc != SSH_OK) { + goto error; + } + } } /* Skip also the closing bracket */ if (*endp == ']') { @@ -246,7 +257,7 @@ int ssh_config_parse_uri(const char *tok, /* Verify the port is valid positive number */ port_n = strtol(endp + 1, &port_end, 10); if (port_n < 1 || *port_end != '\0') { - SSH_LOG(SSH_LOG_WARN, "Failed to parse port number." + SSH_LOG(SSH_LOG_TRACE, "Failed to parse port number." " The value '%ld' is invalid or there are some" " trailing characters: '%s'", port_n, port_end); goto error; diff --git a/src/connect.c b/src/connect.c index 252e2c63..dd3bbcf5 100644 --- a/src/connect.c +++ b/src/connect.c @@ -29,6 +29,9 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif /* HAVE_SYS_TIME_H */ #include "libssh/libssh.h" #include "libssh/misc.h" @@ -51,11 +54,6 @@ #define NTDDI_VERSION 0x05010000 /* NTDDI_WINXP */ #endif -#if _MSC_VER >= 1400 -#include <io.h> -#undef close -#define close _close -#endif /* _MSC_VER */ #include <winsock2.h> #include <ws2tcpip.h> @@ -133,7 +131,7 @@ static int getai(const char *host, int port, struct addrinfo **ai) #endif } - if (ssh_is_ipaddr(host)) { + if (ssh_is_ipaddr(host) == 1) { /* this is an IP address */ SSH_LOG(SSH_LOG_PACKET, "host %s matches an IP address", host); hints.ai_flags |= AI_NUMERICHOST; @@ -165,7 +163,7 @@ static int set_tcp_nodelay(socket_t socket) socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, const char *bind_addr, int port) { - socket_t s = -1; + socket_t s = -1, first = -1; int rc; struct addrinfo *ai = NULL; struct addrinfo *itr = NULL; @@ -180,11 +178,13 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, } for (itr = ai; itr != NULL; itr = itr->ai_next) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; /* create socket */ s = socket(itr->ai_family, itr->ai_socktype, itr->ai_protocol); if (s < 0) { ssh_set_error(session, SSH_FATAL, - "Socket create failed: %s", strerror(errno)); + "Socket create failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); continue; } @@ -211,7 +211,8 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, { if (bind(s, bind_itr->ai_addr, bind_itr->ai_addrlen) < 0) { ssh_set_error(session, SSH_FATAL, - "Binding local address: %s", strerror(errno)); + "Binding local address: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); continue; } else { break; @@ -243,7 +244,7 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, if (rc < 0) { ssh_set_error(session, SSH_FATAL, "Failed to set TCP_NODELAY on socket: %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); ssh_connect_socket_close(s); s = -1; continue; @@ -252,11 +253,22 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, errno = 0; rc = connect(s, itr->ai_addr, itr->ai_addrlen); - if (rc == -1 && (errno != 0) && (errno != EINPROGRESS)) { - ssh_set_error(session, SSH_FATAL, - "Failed to connect: %s", strerror(errno)); - ssh_connect_socket_close(s); - s = -1; + if (rc == -1) { + if ((errno != 0) && (errno != EINPROGRESS)) { + ssh_set_error(session, SSH_FATAL, + "Failed to connect: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + ssh_connect_socket_close(s); + s = -1; + } else { + if (first == -1) { + first = s; + } else { /* errno == EINPROGRESS */ + /* save only the first "working" socket */ + ssh_connect_socket_close(s); + s = -1; + } + } continue; } @@ -265,6 +277,12 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, freeaddrinfo(ai); + /* first let's go through all the addresses looking for immediate + * connection, otherwise return the first address without error or error */ + if (s == -1) { + s = first; + } + return s; } @@ -286,14 +304,14 @@ static int ssh_select_cb (socket_t fd, int revents, void *userdata) /** * @brief A wrapper for the select syscall * - * This functions acts more or less like the select(2) syscall.\n + * This function acts more or less like the select(2) syscall.\n * There is no support for writing or exceptions.\n * * @param[in] channels Arrays of channels pointers terminated by a NULL. * It is never rewritten. * - * @param[out] outchannels Arrays of same size that "channels", there is no need - * to initialize it. + * @param[out] outchannels Arrays of the same size as "channels", there is no + * need to initialize it. * * @param[in] maxfd Maximum +1 file descriptor from readfds. * diff --git a/src/connector.c b/src/connector.c index ac841338..21c4d79b 100644 --- a/src/connector.c +++ b/src/connector.c @@ -30,21 +30,11 @@ #include <stdbool.h> #include <sys/stat.h> +#ifndef CHUNKSIZE #define CHUNKSIZE 4096 +#endif -#ifdef _WIN32 -# ifdef HAVE_IO_H -# include <io.h> -# undef open -# define open _open -# undef close -# define close _close -# undef read -# define read _read -# undef unlink -# define unlink _unlink -# endif /* HAVE_IO_H */ -#else +#ifndef _WIN32 # include <sys/types.h> # include <sys/socket.h> #endif @@ -83,7 +73,7 @@ static int ssh_connector_channel_data_cb(ssh_session session, void *userdata); static int ssh_connector_channel_write_wontblock_cb(ssh_session session, ssh_channel channel, - size_t bytes, + uint32_t bytes, void *userdata); static ssize_t ssh_connector_fd_read(ssh_connector connector, void *buffer, @@ -214,7 +204,7 @@ static void ssh_connector_except_channel(ssh_connector connector, /** * @internal * - * @brief Reset the poll events to be followed for each file descriptors. + * @brief Reset the poll events to be followed for each file descriptor. */ static void ssh_connector_reset_pollevents(ssh_connector connector) { @@ -246,14 +236,14 @@ static void ssh_connector_fd_in_cb(ssh_connector connector) uint32_t toread = CHUNKSIZE; ssize_t r; ssize_t w; - int total = 0; + ssize_t total = 0; int rc; SSH_LOG(SSH_LOG_TRACE, "connector POLLIN event for fd %d", connector->in_fd); if (connector->out_wontblock) { if (connector->out_channel != NULL) { - size_t size = ssh_channel_window_size(connector->out_channel); + uint32_t size = ssh_channel_window_size(connector->out_channel); /* Don't attempt reading more than the window */ toread = MIN(size, CHUNKSIZE); @@ -324,35 +314,40 @@ static void ssh_connector_fd_in_cb(ssh_connector connector) /** @internal * @brief Callback called when a poll event is received on an output fd */ -static void ssh_connector_fd_out_cb(ssh_connector connector){ +static void +ssh_connector_fd_out_cb(ssh_connector connector) +{ unsigned char buffer[CHUNKSIZE]; - int r; - int w; - int total = 0; - SSH_LOG(SSH_LOG_TRACE, "connector POLLOUT event for fd %d", connector->out_fd); + ssize_t r; + ssize_t w; + ssize_t total = 0; + SSH_LOG(SSH_LOG_TRACE, "connector POLLOUT event for fd %d", + connector->out_fd); - if(connector->in_available){ - if (connector->in_channel != NULL){ - r = ssh_channel_read_nonblocking(connector->in_channel, buffer, CHUNKSIZE, 0); - if(r == SSH_ERROR){ + if (connector->in_available) { + if (connector->in_channel != NULL) { + r = ssh_channel_read_nonblocking(connector->in_channel, buffer, + CHUNKSIZE, 0); + if (r == SSH_ERROR) { ssh_connector_except_channel(connector, connector->in_channel); return; - } else if(r == 0 && ssh_channel_is_eof(connector->in_channel)){ + } else if (r == 0 && ssh_channel_is_eof(connector->in_channel)) { close(connector->out_fd); connector->out_fd = SSH_INVALID_SOCKET; return; - } else if(r>0) { + } else if (r > 0) { /* loop around write in case the write blocks even for CHUNKSIZE bytes */ - while (total != r){ - w = ssh_connector_fd_write(connector, buffer + total, r - total); - if (w < 0){ + while (total != r) { + w = ssh_connector_fd_write(connector, buffer + total, + r - total); + if (w < 0) { ssh_connector_except(connector, connector->out_fd); return; } total += w; } } - } else if (connector->in_fd != SSH_INVALID_SOCKET){ + } else if (connector->in_fd != SSH_INVALID_SOCKET) { /* fallback on the socket input callback */ connector->out_wontblock = 1; ssh_connector_fd_in_cb(connector); @@ -430,7 +425,7 @@ static int ssh_connector_channel_data_cb(ssh_session session, { ssh_connector connector = userdata; int w; - size_t window; + uint32_t window; (void) session; (void) channel; @@ -444,11 +439,14 @@ static int ssh_connector_channel_data_cb(ssh_session session, } else if (!is_stderr && !(connector->in_flags & SSH_CONNECTOR_STDOUT)) { /* ignore stdout */ return 0; + } else if (len == 0) { + /* ignore empty data */ + return 0; } if (connector->out_wontblock) { if (connector->out_channel != NULL) { - int window_len; + uint32_t window_len; window = ssh_channel_window_size(connector->out_channel); window_len = MIN(window, len); @@ -512,7 +510,7 @@ static int ssh_connector_channel_data_cb(ssh_session session, */ static int ssh_connector_channel_write_wontblock_cb(ssh_session session, ssh_channel channel, - size_t bytes, + uint32_t bytes, void *userdata) { ssh_connector connector = userdata; @@ -524,7 +522,7 @@ static int ssh_connector_channel_write_wontblock_cb(ssh_session session, SSH_LOG(SSH_LOG_TRACE, "Channel write won't block"); if (connector->in_available) { if (connector->in_channel != NULL) { - size_t len = MIN(CHUNKSIZE, bytes); + uint32_t len = MIN(CHUNKSIZE, bytes); r = ssh_channel_read_nonblocking(connector->in_channel, buffer, diff --git a/src/crypto_common.c b/src/crypto_common.c new file mode 100644 index 00000000..5dc883f6 --- /dev/null +++ b/src/crypto_common.c @@ -0,0 +1,36 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2020 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "libssh/crypto.h" + +int secure_memcmp(const void *s1, const void *s2, size_t n) +{ + size_t i; + uint8_t status = 0; + const uint8_t *p1 = s1; + const uint8_t *p2 = s2; + + for (i = 0; i < n; i++) { + status |= (p1[i] ^ p2[i]); + } + + return (status != 0); +} diff --git a/src/curve25519.c b/src/curve25519.c index c13b3604..3f57f25d 100644 --- a/src/curve25519.c +++ b/src/curve25519.c @@ -39,7 +39,7 @@ #include "libssh/pki.h" #include "libssh/bignum.h" -#ifdef HAVE_OPENSSL_X25519 +#ifdef HAVE_LIBCRYPTO #include <openssl/err.h> #endif @@ -59,7 +59,7 @@ static struct ssh_packet_callbacks_struct ssh_curve25519_client_callbacks = { static int ssh_curve25519_init(ssh_session session) { int rc; -#ifdef HAVE_OPENSSL_X25519 +#ifdef HAVE_LIBCRYPTO EVP_PKEY_CTX *pctx = NULL; EVP_PKEY *pkey = NULL; size_t pubkey_len = CURVE25519_PUBKEY_SIZE; @@ -136,7 +136,7 @@ static int ssh_curve25519_init(ssh_session session) crypto_scalarmult_base(session->next_crypto->curve25519_client_pubkey, session->next_crypto->curve25519_privkey); } -#endif /* HAVE_OPENSSL_X25519 */ +#endif /* HAVE_LIBCRYPTO */ return SSH_OK; } @@ -172,11 +172,16 @@ int ssh_client_curve25519_init(ssh_session session) return rc; } +void ssh_client_curve25519_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_curve25519_client_callbacks); +} + static int ssh_curve25519_build_k(ssh_session session) { ssh_curve25519_pubkey k; -#ifdef HAVE_OPENSSL_X25519 +#ifdef HAVE_LIBCRYPTO EVP_PKEY_CTX *pctx = NULL; EVP_PKEY *pkey = NULL, *pubkey = NULL; size_t shared_key_len = sizeof(k); @@ -255,7 +260,7 @@ out: crypto_scalarmult(k, session->next_crypto->curve25519_privkey, session->next_crypto->curve25519_server_pubkey); } -#endif /* HAVE_OPENSSL_X25519 */ +#endif /* HAVE_LIBCRYPTO */ bignum_bin2bn(k, CURVE25519_PUBKEY_SIZE, &session->next_crypto->shared_secret); if (session->next_crypto->shared_secret == NULL) { @@ -285,7 +290,7 @@ static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply){ (void)type; (void)user; - ssh_packet_remove_callbacks(session, &ssh_curve25519_client_callbacks); + ssh_client_curve25519_remove_callbacks(session); pubkey_blob = ssh_buffer_get_ssh_string(packet); if (pubkey_blob == NULL) { @@ -330,16 +335,10 @@ static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply){ } /* Send the MSG_NEWKEYS */ - if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { - goto error; - } - - rc=ssh_packet_send(session); + rc = ssh_packet_send_newkeys(session); if (rc == SSH_ERROR) { goto error; } - - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; return SSH_PACKET_USED; @@ -377,12 +376,12 @@ void ssh_server_curve25519_init(ssh_session session){ */ static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init){ /* ECDH keys */ - ssh_string q_c_string; - ssh_string q_s_string; + ssh_string q_c_string = NULL; + ssh_string q_s_string = NULL; ssh_string server_pubkey_blob = NULL; - /* SSH host keys (rsa,dsa,ecdsa) */ - ssh_key privkey; + /* SSH host keys (rsa, ed25519 and ecdsa) */ + ssh_key privkey = NULL; enum ssh_digest_e digest = SSH_DIGEST_AUTO; ssh_string sig_blob = NULL; int rc; @@ -402,15 +401,14 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init){ SSH_FATAL, "Incorrect size for server Curve25519 public key: %zu", ssh_string_len(q_c_string)); - SSH_STRING_FREE(q_c_string); goto error; } memcpy(session->next_crypto->curve25519_client_pubkey, ssh_string_data(q_c_string), CURVE25519_PUBKEY_SIZE); SSH_STRING_FREE(q_c_string); - /* Build server's keypair */ + /* Build server's key pair */ rc = ssh_curve25519_init(session); if (rc != SSH_OK) { ssh_set_error(session, SSH_FATAL, "Failed to generate curve25519 keys"); @@ -460,12 +458,17 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init){ /* add ecdh public key */ q_s_string = ssh_string_new(CURVE25519_PUBKEY_SIZE); if (q_s_string == NULL) { + ssh_set_error_oom(session); goto error; } - ssh_string_fill(q_s_string, - session->next_crypto->curve25519_server_pubkey, - CURVE25519_PUBKEY_SIZE); + rc = ssh_string_fill(q_s_string, + session->next_crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Could not copy public key"); + goto error; + } rc = ssh_buffer_add_ssh_string(session->out_buffer, q_s_string); SSH_STRING_FREE(q_s_string); @@ -487,27 +490,24 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init){ goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEX_ECDH_REPLY sent"); + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_ECDH_REPLY sent"); rc = ssh_packet_send(session); if (rc == SSH_ERROR) { return SSH_ERROR; } - /* Send the MSG_NEWKEYS */ - rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); - if (rc < 0) { - goto error; - } - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; - rc = ssh_packet_send(session); + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); if (rc == SSH_ERROR) { goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); return SSH_PACKET_USED; error: + SSH_STRING_FREE(q_c_string); + SSH_STRING_FREE(q_s_string); ssh_buffer_reinit(session->out_buffer); session->session_state=SSH_SESSION_STATE_ERROR; return SSH_PACKET_USED; diff --git a/src/dh-gex.c b/src/dh-gex.c index 9bf0546a..4e59073f 100644 --- a/src/dh-gex.c +++ b/src/dh-gex.c @@ -37,7 +37,7 @@ #include "libssh/buffer.h" #include "libssh/session.h" -/* Minimum, recommanded and maximum size of DH group */ +/* Minimum, recommended and maximum size of DH group */ #define DH_PMIN 2048 #define DH_PREQ 2048 #define DH_PMAX 8192 @@ -108,11 +108,15 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group) bignum pmin1 = NULL, one = NULL; bignum_CTX ctx = bignum_ctx_new(); bignum modulus = NULL, generator = NULL; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum pubkey; +#else + bignum pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ (void) type; (void) user; - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEX_DH_GEX_GROUP received"); + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_DH_GEX_GROUP received"); if (bignum_ctx_invalid(ctx)) { goto error; @@ -212,6 +216,9 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group) if (rc != SSH_OK) { goto error; } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ session->dh_handshake_state = DH_STATE_INIT_SENT; @@ -229,6 +236,9 @@ error: bignum_safe_free(generator); bignum_safe_free(one); bignum_safe_free(pmin1); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ if(!bignum_ctx_invalid(ctx)) { bignum_ctx_free(ctx); } @@ -238,6 +248,11 @@ error: return SSH_PACKET_USED; } +void ssh_client_dhgex_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_dhgex_client_callbacks); +} + static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply) { struct ssh_crypto_struct *crypto=session->next_crypto; @@ -246,9 +261,9 @@ static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply) bignum server_pubkey = NULL; (void)type; (void)user; - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEX_DH_GEX_REPLY received"); + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_DH_GEX_REPLY received"); - ssh_packet_remove_callbacks(session, &ssh_dhgex_client_callbacks); + ssh_client_dhgex_remove_callbacks(session); rc = ssh_buffer_unpack(packet, "SBS", &pubkey_blob, &server_pubkey, @@ -263,6 +278,8 @@ static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply) bignum_safe_free(server_pubkey); goto error; } + /* The ownership was passed to the crypto structure */ + server_pubkey = NULL; rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); SSH_STRING_FREE(pubkey_blob); @@ -280,19 +297,15 @@ static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply) } /* Send the MSG_NEWKEYS */ - if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { - goto error; - } - - rc = ssh_packet_send(session); + rc = ssh_packet_send_newkeys(session); if (rc == SSH_ERROR) { goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; return SSH_PACKET_USED; error: + SSH_STRING_FREE(pubkey_blob); ssh_dh_cleanup(session->next_crypto); session->session_state = SSH_SESSION_STATE_ERROR; @@ -364,9 +377,9 @@ static bool dhgroup_better_size(uint32_t pmin, * @brief returns 1 with 1/n probability * @returns 1 on with P(1/n), 0 with P(n-1/n). */ -static bool invn_chance(int n) +static bool invn_chance(size_t n) { - uint32_t nounce = 0; + size_t nounce = 0; int ok; ok = ssh_get_random(&nounce, sizeof(nounce), 0); @@ -422,7 +435,7 @@ static int ssh_retrieve_dhgroup_file(FILE *moduli, if (rc == EOF) { break; } - SSH_LOG(SSH_LOG_INFO, "Invalid moduli entry line %zu", line); + SSH_LOG(SSH_LOG_DEBUG, "Invalid moduli entry line %zu", line); do { firstbyte = getc(moduli); } while(firstbyte != '\n' && firstbyte != EOF); @@ -460,14 +473,14 @@ static int ssh_retrieve_dhgroup_file(FILE *moduli, } } if (*best_size != 0) { - SSH_LOG(SSH_LOG_INFO, + SSH_LOG(SSH_LOG_DEBUG, "Selected %zu bits modulus out of %zu candidates in %zu lines", *best_size, best_nlines - 1, line); } else { - SSH_LOG(SSH_LOG_WARNING, - "No moduli found for [%u:%u:%u]", + SSH_LOG(SSH_LOG_DEBUG, + "No moduli found for [%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", pmin, pn, pmax); @@ -486,7 +499,8 @@ static int ssh_retrieve_dhgroup_file(FILE *moduli, * @param[out] g generator * @return SSH_OK on success, SSH_ERROR otherwise. */ -static int ssh_retrieve_dhgroup(uint32_t pmin, +static int ssh_retrieve_dhgroup(char *moduli_file, + uint32_t pmin, uint32_t pn, uint32_t pmax, size_t *size, @@ -505,12 +519,17 @@ static int ssh_retrieve_dhgroup(uint32_t pmin, return ssh_fallback_group(pmax, p, g); } - moduli = fopen(MODULI_FILE, "r"); + if (moduli_file != NULL) + moduli = fopen(moduli_file, "r"); + else + moduli = fopen(MODULI_FILE, "r"); + if (moduli == NULL) { - SSH_LOG(SSH_LOG_WARNING, + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + SSH_LOG(SSH_LOG_DEBUG, "Unable to open moduli file: %s", - strerror(errno)); - return ssh_fallback_group(pmax, p, g); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return ssh_fallback_group(pmax, p, g); } *size = 0; @@ -602,12 +621,12 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request) ssh_set_error_invalid(session); goto error; } - SSH_LOG(SSH_LOG_INFO, "dh-gex: DHGEX_REQUEST[%u:%u:%u]", pmin, pn, pmax); + SSH_LOG(SSH_LOG_DEBUG, "dh-gex: DHGEX_REQUEST[%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", pmin, pn, pmax); if (pmin > pn || pn > pmax || pn > DH_PMAX || pmax < DH_PMIN) { ssh_set_error(session, SSH_FATAL, - "Invalid dh-gex arguments [%u:%u:%u]", + "Invalid dh-gex arguments [%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", pmin, pn, pmax); @@ -624,7 +643,8 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request) pn = pmin; } } - rc = ssh_retrieve_dhgroup(pmin, + rc = ssh_retrieve_dhgroup(session->opts.moduli_file, + pmin, pn, pmax, &size, @@ -633,7 +653,7 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request) if (rc == SSH_ERROR) { ssh_set_error(session, SSH_FATAL, - "Couldn't find DH group for [%u:%u:%u]", + "Couldn't find DH group for [%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", pmin, pn, pmax); @@ -25,6 +25,8 @@ #include "config.h" +#include <stdio.h> + #include "libssh/priv.h" #include "libssh/crypto.h" #include "libssh/buffer.h" @@ -309,7 +311,11 @@ static struct ssh_packet_callbacks_struct ssh_dh_client_callbacks = { */ int ssh_client_dh_init(ssh_session session){ struct ssh_crypto_struct *crypto = session->next_crypto; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum pubkey; +#else + bignum pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ int rc; rc = ssh_dh_init_common(crypto); @@ -330,6 +336,9 @@ int ssh_client_dh_init(ssh_session session){ if (rc != SSH_OK) { goto error; } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif /* register the packet callbacks */ ssh_packet_set_callbacks(session, &ssh_dh_client_callbacks); @@ -338,10 +347,18 @@ int ssh_client_dh_init(ssh_session session){ rc = ssh_packet_send(session); return rc; error: +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif ssh_dh_cleanup(crypto); return SSH_ERROR; } +void ssh_client_dh_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_dh_client_callbacks); +} + SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ struct ssh_crypto_struct *crypto=session->next_crypto; ssh_string pubkey_blob = NULL; @@ -351,7 +368,7 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ (void)type; (void)user; - ssh_packet_remove_callbacks(session, &ssh_dh_client_callbacks); + ssh_client_dh_remove_callbacks(session); rc = ssh_buffer_unpack(packet, "SBS", &pubkey_blob, &server_pubkey, &crypto->dh_server_signature); @@ -361,6 +378,7 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, DH_SERVER_KEYPAIR, NULL, server_pubkey); if (rc != SSH_OK) { + SSH_STRING_FREE(pubkey_blob); bignum_safe_free(server_pubkey); goto error; } @@ -369,7 +387,7 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ if (rc != 0) { goto error; } - + rc = ssh_dh_compute_shared_secret(session->next_crypto->dh_ctx, DH_CLIENT_KEYPAIR, DH_SERVER_KEYPAIR, &session->next_crypto->shared_secret); @@ -380,16 +398,10 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ } /* Send the MSG_NEWKEYS */ - if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { - goto error; - } - - rc=ssh_packet_send(session); + rc = ssh_packet_send_newkeys(session); if (rc == SSH_ERROR) { goto error; } - - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; return SSH_PACKET_USED; error: @@ -435,7 +447,11 @@ int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet) ssh_string sig_blob = NULL; ssh_string pubkey_blob = NULL; bignum client_pubkey; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum server_pubkey; +#else + bignum server_pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ int packet_type; int rc; @@ -515,6 +531,9 @@ int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet) sig_blob); SSH_STRING_FREE(sig_blob); SSH_STRING_FREE(pubkey_blob); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(server_pubkey); +#endif if(rc != SSH_OK) { ssh_set_error_oom(session); ssh_buffer_reinit(session->out_buffer); @@ -526,20 +545,20 @@ int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet) } SSH_LOG(SSH_LOG_DEBUG, "Sent KEX_DH_[GEX]_REPLY"); - if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { - ssh_buffer_reinit(session->out_buffer); - goto error; - } session->dh_handshake_state=DH_STATE_NEWKEYS_SENT; - if (ssh_packet_send(session) == SSH_ERROR) { + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { goto error; } - SSH_LOG(SSH_LOG_PACKET, "SSH_MSG_NEWKEYS sent"); return SSH_OK; error: SSH_STRING_FREE(sig_blob); SSH_STRING_FREE(pubkey_blob); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(server_pubkey); +#endif session->session_state = SSH_SESSION_STATE_ERROR; ssh_dh_cleanup(session->next_crypto); @@ -639,7 +658,7 @@ ssh_key ssh_dh_get_current_server_publickey(ssh_session session) return session->current_crypto->server_pubkey; } -/* Caller need to free the blob */ +/* Caller needs to free the blob */ int ssh_dh_get_current_server_publickey_blob(ssh_session session, ssh_string *pubkey_blob) { @@ -653,7 +672,7 @@ ssh_key ssh_dh_get_next_server_publickey(ssh_session session) return session->next_crypto->server_pubkey; } -/* Caller need to free the blob */ +/* Caller needs to free the blob */ int ssh_dh_get_next_server_publickey_blob(ssh_session session, ssh_string *pubkey_blob) { @@ -682,7 +701,7 @@ static char *ssh_get_b64_unpadded(const unsigned char *hash, size_t len) char *b64_unpadded = NULL; size_t k; - b64_padded = (char *)bin_to_base64(hash, (int)len); + b64_padded = (char *)bin_to_base64(hash, len); if (b64_padded == NULL) { return NULL; } @@ -698,7 +717,7 @@ static char *ssh_get_b64_unpadded(const unsigned char *hash, size_t len) * @brief Get a hash as a human-readable hex- or base64-string. * * This gets an allocated fingerprint hash. If it is a SHA sum, it will - * return an unpadded base64 strings. If it is a MD5 sum, it will return hex + * return an unpadded base64 string. If it is a MD5 sum, it will return a hex * string. Either way, the output is prepended by the hash-type. * * @warning Do NOT use MD5 or SHA1! Those hash functions are being deprecated. @@ -710,7 +729,8 @@ static char *ssh_get_b64_unpadded(const unsigned char *hash, size_t len) * * @param len Length of the buffer to convert. * - * @return Returns the allocated fingerprint hash or NULL on error. + * @return Returns the allocated fingerprint hash or NULL on error. The caller + * needs to free the memory using ssh_string_free_char(). * * @see ssh_string_free_char() */ diff --git a/src/dh_crypto.c b/src/dh_crypto.c index 3b3495c1..9ff7ad3c 100644 --- a/src/dh_crypto.c +++ b/src/dh_crypto.c @@ -30,6 +30,13 @@ #include "openssl/crypto.h" #include "openssl/dh.h" #include "libcrypto-compat.h" +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include <openssl/evp.h> +#include <openssl/params.h> +#include <openssl/core_names.h> +#include <openssl/param_build.h> +#include <openssl/err.h> +#endif /* OPENSSL_VERSION_NUMBER */ extern bignum ssh_dh_generator; extern bignum ssh_dh_group1; @@ -38,13 +45,21 @@ extern bignum ssh_dh_group16; extern bignum ssh_dh_group18; struct dh_ctx { +#if OPENSSL_VERSION_NUMBER < 0x30000000L DH *keypair[2]; +#else + EVP_PKEY *keypair[2]; +#endif /* OPENSSL_VERSION_NUMBER */ }; void ssh_dh_debug_crypto(struct ssh_crypto_struct *c) { #ifdef DEBUG_CRYPTO +#if OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum x = NULL, y = NULL, e = NULL, f = NULL; +#else + bignum x = NULL, y = NULL, e = NULL, f = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ ssh_dh_keypair_get_keys(c->dh_ctx, DH_CLIENT_KEYPAIR, &x, &e); ssh_dh_keypair_get_keys(c->dh_ctx, DH_SERVER_KEYPAIR, &y, &f); @@ -52,6 +67,12 @@ void ssh_dh_debug_crypto(struct ssh_crypto_struct *c) ssh_print_bignum("y", y); ssh_print_bignum("e", e); ssh_print_bignum("f", f); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(x); + bignum_safe_free(y); + bignum_safe_free(e); + bignum_safe_free(f); +#endif /* OPENSSL_VERSION_NUMBER */ ssh_log_hexdump("Session server cookie", c->server_kex.cookie, 16); ssh_log_hexdump("Session client cookie", c->client_kex.cookie, 16); @@ -59,9 +80,10 @@ void ssh_dh_debug_crypto(struct ssh_crypto_struct *c) #else (void)c; /* UNUSED_PARAM */ -#endif +#endif /* DEBUG_CRYPTO */ } +#if OPENSSL_VERSION_NUMBER < 0x30000000L int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, const_bignum *priv, const_bignum *pub) { @@ -70,22 +92,76 @@ int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, (ctx->keypair[peer] == NULL)) { return SSH_ERROR; } + DH_get0_key(ctx->keypair[peer], pub, priv); + + if (priv && (*priv == NULL || bignum_num_bits(*priv) == 0)) { + return SSH_ERROR; + } + if (pub && (*pub == NULL || bignum_num_bits(*pub) == 0)) { + return SSH_ERROR; + } + + return SSH_OK; +} + +#else +/* If set *priv and *pub should be initialized + * to NULL before calling this function*/ +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + bignum *priv, bignum *pub) +{ + int rc; + if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || + ((priv == NULL) && (pub == NULL)) || (ctx == NULL) || + (ctx->keypair[peer] == NULL)) { + return SSH_ERROR; + } + + if (priv) { + rc = EVP_PKEY_get_bn_param(ctx->keypair[peer], + OSSL_PKEY_PARAM_PRIV_KEY, + priv); + if (rc != 1) { + return SSH_ERROR; + } + } + if (pub) { + rc = EVP_PKEY_get_bn_param(ctx->keypair[peer], + OSSL_PKEY_PARAM_PUB_KEY, + pub); + if (rc != 1) { + return SSH_ERROR; + } + } if (priv && (*priv == NULL || bignum_num_bits(*priv) == 0)) { + if (pub && (*pub != NULL && bignum_num_bits(*pub) != 0)) { + bignum_safe_free(*pub); + *pub = NULL; + } return SSH_ERROR; } if (pub && (*pub == NULL || bignum_num_bits(*pub) == 0)) { + if (priv) { + bignum_safe_free(*priv); + *priv = NULL; + } return SSH_ERROR; } return SSH_OK; } +#endif /* OPENSSL_VERSION_NUMBER */ int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, - const bignum priv, const bignum pub) + bignum priv, bignum pub) { - bignum priv_key = NULL; - bignum pub_key = NULL; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + int rc; + OSSL_PARAM *params = NULL, *out_params = NULL, *merged_params = NULL; + OSSL_PARAM_BLD *param_bld = NULL; + EVP_PKEY_CTX *evp_ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || ((priv == NULL) && (pub == NULL)) || (ctx == NULL) || @@ -93,17 +169,85 @@ int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, return SSH_ERROR; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + (void)DH_set0_key(ctx->keypair[peer], pub, priv); + + return SSH_OK; +#else + rc = EVP_PKEY_todata(ctx->keypair[peer], EVP_PKEY_KEYPAIR, &out_params); + if (rc != 1) { + return SSH_ERROR; + } + + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) { + rc = SSH_ERROR; + goto out; + } + + evp_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, ctx->keypair[peer], NULL); + if (evp_ctx == NULL) { + rc = SSH_ERROR; + goto out; + } + + rc = EVP_PKEY_fromdata_init(evp_ctx); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } + if (priv) { - priv_key = priv; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PRIV_KEY, priv); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } } if (pub) { - pub_key = pub; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PUB_KEY, pub); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } } - (void)DH_set0_key(ctx->keypair[peer], pub_key, priv_key); - return SSH_OK; + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + rc = SSH_ERROR; + goto out; + } + OSSL_PARAM_BLD_free(param_bld); + + merged_params = OSSL_PARAM_merge(out_params, params); + if (merged_params == NULL) { + rc = SSH_ERROR; + goto out; + } + + rc = EVP_PKEY_fromdata(evp_ctx, + &(ctx->keypair[peer]), + EVP_PKEY_PUBLIC_KEY, + merged_params); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } + + rc = SSH_OK; +out: + bignum_safe_free(priv); + bignum_safe_free(pub); + EVP_PKEY_CTX_free(evp_ctx); + OSSL_PARAM_free(out_params); + OSSL_PARAM_free(params); + OSSL_PARAM_free(merged_params); + + return rc; +#endif /* OPENSSL_VERSION_NUMBER */ } +#if OPENSSL_VERSION_NUMBER < 0x30000000L int ssh_dh_get_parameters(struct dh_ctx *ctx, const_bignum *modulus, const_bignum *generator) { @@ -113,18 +257,51 @@ int ssh_dh_get_parameters(struct dh_ctx *ctx, DH_get0_pqg(ctx->keypair[0], modulus, NULL, generator); return SSH_OK; } +#else +int ssh_dh_get_parameters(struct dh_ctx *ctx, + bignum *modulus, bignum *generator) +{ + int rc; + + if (ctx == NULL || ctx->keypair[0] == NULL) { + return SSH_ERROR; + } + + rc = EVP_PKEY_get_bn_param(ctx->keypair[0], OSSL_PKEY_PARAM_FFC_P, (BIGNUM**)modulus); + if (rc != 1) { + return SSH_ERROR; + } + rc = EVP_PKEY_get_bn_param(ctx->keypair[0], OSSL_PKEY_PARAM_FFC_G, (BIGNUM**)generator); + if (rc != 1) { + bignum_safe_free(*modulus); + return SSH_ERROR; + } + + return SSH_OK; +} +#endif /* OPENSSL_VERSION_NUMBER */ int ssh_dh_set_parameters(struct dh_ctx *ctx, const bignum modulus, const bignum generator) { size_t i; int rc; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM *params = NULL; + OSSL_PARAM_BLD *param_bld = NULL; + EVP_PKEY_CTX *evp_ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ if ((ctx == NULL) || (modulus == NULL) || (generator == NULL)) { return SSH_ERROR; } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + evp_ctx = EVP_PKEY_CTX_new_from_name(NULL, "DHX", NULL); +#endif + for (i = 0; i < 2; i++) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L bignum p = NULL; bignum g = NULL; @@ -146,16 +323,78 @@ int ssh_dh_set_parameters(struct dh_ctx *ctx, rc = SSH_ERROR; goto done; } +#else + param_bld = OSSL_PARAM_BLD_new(); + + if (param_bld == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_P, modulus); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_G, generator); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + OSSL_PARAM_BLD_free(param_bld); + rc = SSH_ERROR; + goto done; + } + OSSL_PARAM_BLD_free(param_bld); + + rc = EVP_PKEY_fromdata_init(evp_ctx); + if (rc != 1) { + OSSL_PARAM_free(params); + rc = SSH_ERROR; + goto done; + } + + /* make sure to invalidate existing keys */ + EVP_PKEY_free(ctx->keypair[i]); + ctx->keypair[i] = NULL; + + rc = EVP_PKEY_fromdata(evp_ctx, + &(ctx->keypair[i]), + EVP_PKEY_KEY_PARAMETERS, + params); + if (rc != 1) { + OSSL_PARAM_free(params); + rc = SSH_ERROR; + goto done; + } + + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ } rc = SSH_OK; +#if OPENSSL_VERSION_NUMBER < 0x30000000L done: if (rc != SSH_OK) { DH_free(ctx->keypair[0]); DH_free(ctx->keypair[1]); + } +#else +done: + EVP_PKEY_CTX_free(evp_ctx); + + if (rc != SSH_OK) { + EVP_PKEY_free(ctx->keypair[0]); + EVP_PKEY_free(ctx->keypair[1]); + } +#endif /* OPENSSL_VERSION_NUMBER */ + if (rc != SSH_OK) { ctx->keypair[0] = NULL; ctx->keypair[1] = NULL; } + return rc; } @@ -202,8 +441,13 @@ int ssh_dh_init_common(struct ssh_crypto_struct *crypto) void ssh_dh_cleanup(struct ssh_crypto_struct *crypto) { if (crypto->dh_ctx != NULL) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L DH_free(crypto->dh_ctx->keypair[0]); DH_free(crypto->dh_ctx->keypair[1]); +#else + EVP_PKEY_free(crypto->dh_ctx->keypair[0]); + EVP_PKEY_free(crypto->dh_ctx->keypair[1]); +#endif /* OPENSSL_VERSION_NUMBER */ free(crypto->dh_ctx); crypto->dh_ctx = NULL; } @@ -212,7 +456,8 @@ void ssh_dh_cleanup(struct ssh_crypto_struct *crypto) /** @internal * @brief generates a secret DH parameter of at least DH_SECURITY_BITS * security as well as the corresponding public key. - * @param[out] parms a dh_ctx that will hold the new keys. + * + * @param[out] params a dh_ctx that will hold the new keys. * @param peer Select either client or server key storage. Valid values are: * DH_CLIENT_KEYPAIR or DH_SERVER_KEYPAIR * @@ -221,14 +466,43 @@ void ssh_dh_cleanup(struct ssh_crypto_struct *crypto) int ssh_dh_keypair_gen_keys(struct dh_ctx *dh_ctx, int peer) { int rc; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_CTX *evp_ctx = NULL; +#endif if ((dh_ctx == NULL) || (dh_ctx->keypair[peer] == NULL)) { return SSH_ERROR; } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L rc = DH_generate_key(dh_ctx->keypair[peer]); if (rc != 1) { return SSH_ERROR; } +#else + evp_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, dh_ctx->keypair[peer], NULL); + if (evp_ctx == NULL) { + return SSH_ERROR; + } + + rc = EVP_PKEY_keygen_init(evp_ctx); + if (rc != 1) { + EVP_PKEY_CTX_free(evp_ctx); + return SSH_ERROR; + } + + rc = EVP_PKEY_generate(evp_ctx, &(dh_ctx->keypair[peer])); + if (rc != 1) { + EVP_PKEY_CTX_free(evp_ctx); + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate DH: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + EVP_PKEY_CTX_free(evp_ctx); +#endif /* OPENSSL_VERSION_NUMBER */ + return SSH_OK; } @@ -247,8 +521,14 @@ int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, bignum *dest) { unsigned char *kstring = NULL; + int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum pub_key = NULL; - int klen, rc; + int klen; +#else + size_t klen; + EVP_PKEY_CTX *evp_ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ if ((dh_ctx == NULL) || (dh_ctx->keypair[local] == NULL) || @@ -256,6 +536,7 @@ int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, return SSH_ERROR; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L kstring = malloc(DH_size(dh_ctx->keypair[local])); if (kstring == NULL) { rc = SSH_ERROR; @@ -273,6 +554,43 @@ int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, rc = SSH_ERROR; goto done; } +#else + evp_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, dh_ctx->keypair[local], NULL); + + rc = EVP_PKEY_derive_init(evp_ctx); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + + rc = EVP_PKEY_derive_set_peer(evp_ctx, dh_ctx->keypair[remote]); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to set peer key: %s", + ERR_error_string(ERR_get_error(), NULL)); + rc = SSH_ERROR; + goto done; + } + + /* getting the size of the secret */ + rc = EVP_PKEY_derive(evp_ctx, kstring, &klen); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + + kstring = malloc(klen); + if (kstring == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = EVP_PKEY_derive(evp_ctx, kstring, &klen); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } +#endif /* OPENSSL_VERSION_NUMBER */ *dest = BN_bin2bn(kstring, klen, NULL); if (*dest == NULL) { @@ -282,6 +600,9 @@ int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, rc = SSH_OK; done: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_CTX_free(evp_ctx); +#endif free(kstring); return rc; } diff --git a/src/dh_key.c b/src/dh_key.c index bda54b17..20d24a31 100644 --- a/src/dh_key.c +++ b/src/dh_key.c @@ -289,8 +289,10 @@ void ssh_dh_cleanup(struct ssh_crypto_struct *crypto) /** @internal * @brief generates a secret DH parameter of at least DH_SECURITY_BITS * security as well as the corresponding public key. - * @param[out] parms a dh_kex paramters structure with preallocated bignum + * + * @param[out] params a dh_kex parameters structure with preallocated bignum * where to store the parameters + * * @return SSH_OK on success, SSH_ERROR on error */ int ssh_dh_keypair_gen_keys(struct dh_ctx *dh_ctx, int peer) @@ -43,6 +43,11 @@ struct ssh_packet_callbacks_struct ssh_ecdh_client_callbacks = { .user = NULL }; +void ssh_client_ecdh_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_ecdh_client_callbacks); +} + /** @internal * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back * a SSH_MSG_NEWKEYS @@ -55,7 +60,7 @@ SSH_PACKET_CALLBACK(ssh_packet_client_ecdh_reply){ (void)type; (void)user; - ssh_packet_remove_callbacks(session, &ssh_ecdh_client_callbacks); + ssh_client_ecdh_remove_callbacks(session); pubkey_blob = ssh_buffer_get_ssh_string(packet); if (pubkey_blob == NULL) { ssh_set_error(session,SSH_FATAL, "No public key in packet"); @@ -88,16 +93,10 @@ SSH_PACKET_CALLBACK(ssh_packet_client_ecdh_reply){ } /* Send the MSG_NEWKEYS */ - if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { - goto error; - } - - rc=ssh_packet_send(session); + rc = ssh_packet_send_newkeys(session); if (rc == SSH_ERROR) { goto error; } - - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; return SSH_PACKET_USED; diff --git a/src/ecdh_crypto.c b/src/ecdh_crypto.c index a1de27fd..603c293b 100644 --- a/src/ecdh_crypto.c +++ b/src/ecdh_crypto.c @@ -30,15 +30,27 @@ #ifdef HAVE_ECDH #include <openssl/ecdh.h> - +#if OPENSSL_VERSION_NUMBER < 0x30000000L #define NISTP256 NID_X9_62_prime256v1 #define NISTP384 NID_secp384r1 #define NISTP521 NID_secp521r1 +#else +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/param_build.h> +#include <openssl/params.h> +#include <openssl/core_names.h> +#include "libcrypto-compat.h" +#endif /* OPENSSL_VERSION_NUMBER */ /** @internal * @brief Map the given key exchange enum value to its curve name. */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L static int ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { +#else +static const char *ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { +#endif /* OPENSSL_VERSION_NUMBER */ if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256) { return NISTP256; } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP384) { @@ -46,88 +58,191 @@ static int ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP521) { return NISTP521; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L return SSH_ERROR; +#else + return NULL; +#endif } -/** @internal - * @brief Starts ecdh-sha2-nistp256 key exchange +/* @internal + * @brief Generate ECDH key pair for ecdh key exchange and store it in the + * session->next_crypto structure */ -int ssh_client_ecdh_init(ssh_session session){ - EC_KEY *key; - const EC_GROUP *group; - const EC_POINT *pubkey; - ssh_string client_pubkey; - int curve; - int len; - int rc; - bignum_CTX ctx = BN_CTX_new(); +static ssh_string ssh_ecdh_generate(ssh_session session) +{ + ssh_string pubkey_string = NULL; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const EC_POINT *point = NULL; + const EC_GROUP *group = NULL; + EC_KEY *key = NULL; + int curve; +#else + EC_POINT *point = NULL; + EC_GROUP *group = NULL; + const char *curve = NULL; + EVP_PKEY *key = NULL; + OSSL_PARAM *out_params = NULL; + const OSSL_PARAM *pubkey_param = NULL; + const void *pubkey = NULL; + size_t pubkey_len; + int nid; + int rc; +#endif /* OPENSSL_VERSION_NUMBER */ - rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT); - if (rc < 0) { - BN_CTX_free(ctx); - return SSH_ERROR; - } + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + if (curve == SSH_ERROR) { + SSH_LOG(SSH_LOG_TRACE, "Failed to get curve name"); + return NULL; + } - curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); - if (curve == SSH_ERROR) { - BN_CTX_free(ctx); - return SSH_ERROR; - } + key = EC_KEY_new_by_curve_name(curve); +#else + if (curve == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to get curve name"); + return NULL; + } - key = EC_KEY_new_by_curve_name(curve); - if (key == NULL) { - BN_CTX_free(ctx); - return SSH_ERROR; - } - group = EC_KEY_get0_group(key); + key = EVP_EC_gen(curve); +#endif /* OPENSSL_VERSION_NUMBER */ + if (key == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to generate key"); + return NULL; + } - EC_KEY_generate_key(key); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + group = EC_KEY_get0_group(key); - pubkey=EC_KEY_get0_public_key(key); - len = EC_POINT_point2oct(group,pubkey,POINT_CONVERSION_UNCOMPRESSED, - NULL,0,ctx); + EC_KEY_generate_key(key); - client_pubkey = ssh_string_new(len); - if (client_pubkey == NULL) { - BN_CTX_free(ctx); - EC_KEY_free(key); - return SSH_ERROR; - } + point = EC_KEY_get0_public_key(key); - EC_POINT_point2oct(group,pubkey,POINT_CONVERSION_UNCOMPRESSED, - ssh_string_data(client_pubkey),len,ctx); - BN_CTX_free(ctx); + pubkey_string = pki_key_make_ecpoint_string(group, point); +#else + rc = EVP_PKEY_todata(key, EVP_PKEY_PUBLIC_KEY, &out_params); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to export public key"); + EVP_PKEY_free(key); + return NULL; + } - rc = ssh_buffer_add_ssh_string(session->out_buffer,client_pubkey); - if (rc < 0) { - EC_KEY_free(key); - SSH_STRING_FREE(client_pubkey); - return SSH_ERROR; - } + pubkey_param = OSSL_PARAM_locate_const(out_params, OSSL_PKEY_PARAM_PUB_KEY); + if (pubkey_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to find public key"); + EVP_PKEY_free(key); + OSSL_PARAM_free(out_params); + return NULL; + } - session->next_crypto->ecdh_privkey = key; - session->next_crypto->ecdh_client_pubkey = client_pubkey; + rc = OSSL_PARAM_get_octet_string_ptr(pubkey_param, + (const void**)&pubkey, + &pubkey_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to read public key"); + OSSL_PARAM_free(out_params); + EVP_PKEY_free(key); + return NULL; + } - /* register the packet callbacks */ - ssh_packet_set_callbacks(session, &ssh_ecdh_client_callbacks); - session->dh_handshake_state = DH_STATE_INIT_SENT; + /* Convert the data to low-level representation */ + nid = pki_key_ecgroup_name_to_nid(curve); + group = EC_GROUP_new_by_curve_name_ex(NULL, NULL, nid); + if (group == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Could not create group: %s", + ERR_error_string(ERR_get_error(), NULL)); + OSSL_PARAM_free(out_params); + EVP_PKEY_free(key); + return NULL; + } + point = EC_POINT_new(group); + if (point == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Could not create point: %s", + ERR_error_string(ERR_get_error(), NULL)); + EC_GROUP_free(group); + OSSL_PARAM_free(out_params); + EVP_PKEY_free(key); + return NULL; + } + rc = EC_POINT_oct2point(group, point, pubkey, pubkey_len, NULL); + OSSL_PARAM_free(out_params); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to export public key"); + EC_GROUP_free(group); + EC_POINT_free(point); + EVP_PKEY_free(key); + return NULL; + } - rc = ssh_packet_send(session); + pubkey_string = pki_key_make_ecpoint_string(group, point); + EC_GROUP_free(group); + EC_POINT_free(point); +#endif /* OPENSSL_VERSION_NUMBER */ + if (pubkey_string == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to convert public key"); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(key); +#else + EVP_PKEY_free(key); +#endif /* OPENSSL_VERSION_NUMBER */ + return NULL; + } + session->next_crypto->ecdh_privkey = key; + return pubkey_string; +} - return rc; +/** @internal + * @brief Starts ecdh-sha2-nistp256 key exchange + */ +int ssh_client_ecdh_init(ssh_session session) +{ + ssh_string client_pubkey = NULL; + int rc; + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT); + if (rc < 0) { + return SSH_ERROR; + } + + client_pubkey = ssh_ecdh_generate(session); + if (client_pubkey == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, client_pubkey); + if (rc < 0) { + ssh_string_free(client_pubkey); + return SSH_ERROR; + } + + session->next_crypto->ecdh_client_pubkey = client_pubkey; + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_ecdh_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + + return rc; } -int ecdh_build_k(ssh_session session) { - const EC_GROUP *group = EC_KEY_get0_group(session->next_crypto->ecdh_privkey); - EC_POINT *pubkey; - void *buffer; +int ecdh_build_k(ssh_session session) +{ + struct ssh_crypto_struct *next_crypto = session->next_crypto; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const EC_GROUP *group = EC_KEY_get0_group(next_crypto->ecdh_privkey); + EC_POINT *pubkey = NULL; + void *buffer = NULL; int rc; int len = (EC_GROUP_get_degree(group) + 7) / 8; bignum_CTX ctx = bignum_ctx_new(); if (ctx == NULL) { return -1; } - pubkey = EC_POINT_new(group); if (pubkey == NULL) { bignum_ctx_free(ctx); @@ -137,14 +252,14 @@ int ecdh_build_k(ssh_session session) { if (session->server) { rc = EC_POINT_oct2point(group, pubkey, - ssh_string_data(session->next_crypto->ecdh_client_pubkey), - ssh_string_len(session->next_crypto->ecdh_client_pubkey), + ssh_string_data(next_crypto->ecdh_client_pubkey), + ssh_string_len(next_crypto->ecdh_client_pubkey), ctx); } else { rc = EC_POINT_oct2point(group, pubkey, - ssh_string_data(session->next_crypto->ecdh_server_pubkey), - ssh_string_len(session->next_crypto->ecdh_server_pubkey), + ssh_string_data(next_crypto->ecdh_server_pubkey), + ssh_string_len(next_crypto->ecdh_server_pubkey), ctx); } bignum_ctx_free(ctx); @@ -162,7 +277,7 @@ int ecdh_build_k(ssh_session session) { rc = ECDH_compute_key(buffer, len, pubkey, - session->next_crypto->ecdh_privkey, + next_crypto->ecdh_privkey, NULL); EC_POINT_clear_free(pubkey); if (rc <= 0) { @@ -170,23 +285,150 @@ int ecdh_build_k(ssh_session session) { return -1; } - bignum_bin2bn(buffer, len, &session->next_crypto->shared_secret); + bignum_bin2bn(buffer, len, &next_crypto->shared_secret); free(buffer); - if (session->next_crypto->shared_secret == NULL) { - EC_KEY_free(session->next_crypto->ecdh_privkey); - session->next_crypto->ecdh_privkey = NULL; +#else + const char *curve = NULL; + EVP_PKEY *pubkey = NULL; + void *secret = NULL; + size_t secret_len; + int rc; + ssh_string peer_pubkey = NULL; + OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); + EVP_PKEY_CTX *dh_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, + next_crypto->ecdh_privkey, + NULL); + + if (dh_ctx == NULL || param_bld == NULL) { + ssh_set_error_oom(session); + EVP_PKEY_CTX_free(dh_ctx); + OSSL_PARAM_BLD_free(param_bld); + return -1; + } + + rc = EVP_PKEY_derive_init(dh_ctx); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not init PKEY derive: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + OSSL_PARAM_BLD_free(param_bld); return -1; } - EC_KEY_free(session->next_crypto->ecdh_privkey); - session->next_crypto->ecdh_privkey = NULL; + + if (session->server) { + peer_pubkey = next_crypto->ecdh_client_pubkey; + } else { + peer_pubkey = next_crypto->ecdh_server_pubkey; + } + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, + OSSL_PKEY_PARAM_PUB_KEY, + ssh_string_data(peer_pubkey), + ssh_string_len(peer_pubkey)); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not push the pub key: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + OSSL_PARAM_BLD_free(param_bld); + return -1; + } + curve = ecdh_kex_type_to_curve(next_crypto->kex_type); + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, + OSSL_PKEY_PARAM_GROUP_NAME, + (char *)curve, + strlen(curve)); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not push the group name: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + OSSL_PARAM_BLD_free(param_bld); + return -1; + } + + rc = evp_build_pkey("EC", param_bld, &pubkey, EVP_PKEY_PUBLIC_KEY); + OSSL_PARAM_BLD_free(param_bld); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Could not build the pkey: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + rc = EVP_PKEY_derive_set_peer(dh_ctx, pubkey); + EVP_PKEY_free(pubkey); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not set peer pubkey: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + /* get the max length of the secret */ + rc = EVP_PKEY_derive(dh_ctx, NULL, &secret_len); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not set peer pubkey: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + secret = malloc(secret_len); + if (secret == NULL) { + ssh_set_error_oom(session); + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + rc = EVP_PKEY_derive(dh_ctx, secret, &secret_len); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not derive shared key: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + free(secret); + return -1; + } + + EVP_PKEY_CTX_free(dh_ctx); + + bignum_bin2bn(secret, secret_len, &next_crypto->shared_secret); + free(secret); +#endif /* OPENSSL_VERSION_NUMBER */ + if (next_crypto->shared_secret == NULL) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(next_crypto->ecdh_privkey); +#else + EVP_PKEY_free(next_crypto->ecdh_privkey); +#endif /* OPENSSL_VERSION_NUMBER */ + next_crypto->ecdh_privkey = NULL; + return -1; + } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(next_crypto->ecdh_privkey); +#else + EVP_PKEY_free(next_crypto->ecdh_privkey); +#endif /* OPENSSL_VERSION_NUMBER */ + next_crypto->ecdh_privkey = NULL; #ifdef DEBUG_CRYPTO ssh_log_hexdump("Session server cookie", - session->next_crypto->server_kex.cookie, 16); + next_crypto->server_kex.cookie, 16); ssh_log_hexdump("Session client cookie", - session->next_crypto->client_kex.cookie, 16); - ssh_print_bignum("Shared secret key", session->next_crypto->shared_secret); -#endif + next_crypto->client_kex.cookie, 16); + ssh_print_bignum("Shared secret key", next_crypto->shared_secret); +#endif /* DEBUG_CRYPTO */ return 0; } @@ -196,78 +438,36 @@ int ecdh_build_k(ssh_session session) { /** @brief Handle a SSH_MSG_KEXDH_INIT packet (server) and send a * SSH_MSG_KEXDH_REPLY */ -SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ +SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init) +{ /* ECDH keys */ - ssh_string q_c_string; - ssh_string q_s_string; - EC_KEY *ecdh_key; - const EC_GROUP *group; - const EC_POINT *ecdh_pubkey; - bignum_CTX ctx; - /* SSH host keys (rsa,dsa,ecdsa) */ + ssh_string q_c_string = NULL; + ssh_string q_s_string = NULL; + /* SSH host keys (rsa, ed25519 and ecdsa) */ ssh_key privkey; enum ssh_digest_e digest = SSH_DIGEST_AUTO; ssh_string sig_blob = NULL; ssh_string pubkey_blob = NULL; - int curve; - int len; int rc; (void)type; (void)user; + SSH_LOG(SSH_LOG_TRACE, "Processing SSH_MSG_KEXDH_INIT"); + ssh_packet_remove_callbacks(session, &ssh_ecdh_server_callbacks); /* Extract the client pubkey from the init packet */ q_c_string = ssh_buffer_get_ssh_string(packet); if (q_c_string == NULL) { - ssh_set_error(session,SSH_FATAL, "No Q_C ECC point in packet"); + ssh_set_error(session, SSH_FATAL, "No Q_C ECC point in packet"); goto error; } session->next_crypto->ecdh_client_pubkey = q_c_string; - /* Build server's keypair */ - - ctx = BN_CTX_new(); - - curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); - if (curve == SSH_ERROR) { - BN_CTX_free(ctx); - return SSH_ERROR; - } - - ecdh_key = EC_KEY_new_by_curve_name(curve); - if (ecdh_key == NULL) { - ssh_set_error_oom(session); - BN_CTX_free(ctx); - goto error; - } - - group = EC_KEY_get0_group(ecdh_key); - EC_KEY_generate_key(ecdh_key); - - ecdh_pubkey = EC_KEY_get0_public_key(ecdh_key); - len = EC_POINT_point2oct(group, - ecdh_pubkey, - POINT_CONVERSION_UNCOMPRESSED, - NULL, - 0, - ctx); - - q_s_string = ssh_string_new(len); + q_s_string = ssh_ecdh_generate(session); if (q_s_string == NULL) { - EC_KEY_free(ecdh_key); - BN_CTX_free(ctx); goto error; } - EC_POINT_point2oct(group, - ecdh_pubkey, - POINT_CONVERSION_UNCOMPRESSED, - ssh_string_data(q_s_string), - len, - ctx); - BN_CTX_free(ctx); - - session->next_crypto->ecdh_privkey = ecdh_key; session->next_crypto->ecdh_server_pubkey = q_s_string; /* build k and session_id */ @@ -317,24 +517,18 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEXDH_REPLY sent"); + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEXDH_REPLY sent"); rc = ssh_packet_send(session); if (rc == SSH_ERROR) { goto error; } - /* Send the MSG_NEWKEYS */ - rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); - if (rc < 0) { - goto error; - } - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; - rc = ssh_packet_send(session); - if (rc == SSH_ERROR){ + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); return SSH_PACKET_USED; error: diff --git a/src/ecdh_gcrypt.c b/src/ecdh_gcrypt.c index d9c41bf9..86d15f72 100644 --- a/src/ecdh_gcrypt.c +++ b/src/ecdh_gcrypt.c @@ -271,7 +271,7 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ ssh_string q_s_string; gcry_sexp_t param = NULL; gcry_sexp_t key = NULL; - /* SSH host keys (rsa,dsa,ecdsa) */ + /* SSH host keys (rsa, ed25519 and ecdsa) */ ssh_key privkey; enum ssh_digest_e digest = SSH_DIGEST_AUTO; ssh_string sig_blob = NULL; @@ -295,7 +295,7 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ } session->next_crypto->ecdh_client_pubkey = q_c_string; - /* Build server's keypair */ + /* Build server's key pair */ err = gcry_sexp_build(¶m, NULL, "(genkey(ecdh(curve %s) (flags transient-key)))", curve); if (err) { @@ -366,23 +366,19 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ goto out; } - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEXDH_REPLY sent"); + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEXDH_REPLY sent"); rc = ssh_packet_send(session); if (rc != SSH_OK) { goto out; } - + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; /* Send the MSG_NEWKEYS */ - rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); - if (rc != SSH_OK) { + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { goto out; } - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; - rc = ssh_packet_send(session); - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); - out: gcry_sexp_release(param); gcry_sexp_release(key); diff --git a/src/ecdh_mbedcrypto.c b/src/ecdh_mbedcrypto.c index 718f1522..1d9c8f36 100644 --- a/src/ecdh_mbedcrypto.c +++ b/src/ecdh_mbedcrypto.c @@ -34,6 +34,7 @@ #include <mbedtls/ecdh.h> #include <mbedtls/ecp.h> +#include "mbedcrypto-compat.h" #ifdef HAVE_ECDH @@ -54,6 +55,10 @@ int ssh_client_ecdh_init(ssh_session session) mbedtls_ecp_group grp; int rc; mbedtls_ecp_group_id curve; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + mbedtls_ecp_keypair *ecdh_privkey = NULL; + + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); if (curve == MBEDTLS_ECP_DP_NONE) { @@ -70,7 +75,9 @@ int ssh_client_ecdh_init(ssh_session session) return SSH_ERROR; } - mbedtls_ecp_keypair_init(session->next_crypto->ecdh_privkey); + ecdh_privkey = session->next_crypto->ecdh_privkey; + + mbedtls_ecp_keypair_init(ecdh_privkey); mbedtls_ecp_group_init(&grp); rc = mbedtls_ecp_group_load(&grp, curve); @@ -80,10 +87,10 @@ int ssh_client_ecdh_init(ssh_session session) } rc = mbedtls_ecp_gen_keypair(&grp, - &session->next_crypto->ecdh_privkey->d, - &session->next_crypto->ecdh_privkey->Q, - mbedtls_ctr_drbg_random, - ssh_get_mbedtls_ctr_drbg_context()); + &ecdh_privkey->MBEDTLS_PRIVATE(d), + &ecdh_privkey->MBEDTLS_PRIVATE(Q), + mbedtls_ctr_drbg_random, + ctr_drbg); if (rc != 0) { rc = SSH_ERROR; @@ -91,7 +98,7 @@ int ssh_client_ecdh_init(ssh_session session) } client_pubkey = make_ecpoint_string(&grp, - &session->next_crypto->ecdh_privkey->Q); + &ecdh_privkey->MBEDTLS_PRIVATE(Q)); if (client_pubkey == NULL) { rc = SSH_ERROR; goto out; @@ -124,6 +131,10 @@ int ecdh_build_k(ssh_session session) mbedtls_ecp_point pubkey; int rc; mbedtls_ecp_group_id curve; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + mbedtls_ecp_keypair *ecdh_privkey = NULL; + + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); if (curve == MBEDTLS_ECP_DP_NONE) { @@ -162,19 +173,20 @@ int ecdh_build_k(ssh_session session) mbedtls_mpi_init(session->next_crypto->shared_secret); + ecdh_privkey = session->next_crypto->ecdh_privkey; rc = mbedtls_ecdh_compute_shared(&grp, - session->next_crypto->shared_secret, - &pubkey, - &session->next_crypto->ecdh_privkey->d, - mbedtls_ctr_drbg_random, - ssh_get_mbedtls_ctr_drbg_context()); + session->next_crypto->shared_secret, + &pubkey, + &ecdh_privkey->MBEDTLS_PRIVATE(d), + mbedtls_ctr_drbg_random, + ctr_drbg); if (rc != 0) { rc = SSH_ERROR; goto out; } out: - mbedtls_ecp_keypair_free(session->next_crypto->ecdh_privkey); + mbedtls_ecp_keypair_free(ecdh_privkey); SAFE_FREE(session->next_crypto->ecdh_privkey); mbedtls_ecp_group_free(&grp); mbedtls_ecp_point_free(&pubkey); @@ -187,6 +199,8 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ ssh_string q_c_string = NULL; ssh_string q_s_string = NULL; mbedtls_ecp_group grp; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + mbedtls_ecp_keypair *ecdh_privkey = NULL; ssh_key privkey = NULL; enum ssh_digest_e digest = SSH_DIGEST_AUTO; ssh_string sig_blob = NULL; @@ -214,10 +228,14 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ return SSH_ERROR; } + ecdh_privkey = session->next_crypto->ecdh_privkey; + session->next_crypto->ecdh_client_pubkey = q_c_string; + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); + mbedtls_ecp_group_init(&grp); - mbedtls_ecp_keypair_init(session->next_crypto->ecdh_privkey); + mbedtls_ecp_keypair_init(ecdh_privkey); rc = mbedtls_ecp_group_load(&grp, curve); if (rc != 0) { @@ -226,16 +244,16 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ } rc = mbedtls_ecp_gen_keypair(&grp, - &session->next_crypto->ecdh_privkey->d, - &session->next_crypto->ecdh_privkey->Q, - mbedtls_ctr_drbg_random, - ssh_get_mbedtls_ctr_drbg_context()); + &ecdh_privkey->MBEDTLS_PRIVATE(d), + &ecdh_privkey->MBEDTLS_PRIVATE(Q), + mbedtls_ctr_drbg_random, + ctr_drbg); if (rc != 0) { rc = SSH_ERROR; goto out; } - q_s_string = make_ecpoint_string(&grp, &session->next_crypto->ecdh_privkey->Q); + q_s_string = make_ecpoint_string(&grp, &ecdh_privkey->MBEDTLS_PRIVATE(Q)); if (q_s_string == NULL) { rc = SSH_ERROR; goto out; @@ -293,23 +311,20 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ goto out; } - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEXDH_REPLY sent"); + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEXDH_REPLY sent"); rc = ssh_packet_send(session); if (rc != SSH_OK) { rc = SSH_ERROR; goto out; } - rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); - if (rc < 0) { - rc = SSH_ERROR; + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { goto out; } - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; - rc = ssh_packet_send(session); - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); - out: mbedtls_ecp_group_free(&grp); if (rc == SSH_ERROR) { diff --git a/src/error.c b/src/error.c index 22180407..3f8d78cd 100644 --- a/src/error.c +++ b/src/error.c @@ -29,7 +29,7 @@ #include "libssh/session.h" /** - * @defgroup libssh_error The SSH error functions. + * @defgroup libssh_error The SSH error functions * @ingroup libssh * * Functions for error handling. @@ -63,8 +63,8 @@ void _ssh_set_error(void *error, va_end(va); err->error.error_code = code; - if (ssh_get_log_level() >= SSH_LOG_WARN) { - ssh_log_function(SSH_LOG_WARN, + if (ssh_get_log_level() == SSH_LOG_TRACE) { + ssh_log_function(SSH_LOG_TRACE, function, err->error.error_buffer); } @@ -142,7 +142,7 @@ const char *ssh_get_error(void *error) { * SSH_FATAL A fatal error occurred. This could be an unexpected * disconnection\n * - * Other error codes are internal but can be considered same than + * Other error codes are internal but can be considered the same as * SSH_FATAL. */ int ssh_get_error_code(void *error) { diff --git a/src/external/bcrypt_pbkdf.c b/src/external/bcrypt_pbkdf.c index 2208654d..85f4be47 100644 --- a/src/external/bcrypt_pbkdf.c +++ b/src/external/bcrypt_pbkdf.c @@ -42,7 +42,7 @@ * function with the following modifications: * 1. The input password and salt are preprocessed with SHA512. * 2. The output length is expanded to 256 bits. - * 3. Subsequently the magic string to be encrypted is lengthened and modifed + * 3. Subsequently the magic string to be encrypted is lengthened and modified * to "OxychromaticBlowfishSwatDynamite" * 4. The hash function is defined to perform 64 rounds of initial state * expansion. (More rounds are performed by iterating the hash.) @@ -63,9 +63,8 @@ #define BCRYPT_HASHSIZE (BCRYPT_BLOCKS * 4) static void -bcrypt_hash(uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *out) +bcrypt_hash(ssh_blf_ctx *state, uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *out) { - ssh_blf_ctx state; uint8_t ciphertext[BCRYPT_HASHSIZE] = "OxychromaticBlowfishSwatDynamite"; uint32_t cdata[BCRYPT_BLOCKS]; @@ -74,11 +73,11 @@ bcrypt_hash(uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *out) uint16_t shalen = SHA512_DIGEST_LENGTH; /* key expansion */ - Blowfish_initstate(&state); - Blowfish_expandstate(&state, sha2salt, shalen, sha2pass, shalen); + Blowfish_initstate(state); + Blowfish_expandstate(state, sha2salt, shalen, sha2pass, shalen); for (i = 0; i < 64; i++) { - Blowfish_expand0state(&state, sha2salt, shalen); - Blowfish_expand0state(&state, sha2pass, shalen); + Blowfish_expand0state(state, sha2salt, shalen); + Blowfish_expand0state(state, sha2pass, shalen); } /* encryption */ @@ -87,7 +86,7 @@ bcrypt_hash(uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *out) cdata[i] = Blowfish_stream2word(ciphertext, sizeof(ciphertext), &j); for (i = 0; i < 64; i++) - ssh_blf_enc(&state, cdata, BCRYPT_BLOCKS/2); + ssh_blf_enc(state, cdata, BCRYPT_BLOCKS/2); /* copy out */ for (i = 0; i < BCRYPT_BLOCKS; i++) { @@ -100,7 +99,6 @@ bcrypt_hash(uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *out) /* zap */ explicit_bzero(ciphertext, sizeof(ciphertext)); explicit_bzero(cdata, sizeof(cdata)); - ZERO_STRUCT(state); } int @@ -115,6 +113,7 @@ bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltl size_t i, j, amt, stride; uint32_t count; size_t origkeylen = keylen; + ssh_blf_ctx *state; SHA512CTX ctx; /* nothing crazy */ @@ -130,6 +129,12 @@ bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltl memcpy(countsalt, salt, saltlen); + state = malloc(sizeof(*state)); + if (state == NULL) { + free(countsalt); + return -1; + } + /* collapse password */ ctx = sha512_init(); sha512_update(ctx, pass, passlen); @@ -147,7 +152,7 @@ bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltl sha512_update(ctx, countsalt, saltlen + 4); sha512_final(sha2salt, ctx); - bcrypt_hash(sha2pass, sha2salt, tmpout); + bcrypt_hash(state, sha2pass, sha2salt, tmpout); memcpy(out, tmpout, sizeof(out)); for (i = 1; i < rounds; i++) { @@ -155,13 +160,13 @@ bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltl ctx = sha512_init(); sha512_update(ctx, tmpout, sizeof(tmpout)); sha512_final(sha2salt, ctx); - bcrypt_hash(sha2pass, sha2salt, tmpout); + bcrypt_hash(state, sha2pass, sha2salt, tmpout); for (j = 0; j < sizeof(out); j++) out[j] ^= tmpout[j]; } /* - * pbkdf2 deviation: ouput the key material non-linearly. + * pbkdf2 deviation: output the key material non-linearly. */ amt = MIN(amt, keylen); for (i = 0; i < amt; i++) { @@ -176,6 +181,9 @@ bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltl /* zap */ explicit_bzero(out, sizeof(out)); + explicit_bzero(state, sizeof(*state)); + + free(state); free(countsalt); return 0; diff --git a/src/external/ed25519.c b/src/external/ed25519.c index 8cd58591..41b5f289 100644 --- a/src/external/ed25519.c +++ b/src/external/ed25519.c @@ -77,8 +77,8 @@ static void get_hram(unsigned char *hram, } -int crypto_sign_ed25519_keypair(unsigned char *pk, - unsigned char *sk) +int crypto_sign_ed25519_keypair(ed25519_pubkey pk, + ed25519_privkey sk) { sc25519 scsk; ge25519 gepk; @@ -114,7 +114,7 @@ int crypto_sign_ed25519(unsigned char *sm, uint64_t *smlen, const unsigned char *m, uint64_t mlen, - const unsigned char *sk) + const ed25519_privkey sk) { sc25519 sck, scs, scsk; ge25519 ger; @@ -177,7 +177,7 @@ int crypto_sign_ed25519_open(unsigned char *m, uint64_t *mlen, const unsigned char *sm, uint64_t smlen, - const unsigned char *pk) + const ed25519_pubkey pk) { unsigned int i; int ret; diff --git a/src/gcrypt_missing.c b/src/gcrypt_missing.c index e931ec5b..21a63a9b 100644 --- a/src/gcrypt_missing.c +++ b/src/gcrypt_missing.c @@ -55,7 +55,7 @@ char *ssh_gcry_bn2dec(bignum bn) { size = gcry_mpi_get_nbits(bn) * 3; rsize = size / 10 + size / 1000 + 2; - ret = malloc(rsize + 1); + ret = gcry_malloc(rsize + 1); if (ret == NULL) { return NULL; } diff --git a/src/getpass.c b/src/getpass.c index 99627665..6be33c77 100644 --- a/src/getpass.c +++ b/src/getpass.c @@ -44,7 +44,8 @@ * * @return 1 on success, 0 on error. */ -static int ssh_gets(const char *prompt, char *buf, size_t len, int verify) { +static int ssh_gets(const char *prompt, char *buf, size_t len, int verify) +{ char *tmp; char *ptr = NULL; int ok = 0; @@ -121,7 +122,8 @@ int ssh_getpass(const char *prompt, char *buf, size_t len, int echo, - int verify) { + int verify) +{ HANDLE h; DWORD mode = 0; int ok; @@ -213,7 +215,8 @@ int ssh_getpass(const char *prompt, char *buf, size_t len, int echo, - int verify) { + int verify) +{ struct termios attr; struct termios old_attr; int ok = 0; @@ -255,7 +258,11 @@ int ssh_getpass(const char *prompt, /* disable nonblocking I/O */ if (fd & O_NDELAY) { - fcntl(0, F_SETFL, fd & ~O_NDELAY); + ok = fcntl(0, F_SETFL, fd & ~O_NDELAY); + if (ok < 0) { + perror("fcntl"); + return -1; + } } ok = ssh_gets(prompt, buf, len, verify); @@ -267,7 +274,11 @@ int ssh_getpass(const char *prompt, /* close fd */ if (fd & O_NDELAY) { - fcntl(0, F_SETFL, fd); + ok = fcntl(0, F_SETFL, fd); + if (ok < 0) { + perror("fcntl"); + return -1; + } } if (!ok) { diff --git a/src/getrandom_crypto.c b/src/getrandom_crypto.c new file mode 100644 index 00000000..df8bd19f --- /dev/null +++ b/src/getrandom_crypto.c @@ -0,0 +1,64 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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" + +#include "libssh/crypto.h" +#include <openssl/rand.h> + +/** + * @addtogroup libssh_misc + * + * @{ + */ + +/** + * @brief Get random bytes + * + * Make sure to always check the return code of this function! + * + * @param[in] where The buffer to fill with random bytes + * + * @param[in] len The size of the buffer to fill. + * + * @param[in] strong Use a strong or private RNG source. + * + * @return 1 on success, 0 on error. + */ +int +ssh_get_random(void *where, int len, int strong) +{ +#ifdef HAVE_OPENSSL_RAND_PRIV_BYTES + if (strong) { + /* Returns -1 when not supported, 0 on error, 1 on success */ + return !!RAND_priv_bytes(where, len); + } +#else + (void)strong; +#endif /* HAVE_RAND_PRIV_BYTES */ + + /* Returns -1 when not supported, 0 on error, 1 on success */ + return !!RAND_bytes(where, len); +} + +/** + * @} + */ diff --git a/src/getrandom_gcrypt.c b/src/getrandom_gcrypt.c new file mode 100644 index 00000000..da726405 --- /dev/null +++ b/src/getrandom_gcrypt.c @@ -0,0 +1,38 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * Copyright (C) 2016 g10 Code GmbH + * + * 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" + +#include "libssh/crypto.h" +#include <gcrypt.h> + +int +ssh_get_random(void *where, int len, int strong) +{ + /* variable not used in gcrypt */ + (void)strong; + + /* not using GCRY_VERY_STRONG_RANDOM which is a bit overkill */ + gcry_randomize(where, len, GCRY_STRONG_RANDOM); + + return 1; +} diff --git a/src/getrandom_mbedcrypto.c b/src/getrandom_mbedcrypto.c new file mode 100644 index 00000000..7e87b6a6 --- /dev/null +++ b/src/getrandom_mbedcrypto.c @@ -0,0 +1,52 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk <juraj.vijtiuk@sartura.hr> + * + * 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" + +#include "libssh/crypto.h" +#include "mbedcrypto-compat.h" + +mbedtls_ctr_drbg_context ssh_mbedtls_ctr_drbg; + +int +ssh_mbedtls_random(void *where, int len, int strong) +{ + int rc = 0; + if (strong) { + mbedtls_ctr_drbg_set_prediction_resistance(&ssh_mbedtls_ctr_drbg, + MBEDTLS_CTR_DRBG_PR_ON); + rc = mbedtls_ctr_drbg_random(&ssh_mbedtls_ctr_drbg, where, len); + mbedtls_ctr_drbg_set_prediction_resistance(&ssh_mbedtls_ctr_drbg, + MBEDTLS_CTR_DRBG_PR_OFF); + } else { + rc = mbedtls_ctr_drbg_random(&ssh_mbedtls_ctr_drbg, where, len); + } + + return !rc; +} + +int +ssh_get_random(void *where, int len, int strong) +{ + return ssh_mbedtls_random(where, len, strong); +} diff --git a/src/gssapi.c b/src/gssapi.c index 488df582..5254d38d 100644 --- a/src/gssapi.c +++ b/src/gssapi.c @@ -68,15 +68,15 @@ struct ssh_gssapi_struct{ /** @internal * @initializes a gssapi context for authentication */ -static int ssh_gssapi_init(ssh_session session){ +static int ssh_gssapi_init(ssh_session session) +{ if (session->gssapi != NULL) return SSH_OK; - session->gssapi = malloc(sizeof(struct ssh_gssapi_struct)); - if(!session->gssapi){ + session->gssapi = calloc(1, sizeof(struct ssh_gssapi_struct)); + if (session->gssapi == NULL) { ssh_set_error_oom(session); return SSH_ERROR; } - ZERO_STRUCTP(session->gssapi); session->gssapi->server_creds = GSS_C_NO_CREDENTIAL; session->gssapi->client_creds = GSS_C_NO_CREDENTIAL; session->gssapi->ctx = GSS_C_NO_CONTEXT; @@ -87,7 +87,8 @@ static int ssh_gssapi_init(ssh_session session){ /** @internal * @frees a gssapi context */ -static void ssh_gssapi_free(ssh_session session){ +static void ssh_gssapi_free(ssh_session session) +{ OM_uint32 min; if (session->gssapi == NULL) return; @@ -114,7 +115,8 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token){ * @brief sends a SSH_MSG_USERAUTH_GSSAPI_RESPONSE packet * @param[in] oid the OID that was selected for authentication */ -static int ssh_gssapi_send_response(ssh_session session, ssh_string oid){ +static int ssh_gssapi_send_response(ssh_session session, ssh_string oid) +{ if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) < 0 || ssh_buffer_add_ssh_string(session->out_buffer,oid) < 0) { ssh_set_error_oom(session); @@ -184,8 +186,11 @@ out: /** @internal * @brief handles an user authentication using GSSAPI */ -int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n_oid, ssh_string *oids){ - char service_name[]="host"; +int +ssh_gssapi_handle_userauth(ssh_session session, const char *user, + uint32_t n_oid, ssh_string *oids) +{ + char service_name[] = "host"; gss_buffer_desc name_buf; gss_name_t server_name; /* local server fqdn */ OM_uint32 maj_stat, min_stat; @@ -218,11 +223,12 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n maj_stat = gss_indicate_mechs(&min_stat, &supported); if (maj_stat != GSS_S_COMPLETE) { - SSH_LOG(SSH_LOG_WARNING, "indicate mecks %d, %d", maj_stat, min_stat); - ssh_gssapi_log_error(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_DEBUG, "indicate mecks %d, %d", maj_stat, min_stat); + ssh_gssapi_log_error(SSH_LOG_DEBUG, "indicate mechs", maj_stat, min_stat); + gss_release_oid_set(&min_stat, &both_supported); return SSH_ERROR; } @@ -240,7 +246,7 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n continue; } if(len < 2 || oid_s[0] != SSH_OID_TAG || ((size_t)oid_s[1]) != len - 2){ - SSH_LOG(SSH_LOG_WARNING,"GSSAPI: received invalid OID"); + SSH_LOG(SSH_LOG_TRACE,"GSSAPI: received invalid OID"); continue; } oid.elements = &oid_s[2]; @@ -253,25 +259,28 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n } gss_release_oid_set(&min_stat, &supported); if (oid_count == 0){ - SSH_LOG(SSH_LOG_PROTOCOL,"GSSAPI: no OID match"); + SSH_LOG(SSH_LOG_DEBUG,"GSSAPI: no OID match"); ssh_auth_reply_default(session, 0); gss_release_oid_set(&min_stat, &both_supported); return SSH_OK; } /* from now we have room for context */ - if (ssh_gssapi_init(session) == SSH_ERROR) + if (ssh_gssapi_init(session) == SSH_ERROR) { + gss_release_oid_set(&min_stat, &both_supported); return SSH_ERROR; + } name_buf.value = service_name; name_buf.length = strlen(name_buf.value) + 1; maj_stat = gss_import_name(&min_stat, &name_buf, (gss_OID) GSS_C_NT_HOSTBASED_SERVICE, &server_name); if (maj_stat != GSS_S_COMPLETE) { - SSH_LOG(SSH_LOG_WARNING, "importing name %d, %d", maj_stat, min_stat); - ssh_gssapi_log_error(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_DEBUG, "importing name %d, %d", maj_stat, min_stat); + ssh_gssapi_log_error(SSH_LOG_DEBUG, "importing name", maj_stat, min_stat); + gss_release_oid_set(&min_stat, &both_supported); return -1; } @@ -282,8 +291,8 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n gss_release_oid_set(&min_stat, &both_supported); if (maj_stat != GSS_S_COMPLETE) { - SSH_LOG(SSH_LOG_WARNING, "error acquiring credentials %d, %d", maj_stat, min_stat); - ssh_gssapi_log_error(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_TRACE, "error acquiring credentials %d, %d", maj_stat, min_stat); + ssh_gssapi_log_error(SSH_LOG_TRACE, "acquiring creds", maj_stat, min_stat); @@ -291,7 +300,7 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n return SSH_ERROR; } - SSH_LOG(SSH_LOG_PROTOCOL, "acquiring credentials %d, %d", maj_stat, min_stat); + SSH_LOG(SSH_LOG_DEBUG, "acquiring credentials %d, %d", maj_stat, min_stat); /* finding which OID from client we selected */ for (i=0 ; i< n_oid ; ++i){ @@ -302,7 +311,7 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n continue; } if(len < 2 || oid_s[0] != SSH_OID_TAG || ((size_t)oid_s[1]) != len - 2){ - SSH_LOG(SSH_LOG_WARNING,"GSSAPI: received invalid OID"); + SSH_LOG(SSH_LOG_TRACE,"GSSAPI: received invalid OID"); continue; } oid.elements = &oid_s[2]; @@ -317,6 +326,7 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n session->gssapi->mech.elements = malloc(oid.length); if (session->gssapi->mech.elements == NULL){ ssh_set_error_oom(session); + gss_release_oid_set(&min_stat, &selected); return SSH_ERROR; } memcpy(session->gssapi->mech.elements, oid.elements, oid.length); @@ -327,17 +337,19 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n return ssh_gssapi_send_response(session, oids[i]); } -static char *ssh_gssapi_name_to_char(gss_name_t name){ +static char *ssh_gssapi_name_to_char(gss_name_t name) +{ gss_buffer_desc buffer; OM_uint32 maj_stat, min_stat; char *ptr; maj_stat = gss_display_name(&min_stat, name, &buffer, NULL); - ssh_gssapi_log_error(SSH_LOG_WARNING, + ssh_gssapi_log_error(SSH_LOG_DEBUG, "converting name", maj_stat, min_stat); ptr = malloc(buffer.length + 1); if (ptr == NULL) { + gss_release_buffer(&min_stat, &buffer); return NULL; } memcpy(ptr, buffer.value, buffer.length); @@ -407,7 +419,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server){ maj_stat = gss_accept_sec_context(&min_stat, &session->gssapi->ctx, session->gssapi->server_creds, &input_token, input_bindings, &client_name, NULL /*mech_oid*/, &output_token, &ret_flags, NULL /*time*/, &session->gssapi->client_creds); - ssh_gssapi_log_error(SSH_LOG_PROTOCOL, + ssh_gssapi_log_error(SSH_LOG_DEBUG, "accepting token", maj_stat, min_stat); @@ -417,10 +429,11 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server){ session->gssapi->canonic_user = ssh_gssapi_name_to_char(client_name); } if (GSS_ERROR(maj_stat)){ - ssh_gssapi_log_error(SSH_LOG_WARNING, + ssh_gssapi_log_error(SSH_LOG_DEBUG, "Gssapi error", maj_stat, min_stat); + gss_release_buffer(&min_stat, &output_token); ssh_auth_reply_default(session,0); ssh_gssapi_free(session); session->gssapi=NULL; @@ -431,13 +444,23 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server){ hexa = ssh_get_hexa(output_token.value, output_token.length); SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s",hexa); SAFE_FREE(hexa); - ssh_buffer_pack(session->out_buffer, - "bdP", - SSH2_MSG_USERAUTH_GSSAPI_TOKEN, - output_token.length, - (size_t)output_token.length, output_token.value); + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_USERAUTH_GSSAPI_TOKEN, + output_token.length, + (size_t)output_token.length, output_token.value); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + ssh_auth_reply_default(session, 0); + ssh_gssapi_free(session); + session->gssapi = NULL; + return SSH_PACKET_USED; + } ssh_packet_send(session); } + + gss_release_buffer(&min_stat, &output_token); + if(maj_stat == GSS_S_COMPLETE){ session->gssapi->state = SSH_GSSAPI_STATE_RCV_MIC; } @@ -465,8 +488,8 @@ static ssh_buffer ssh_gssapi_build_mic(ssh_session session) rc = ssh_buffer_pack(mic_buffer, "dPbsss", - crypto->digest_len, - (size_t)crypto->digest_len, crypto->session_id, + crypto->session_id_len, + crypto->session_id_len, crypto->session_id, SSH2_MSG_USERAUTH_REQUEST, session->gssapi->user, "ssh-connection", @@ -524,7 +547,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic) mic_token_buf.value = ssh_string_data(mic_token); maj_stat = gss_verify_mic(&min_stat, session->gssapi->ctx, &mic_buf, &mic_token_buf, NULL); - ssh_gssapi_log_error(SSH_LOG_PROTOCOL, + ssh_gssapi_log_error(SSH_LOG_DEBUG, "verifying MIC", maj_stat, min_stat); @@ -572,7 +595,8 @@ end: * @returns gssapi credentials handle. * @returns NULL if no forwardable token is available. */ -ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session){ +ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session) +{ if (!session || !session->gssapi || session->gssapi->client_creds == GSS_C_NO_CREDENTIAL) return NULL; return (ssh_gssapi_creds)session->gssapi->client_creds; @@ -581,10 +605,11 @@ ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session){ #endif /* SERVER */ /** - * @brief Set the forwadable ticket to be given to the server for authentication. + * @brief Set the forwardable ticket to be given to the server for authentication. * Unlike ssh_gssapi_get_creds() this is called on the client side of an ssh * connection. * + * @param[in] session The session * @param[in] creds gssapi credentials handle. */ void ssh_gssapi_set_creds(ssh_session session, const ssh_gssapi_creds creds) @@ -602,7 +627,9 @@ void ssh_gssapi_set_creds(ssh_session session, const ssh_gssapi_creds creds) session->gssapi->client.client_deleg_creds = (gss_cred_id_t)creds; } -static int ssh_gssapi_send_auth_mic(ssh_session session, ssh_string *oid_set, int n_oid){ +static int +ssh_gssapi_send_auth_mic(ssh_session session, ssh_string *oid_set, int n_oid) +{ int rc; int i; @@ -638,7 +665,7 @@ fail: static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids) { OM_uint32 maj_stat, min_stat, lifetime; - gss_OID_set actual_mechs; + gss_OID_set actual_mechs = GSS_C_NO_OID_SET; gss_buffer_desc namebuf; gss_name_t client_id = GSS_C_NO_NAME; gss_OID oid; @@ -700,6 +727,7 @@ static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids) ret = SSH_OK; end: + gss_release_oid_set(&min_stat, &actual_mechs); gss_release_name(&min_stat, &client_id); return ret; } @@ -711,9 +739,10 @@ end: * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again * later. */ -int ssh_gssapi_auth_mic(ssh_session session){ +int ssh_gssapi_auth_mic(ssh_session session) +{ size_t i; - gss_OID_set selected; /* oid selected for authentication */ + gss_OID_set selected = GSS_C_NO_OID_SET; /* oid selected for authentication */ ssh_string *oids = NULL; int rc; size_t n_oids = 0; @@ -739,8 +768,8 @@ int ssh_gssapi_auth_mic(ssh_session session){ (gss_OID)GSS_C_NT_HOSTBASED_SERVICE, &session->gssapi->client.server_name); if (maj_stat != GSS_S_COMPLETE) { - SSH_LOG(SSH_LOG_WARNING, "importing name %d, %d", maj_stat, min_stat); - ssh_gssapi_log_error(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_DEBUG, "importing name %d, %d", maj_stat, min_stat); + ssh_gssapi_log_error(SSH_LOG_DEBUG, "importing name", maj_stat, min_stat); @@ -754,7 +783,7 @@ int ssh_gssapi_auth_mic(ssh_session session){ return SSH_AUTH_ERROR; } - SSH_LOG(SSH_LOG_PROTOCOL, "Authenticating with gssapi to host %s with user %s", + SSH_LOG(SSH_LOG_DEBUG, "Authenticating with gssapi to host %s with user %s", session->opts.host, session->gssapi->user); rc = ssh_gssapi_match(session, &selected); if (rc == SSH_ERROR) { @@ -762,7 +791,7 @@ int ssh_gssapi_auth_mic(ssh_session session){ } n_oids = selected->count; - SSH_LOG(SSH_LOG_PROTOCOL, "Sending %zu oids", n_oids); + SSH_LOG(SSH_LOG_DEBUG, "Sending %zu oids", n_oids); oids = calloc(n_oids, sizeof(ssh_string)); if (oids == NULL) { @@ -790,6 +819,8 @@ out: SSH_STRING_FREE(oids[i]); } free(oids); + gss_release_oid_set(&min_stat, &selected); + if (rc != SSH_ERROR) { return SSH_AUTH_AGAIN; } @@ -834,6 +865,7 @@ static gss_OID ssh_gssapi_oid_from_string(ssh_string oid_s) } SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response){ + int rc; ssh_string oid_s; gss_uint32 maj_stat, min_stat; gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; @@ -875,7 +907,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response){ 0, NULL, &input_token, NULL, &output_token, NULL, NULL); if(GSS_ERROR(maj_stat)){ - ssh_gssapi_log_error(SSH_LOG_WARNING, + ssh_gssapi_log_error(SSH_LOG_DEBUG, "Initializing gssapi context", maj_stat, min_stat); @@ -885,14 +917,20 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response){ hexa = ssh_get_hexa(output_token.value, output_token.length); SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s", hexa); SAFE_FREE(hexa); - ssh_buffer_pack(session->out_buffer, - "bdP", - SSH2_MSG_USERAUTH_GSSAPI_TOKEN, - output_token.length, - (size_t)output_token.length, output_token.value); + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_USERAUTH_GSSAPI_TOKEN, + output_token.length, + (size_t)output_token.length, output_token.value); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } ssh_packet_send(session); session->auth.state = SSH_AUTH_STATE_GSSAPI_TOKEN; } + + gss_release_buffer(&min_stat, &output_token); return SSH_PACKET_USED; error: @@ -902,7 +940,8 @@ error: return SSH_PACKET_USED; } -static int ssh_gssapi_send_mic(ssh_session session){ +static int ssh_gssapi_send_mic(ssh_session session) +{ OM_uint32 maj_stat, min_stat; gss_buffer_desc mic_buf = GSS_C_EMPTY_BUFFER; gss_buffer_desc mic_token_buf = GSS_C_EMPTY_BUFFER; @@ -921,9 +960,11 @@ static int ssh_gssapi_send_mic(ssh_session session){ maj_stat = gss_get_mic(&min_stat,session->gssapi->ctx, GSS_C_QOP_DEFAULT, &mic_buf, &mic_token_buf); + + SSH_BUFFER_FREE(mic_buffer); + if (GSS_ERROR(maj_stat)){ - SSH_BUFFER_FREE(mic_buffer); - ssh_gssapi_log_error(SSH_LOG_PROTOCOL, + ssh_gssapi_log_error(SSH_LOG_DEBUG, "generating MIC", maj_stat, min_stat); @@ -935,8 +976,10 @@ static int ssh_gssapi_send_mic(ssh_session session){ SSH2_MSG_USERAUTH_GSSAPI_MIC, mic_token_buf.length, (size_t)mic_token_buf.length, mic_token_buf.value); + + gss_release_buffer(&min_stat, &mic_token_buf); + if (rc != SSH_OK) { - SSH_BUFFER_FREE(mic_buffer); ssh_set_error_oom(session); return SSH_ERROR; } @@ -945,6 +988,7 @@ static int ssh_gssapi_send_mic(ssh_session session){ } SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client){ + int rc; ssh_string token; char *hexa; OM_uint32 maj_stat, min_stat; @@ -980,13 +1024,13 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client){ 0, NULL, &input_token, NULL, &output_token, NULL, NULL); - ssh_gssapi_log_error(SSH_LOG_PROTOCOL, + ssh_gssapi_log_error(SSH_LOG_DEBUG, "accepting token", maj_stat, min_stat); SSH_STRING_FREE(token); if (GSS_ERROR(maj_stat)){ - ssh_gssapi_log_error(SSH_LOG_PROTOCOL, + ssh_gssapi_log_error(SSH_LOG_DEBUG, "Gssapi error", maj_stat, min_stat); @@ -997,14 +1041,20 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client){ hexa = ssh_get_hexa(output_token.value, output_token.length); SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s",hexa); SAFE_FREE(hexa); - ssh_buffer_pack(session->out_buffer, - "bdP", - SSH2_MSG_USERAUTH_GSSAPI_TOKEN, - output_token.length, - (size_t)output_token.length, output_token.value); + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_USERAUTH_GSSAPI_TOKEN, + output_token.length, + (size_t)output_token.length, output_token.value); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } ssh_packet_send(session); } + gss_release_buffer(&min_stat, &output_token); + if (maj_stat == GSS_S_COMPLETE) { ssh_gssapi_send_mic(session); session->auth.state = SSH_AUTH_STATE_GSSAPI_MIC_SENT; @@ -24,209 +24,237 @@ #include "config.h" -#include <string.h> #include <stdlib.h> +#include <string.h> #include <zlib.h> -#include "libssh/priv.h" #include "libssh/buffer.h" #include "libssh/crypto.h" +#include "libssh/priv.h" #include "libssh/session.h" +#ifndef BLOCKSIZE #define BLOCKSIZE 4092 +#endif -static z_stream *initcompress(ssh_session session, int level) { - z_stream *stream = NULL; - int status; +static z_stream * +initcompress(ssh_session session, int level) +{ + z_stream *stream = NULL; + int status; - stream = calloc(1, sizeof(z_stream)); - if (stream == NULL) { - return NULL; - } + stream = calloc(1, sizeof(z_stream)); + if (stream == NULL) { + return NULL; + } - status = deflateInit(stream, level); - if (status != Z_OK) { - SAFE_FREE(stream); - ssh_set_error(session, SSH_FATAL, - "status %d inititalising zlib deflate", status); - return NULL; - } + status = deflateInit(stream, level); + if (status != Z_OK) { + SAFE_FREE(stream); + ssh_set_error(session, + SSH_FATAL, + "status %d initialising zlib deflate", + status); + return NULL; + } - return stream; + return stream; } -static ssh_buffer gzip_compress(ssh_session session, ssh_buffer source, int level) +static ssh_buffer +gzip_compress(ssh_session session, ssh_buffer source, int level) { - struct ssh_crypto_struct *crypto = NULL; - z_stream *zout = NULL; - void *in_ptr = ssh_buffer_get(source); - unsigned long in_size = ssh_buffer_get_len(source); - ssh_buffer dest = NULL; - unsigned char out_buf[BLOCKSIZE] = {0}; - unsigned long len; - int status; - - crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); - if (crypto == NULL) { - return NULL; - } - zout = crypto->compress_out_ctx; - if (zout == NULL) { - zout = crypto->compress_out_ctx = initcompress(session, level); + struct ssh_crypto_struct *crypto = NULL; + z_stream *zout = NULL; + void *in_ptr = ssh_buffer_get(source); + uint32_t in_size = ssh_buffer_get_len(source); + ssh_buffer dest = NULL; + unsigned char out_buf[BLOCKSIZE] = {0}; + uint32_t len; + int status; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); + if (crypto == NULL) { + return NULL; + } + zout = crypto->compress_out_ctx; if (zout == NULL) { - return NULL; + zout = crypto->compress_out_ctx = initcompress(session, level); + if (zout == NULL) { + return NULL; + } } - } - - dest = ssh_buffer_new(); - if (dest == NULL) { - return NULL; - } - zout->next_out = out_buf; - zout->next_in = in_ptr; - zout->avail_in = in_size; - do { - zout->avail_out = BLOCKSIZE; - status = deflate(zout, Z_PARTIAL_FLUSH); - if (status != Z_OK) { - SSH_BUFFER_FREE(dest); - ssh_set_error(session, SSH_FATAL, - "status %d deflating zlib packet", status); - return NULL; - } - len = BLOCKSIZE - zout->avail_out; - if (ssh_buffer_add_data(dest, out_buf, len) < 0) { - SSH_BUFFER_FREE(dest); - return NULL; + dest = ssh_buffer_new(); + if (dest == NULL) { + return NULL; } - zout->next_out = out_buf; - } while (zout->avail_out == 0); - return dest; + zout->next_out = out_buf; + zout->next_in = in_ptr; + zout->avail_in = in_size; + do { + zout->avail_out = BLOCKSIZE; + status = deflate(zout, Z_PARTIAL_FLUSH); + if (status != Z_OK) { + SSH_BUFFER_FREE(dest); + ssh_set_error(session, + SSH_FATAL, + "status %d deflating zlib packet", + status); + return NULL; + } + len = BLOCKSIZE - zout->avail_out; + if (ssh_buffer_add_data(dest, out_buf, len) < 0) { + SSH_BUFFER_FREE(dest); + return NULL; + } + zout->next_out = out_buf; + } while (zout->avail_out == 0); + + return dest; } -int compress_buffer(ssh_session session, ssh_buffer buf) { - ssh_buffer dest = NULL; +int +compress_buffer(ssh_session session, ssh_buffer buf) +{ + ssh_buffer dest = NULL; + int rv; - dest = gzip_compress(session, buf, session->opts.compressionlevel); - if (dest == NULL) { - return -1; - } + dest = gzip_compress(session, buf, session->opts.compressionlevel); + if (dest == NULL) { + return -1; + } - if (ssh_buffer_reinit(buf) < 0) { - SSH_BUFFER_FREE(dest); - return -1; - } + if (ssh_buffer_reinit(buf) < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } - if (ssh_buffer_add_data(buf, ssh_buffer_get(dest), ssh_buffer_get_len(dest)) < 0) { - SSH_BUFFER_FREE(dest); - return -1; - } + rv = ssh_buffer_add_data(buf, + ssh_buffer_get(dest), + ssh_buffer_get_len(dest)); + if (rv < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } - SSH_BUFFER_FREE(dest); - return 0; + SSH_BUFFER_FREE(dest); + return 0; } /* decompression */ -static z_stream *initdecompress(ssh_session session) { - z_stream *stream = NULL; - int status; +static z_stream * +initdecompress(ssh_session session) +{ + z_stream *stream = NULL; + int status; - stream = calloc(1, sizeof(z_stream)); - if (stream == NULL) { - return NULL; - } + stream = calloc(1, sizeof(z_stream)); + if (stream == NULL) { + return NULL; + } - status = inflateInit(stream); - if (status != Z_OK) { - SAFE_FREE(stream); - ssh_set_error(session, SSH_FATAL, - "Status = %d initiating inflate context!", status); - return NULL; - } + status = inflateInit(stream); + if (status != Z_OK) { + SAFE_FREE(stream); + ssh_set_error(session, + SSH_FATAL, + "Status = %d initiating inflate context!", + status); + return NULL; + } - return stream; + return stream; } -static ssh_buffer gzip_decompress(ssh_session session, ssh_buffer source, size_t maxlen) +static ssh_buffer +gzip_decompress(ssh_session session, ssh_buffer source, size_t maxlen) { - struct ssh_crypto_struct *crypto = NULL; - z_stream *zin = NULL; - void *in_ptr = ssh_buffer_get(source); - unsigned long in_size = ssh_buffer_get_len(source); - unsigned char out_buf[BLOCKSIZE] = {0}; - ssh_buffer dest = NULL; - unsigned long len; - int status; - - crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); - if (crypto == NULL) { - return NULL; - } - - zin = crypto->compress_in_ctx; - if (zin == NULL) { - zin = crypto->compress_in_ctx = initdecompress(session); - if (zin == NULL) { - return NULL; + struct ssh_crypto_struct *crypto = NULL; + z_stream *zin = NULL; + void *in_ptr = ssh_buffer_get(source); + uint32_t in_size = ssh_buffer_get_len(source); + unsigned char out_buf[BLOCKSIZE] = {0}; + ssh_buffer dest = NULL; + uint32_t len; + int status; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto == NULL) { + return NULL; } - } - - dest = ssh_buffer_new(); - if (dest == NULL) { - return NULL; - } - - zin->next_out = out_buf; - zin->next_in = in_ptr; - zin->avail_in = in_size; - do { - zin->avail_out = BLOCKSIZE; - status = inflate(zin, Z_PARTIAL_FLUSH); - if (status != Z_OK && status != Z_BUF_ERROR) { - ssh_set_error(session, SSH_FATAL, - "status %d inflating zlib packet", status); - SSH_BUFFER_FREE(dest); - return NULL; + zin = crypto->compress_in_ctx; + if (zin == NULL) { + zin = crypto->compress_in_ctx = initdecompress(session); + if (zin == NULL) { + return NULL; + } } - len = BLOCKSIZE - zin->avail_out; - if (ssh_buffer_add_data(dest,out_buf,len) < 0) { - SSH_BUFFER_FREE(dest); - return NULL; - } - if (ssh_buffer_get_len(dest) > maxlen){ - /* Size of packet exceeded, avoid a denial of service attack */ - SSH_BUFFER_FREE(dest); - return NULL; + dest = ssh_buffer_new(); + if (dest == NULL) { + return NULL; } - zin->next_out = out_buf; - } while (zin->avail_out == 0); - return dest; + zin->next_out = out_buf; + zin->next_in = in_ptr; + zin->avail_in = in_size; + + do { + zin->avail_out = BLOCKSIZE; + status = inflate(zin, Z_PARTIAL_FLUSH); + if (status != Z_OK && status != Z_BUF_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "status %d inflating zlib packet", + status); + SSH_BUFFER_FREE(dest); + return NULL; + } + + len = BLOCKSIZE - zin->avail_out; + if (ssh_buffer_add_data(dest, out_buf, len) < 0) { + SSH_BUFFER_FREE(dest); + return NULL; + } + if (ssh_buffer_get_len(dest) > maxlen) { + /* Size of packet exceeded, avoid a denial of service attack */ + SSH_BUFFER_FREE(dest); + return NULL; + } + zin->next_out = out_buf; + } while (zin->avail_out == 0); + + return dest; } -int decompress_buffer(ssh_session session,ssh_buffer buf, size_t maxlen){ - ssh_buffer dest = NULL; +int +decompress_buffer(ssh_session session, ssh_buffer buf, size_t maxlen) +{ + ssh_buffer dest = NULL; + int rv; - dest = gzip_decompress(session,buf, maxlen); - if (dest == NULL) { - return -1; - } + dest = gzip_decompress(session, buf, maxlen); + if (dest == NULL) { + return -1; + } - if (ssh_buffer_reinit(buf) < 0) { - SSH_BUFFER_FREE(dest); - return -1; - } + if (ssh_buffer_reinit(buf) < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } - if (ssh_buffer_add_data(buf, ssh_buffer_get(dest), ssh_buffer_get_len(dest)) < 0) { - SSH_BUFFER_FREE(dest); - return -1; - } + rv = ssh_buffer_add_data(buf, + ssh_buffer_get(dest), + ssh_buffer_get_len(dest)); + if (rv < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } - SSH_BUFFER_FREE(dest); - return 0; + SSH_BUFFER_FREE(dest); + return 0; } @@ -22,6 +22,9 @@ */ #include "config.h" + +#include <stdio.h> + #include "libssh/priv.h" #include "libssh/socket.h" #include "libssh/dh.h" @@ -104,7 +107,7 @@ _ret: /** * @brief Initialize global cryptographic data structures. * - * This functions is automatically called when the library is loaded. + * This function is automatically called when the library is loaded. * */ void libssh_constructor(void) @@ -127,7 +130,7 @@ void libssh_constructor(void) * The libssh library is implementing the SSH protocols and some of its * extensions. This group of functions is mostly used to implement an SSH * client. - * Some function are needed to implement an SSH server too. + * Some functions are needed to implement an SSH server too. * * @{ */ @@ -136,8 +139,8 @@ void libssh_constructor(void) * @brief Initialize global cryptographic data structures. * * Since version 0.8.0, when libssh is dynamically linked, it is not necessary - * to call this function on systems which are fully supported with regards to - * threading (that is, system with pthreads available). + * to call this function on systems that fully support threading (that is, + * systems with pthreads available). * * If libssh is statically linked, it is necessary to explicitly call ssh_init() * before calling any other provided API, and it is necessary to explicitly call @@ -161,12 +164,14 @@ static int _ssh_finalize(unsigned destructor) { if (_ssh_initialized > 1) { _ssh_initialized--; - goto _ret; + ssh_mutex_unlock(&ssh_init_mutex); + return 0; } if (_ssh_initialized == 1) { if (_ssh_init_ret < 0) { - goto _ret; + ssh_mutex_unlock(&ssh_init_mutex); + return 0; } } } @@ -181,15 +186,22 @@ static int _ssh_finalize(unsigned destructor) { _ssh_initialized = 0; -_ret: if (!destructor) { ssh_mutex_unlock(&ssh_init_mutex); } + +#if (defined(_WIN32) && !defined(HAVE_PTHREAD)) + if (ssh_init_mutex != NULL) { + DeleteCriticalSection(ssh_init_mutex); + SAFE_FREE(ssh_init_mutex); + } +#endif + return 0; } /** - * @brief Finalize and cleanup all libssh and cryptographic data structures. + * @brief Finalize and clean up all libssh and cryptographic data structures. * * This function is automatically called when the library is unloaded. * @@ -206,7 +218,7 @@ void libssh_destructor(void) } /** - * @brief Finalize and cleanup all libssh and cryptographic data structures. + * @brief Finalize and clean up all libssh and cryptographic data structures. * * Since version 0.8.0, when libssh is dynamically linked, it is not necessary * to call this function, since it is automatically called when the library is @@ -269,7 +281,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, * * @see ssh_init() */ -bool is_ssh_initialized() { +bool is_ssh_initialized(void) { bool is_initialized = false; @@ -39,7 +39,7 @@ /* The following implements the SSHKDF for crypto backend that - * do not have a native implementations */ + * do not have a native implementation */ struct ssh_mac_ctx_struct { enum ssh_kdf_digest digest_type; union { @@ -58,73 +58,110 @@ static ssh_mac_ctx ssh_mac_ctx_init(enum ssh_kdf_digest type) } ctx->digest_type = type; - switch(type){ + switch (type) { case SSH_KDF_SHA1: ctx->ctx.sha1_ctx = sha1_init(); + if (ctx->ctx.sha1_ctx == NULL) { + goto err; + } return ctx; case SSH_KDF_SHA256: ctx->ctx.sha256_ctx = sha256_init(); + if (ctx->ctx.sha256_ctx == NULL) { + goto err; + } return ctx; case SSH_KDF_SHA384: ctx->ctx.sha384_ctx = sha384_init(); + if (ctx->ctx.sha384_ctx == NULL) { + goto err; + } return ctx; case SSH_KDF_SHA512: ctx->ctx.sha512_ctx = sha512_init(); + if (ctx->ctx.sha512_ctx == NULL) { + goto err; + } return ctx; - default: - SAFE_FREE(ctx); - return NULL; } +err: + SAFE_FREE(ctx); + return NULL; } -static void ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) +static void ssh_mac_ctx_free(ssh_mac_ctx ctx) { - switch(ctx->digest_type){ + if (ctx == NULL) { + return; + } + + switch (ctx->digest_type) { case SSH_KDF_SHA1: - sha1_update(ctx->ctx.sha1_ctx, data, len); + sha1_ctx_free(ctx->ctx.sha1_ctx); break; case SSH_KDF_SHA256: - sha256_update(ctx->ctx.sha256_ctx, data, len); + sha256_ctx_free(ctx->ctx.sha256_ctx); break; case SSH_KDF_SHA384: - sha384_update(ctx->ctx.sha384_ctx, data, len); + sha384_ctx_free(ctx->ctx.sha384_ctx); break; case SSH_KDF_SHA512: - sha512_update(ctx->ctx.sha512_ctx, data, len); + sha512_ctx_free(ctx->ctx.sha512_ctx); break; } + SAFE_FREE(ctx); } -static void ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) +static int ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) { - switch(ctx->digest_type){ + switch (ctx->digest_type) { + case SSH_KDF_SHA1: + return sha1_update(ctx->ctx.sha1_ctx, data, len); + case SSH_KDF_SHA256: + return sha256_update(ctx->ctx.sha256_ctx, data, len); + case SSH_KDF_SHA384: + return sha384_update(ctx->ctx.sha384_ctx, data, len); + case SSH_KDF_SHA512: + return sha512_update(ctx->ctx.sha512_ctx, data, len); + } + return SSH_ERROR; +} + +static int ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) +{ + int rc = SSH_ERROR; + + switch (ctx->digest_type) { case SSH_KDF_SHA1: - sha1_final(md,ctx->ctx.sha1_ctx); + rc = sha1_final(md, ctx->ctx.sha1_ctx); break; case SSH_KDF_SHA256: - sha256_final(md,ctx->ctx.sha256_ctx); + rc = sha256_final(md, ctx->ctx.sha256_ctx); break; case SSH_KDF_SHA384: - sha384_final(md,ctx->ctx.sha384_ctx); + rc = sha384_final(md, ctx->ctx.sha384_ctx); break; case SSH_KDF_SHA512: - sha512_final(md,ctx->ctx.sha512_ctx); + rc = sha512_final(md, ctx->ctx.sha512_ctx); break; } SAFE_FREE(ctx); + return rc; } int sshkdf_derive_key(struct ssh_crypto_struct *crypto, - unsigned char *key, size_t key_len, - int key_type, unsigned char *output, + unsigned char *key, + size_t key_len, + uint8_t key_type, + unsigned char *output, size_t requested_len) { /* Can't use VLAs with Visual Studio, so allocate the biggest * digest buffer we can possibly need */ unsigned char digest[DIGEST_MAX_LEN]; size_t output_len = crypto->digest_len; - char letter = key_type; ssh_mac_ctx ctx; + int rc; if (DIGEST_MAX_LEN < crypto->digest_len) { return -1; @@ -135,11 +172,30 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, return -1; } - ssh_mac_update(ctx, key, key_len); - ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); - ssh_mac_update(ctx, &letter, 1); - ssh_mac_update(ctx, crypto->session_id, crypto->digest_len); - ssh_mac_final(digest, ctx); + rc = ssh_mac_update(ctx, key, key_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, &key_type, 1); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, crypto->session_id, crypto->session_id_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_final(digest, ctx); + if (rc != SSH_OK) { + return -1; + } if (requested_len < output_len) { output_len = requested_len; @@ -151,10 +207,25 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, if (ctx == NULL) { return -1; } - ssh_mac_update(ctx, key, key_len); - ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); - ssh_mac_update(ctx, output, output_len); - ssh_mac_final(digest, ctx); + rc = ssh_mac_update(ctx, key, key_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, output, output_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_final(digest, ctx); + if (rc != SSH_OK) { + return -1; + } if (requested_len < output_len + crypto->digest_len) { memcpy(output + output_len, digest, requested_len - output_len); } else { @@ -28,6 +28,7 @@ #include <stdio.h> #include <stdbool.h> +#include "libssh/libssh.h" #include "libssh/priv.h" #include "libssh/buffer.h" #include "libssh/dh.h" @@ -47,7 +48,7 @@ #ifdef WITH_BLOWFISH_CIPHER # if defined(HAVE_OPENSSL_BLOWFISH_H) || defined(HAVE_LIBGCRYPT) || defined(HAVE_LIBMBEDCRYPTO) -# define BLOWFISH "blowfish-cbc," +# define BLOWFISH ",blowfish-cbc" # else # define BLOWFISH "" # endif @@ -57,10 +58,9 @@ #ifdef HAVE_LIBGCRYPT # define AES "aes256-gcm@openssh.com,aes128-gcm@openssh.com," \ - "aes256-ctr,aes192-ctr,aes128-ctr," -# define AES_CBC "aes256-cbc,aes192-cbc,aes128-cbc," -# define DES "3des-cbc" -# define DES_SUPPORTED "3des-cbc" + "aes256-ctr,aes192-ctr,aes128-ctr" +# define AES_CBC ",aes256-cbc,aes192-cbc,aes128-cbc" +# define DES_SUPPORTED ",3des-cbc" #elif defined(HAVE_LIBMBEDCRYPTO) # ifdef MBEDTLS_GCM_C @@ -68,89 +68,82 @@ # else # define GCM "" # endif /* MBEDTLS_GCM_C */ -# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr," -# define AES_CBC "aes256-cbc,aes192-cbc,aes128-cbc," -# define DES "3des-cbc" -# define DES_SUPPORTED "3des-cbc" +# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr" +# define AES_CBC ",aes256-cbc,aes192-cbc,aes128-cbc" +# define DES_SUPPORTED ",3des-cbc" #elif defined(HAVE_LIBCRYPTO) # ifdef HAVE_OPENSSL_AES_H -# ifdef HAVE_OPENSSL_EVP_AES_GCM -# define GCM "aes256-gcm@openssh.com,aes128-gcm@openssh.com," -# else -# define GCM "" -# endif /* HAVE_OPENSSL_EVP_AES_GCM */ -# ifdef BROKEN_AES_CTR -# define AES GCM -# define AES_CBC "aes256-cbc,aes192-cbc,aes128-cbc," -# else /* BROKEN_AES_CTR */ -# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr," -# define AES_CBC "aes256-cbc,aes192-cbc,aes128-cbc," -# endif /* BROKEN_AES_CTR */ +# define GCM "aes256-gcm@openssh.com,aes128-gcm@openssh.com," +# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr" +# define AES_CBC ",aes256-cbc,aes192-cbc,aes128-cbc" # else /* HAVE_OPENSSL_AES_H */ # define AES "" # define AES_CBC "" # endif /* HAVE_OPENSSL_AES_H */ -# define DES "3des-cbc" -# define DES_SUPPORTED "3des-cbc" +# define DES_SUPPORTED ",3des-cbc" #endif /* HAVE_LIBCRYPTO */ #ifdef WITH_ZLIB -#define ZLIB "none,zlib,zlib@openssh.com" +#define ZLIB "none,zlib@openssh.com,zlib" +#define ZLIB_DEFAULT "none,zlib@openssh.com" #else #define ZLIB "none" -#endif +#define ZLIB_DEFAULT "none" +#endif /* WITH_ZLIB */ #ifdef HAVE_CURVE25519 #define CURVE25519 "curve25519-sha256,curve25519-sha256@libssh.org," #else #define CURVE25519 "" -#endif +#endif /* HAVE_CURVE25519 */ -#ifdef HAVE_ECDH +#ifdef HAVE_ECC #define ECDH "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521," -#define EC_HOSTKEYS "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,ecdsa-sha2-nistp256," -#define EC_PUBLIC_KEY_ALGORITHMS "ecdsa-sha2-nistp521-cert-v01@openssh.com," \ +#define EC_HOSTKEYS "ecdsa-sha2-nistp521," \ + "ecdsa-sha2-nistp384," \ + "ecdsa-sha2-nistp256," +#define EC_SK_HOSTKEYS "sk-ecdsa-sha2-nistp256@openssh.com," +#define EC_FIPS_PUBLIC_KEY_ALGOS "ecdsa-sha2-nistp521-cert-v01@openssh.com," \ "ecdsa-sha2-nistp384-cert-v01@openssh.com," \ "ecdsa-sha2-nistp256-cert-v01@openssh.com," +#define EC_PUBLIC_KEY_ALGORITHMS EC_FIPS_PUBLIC_KEY_ALGOS \ + "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com," #else +#define ECDH "" #define EC_HOSTKEYS "" +#define EC_SK_HOSTKEYS "" +#define EC_FIPS_PUBLIC_KEY_ALGOS "" #define EC_PUBLIC_KEY_ALGORITHMS "" -#define ECDH "" -#endif - -#ifdef HAVE_DSA -#define DSA_HOSTKEYS ",ssh-dss" -#define DSA_PUBLIC_KEY_ALGORITHMS ",ssh-dss-cert-v01@openssh.com" -#else -#define DSA_HOSTKEYS "" -#define DSA_PUBLIC_KEY_ALGORITHMS "" -#endif +#endif /* HAVE_ECC */ #ifdef WITH_INSECURE_NONE #define NONE ",none" #else #define NONE -#endif +#endif /* WITH_INSECURE_NONE */ #define HOSTKEYS "ssh-ed25519," \ EC_HOSTKEYS \ + "sk-ssh-ed25519@openssh.com," \ + EC_SK_HOSTKEYS \ "rsa-sha2-512," \ "rsa-sha2-256," \ - "ssh-rsa" \ - DSA_HOSTKEYS + "ssh-rsa" #define DEFAULT_HOSTKEYS "ssh-ed25519," \ EC_HOSTKEYS \ + "sk-ssh-ed25519@openssh.com," \ + EC_SK_HOSTKEYS \ "rsa-sha2-512," \ "rsa-sha2-256" #define PUBLIC_KEY_ALGORITHMS "ssh-ed25519-cert-v01@openssh.com," \ + "sk-ssh-ed25519-cert-v01@openssh.com," \ EC_PUBLIC_KEY_ALGORITHMS \ "rsa-sha2-512-cert-v01@openssh.com," \ "rsa-sha2-256-cert-v01@openssh.com," \ - "ssh-rsa-cert-v01@openssh.com" \ - DSA_PUBLIC_KEY_ALGORITHMS "," \ + "ssh-rsa-cert-v01@openssh.com," \ HOSTKEYS #define DEFAULT_PUBLIC_KEY_ALGORITHMS "ssh-ed25519-cert-v01@openssh.com," \ EC_PUBLIC_KEY_ALGORITHMS \ @@ -168,19 +161,23 @@ #define CHACHA20 "chacha20-poly1305@openssh.com," -#define KEY_EXCHANGE \ +#define DEFAULT_KEY_EXCHANGE \ CURVE25519 \ ECDH \ "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512," \ GEX_SHA256 \ - "diffie-hellman-group14-sha256," \ - "diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" + "diffie-hellman-group14-sha256" \ + #define KEY_EXCHANGE_SUPPORTED \ GEX_SHA1 \ - KEY_EXCHANGE + DEFAULT_KEY_EXCHANGE \ + ",diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" /* RFC 8308 */ #define KEX_EXTENSION_CLIENT "ext-info-c" +/* Strict kex mitigation against CVE-2023-48795 */ +#define KEX_STRICT_CLIENT "kex-strict-c-v00@openssh.com" +#define KEX_STRICT_SERVER "kex-strict-s-v00@openssh.com" /* Allowed algorithms in FIPS mode */ #define FIPS_ALLOWED_CIPHERS "aes256-gcm@openssh.com,"\ @@ -194,7 +191,7 @@ "rsa-sha2-512," \ "rsa-sha2-256" -#define FIPS_ALLOWED_PUBLIC_KEY_ALGORITHMS EC_PUBLIC_KEY_ALGORITHMS \ +#define FIPS_ALLOWED_PUBLIC_KEY_ALGORITHMS EC_FIPS_PUBLIC_KEY_ALGOS \ "rsa-sha2-512-cert-v01@openssh.com," \ "rsa-sha2-256-cert-v01@openssh.com," \ FIPS_ALLOWED_HOSTKEYS @@ -222,8 +219,8 @@ static const char *fips_methods[] = { FIPS_ALLOWED_CIPHERS, FIPS_ALLOWED_MACS, FIPS_ALLOWED_MACS, - ZLIB, - ZLIB, + ZLIB_DEFAULT, + ZLIB_DEFAULT, "", "", NULL @@ -231,14 +228,14 @@ static const char *fips_methods[] = { /* NOTE: This is a fixed API and the index is defined by ssh_kex_types_e */ static const char *default_methods[] = { - KEY_EXCHANGE, + DEFAULT_KEY_EXCHANGE, DEFAULT_PUBLIC_KEY_ALGORITHMS, - CHACHA20 AES DES, - CHACHA20 AES DES, - "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1", - "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1", - "none", - "none", + CHACHA20 AES, + CHACHA20 AES, + "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-512", + "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-512", + ZLIB_DEFAULT, + ZLIB_DEFAULT, "", "", NULL @@ -324,6 +321,10 @@ static int cmp_first_kex_algo(const char *client_str, int is_wrong = 1; + if (client_str == NULL || server_str == NULL) { + return is_wrong; + } + colon = strchr(client_str, ','); if (colon == NULL) { client_kex_len = strlen(client_str); @@ -350,6 +351,7 @@ static int cmp_first_kex_algo(const char *client_str, SSH_PACKET_CALLBACK(ssh_packet_kexinit) { int i, ok; + struct ssh_crypto_struct *crypto = session->next_crypto; int server_kex = session->server; ssh_string str = NULL; char *strings[SSH_KEX_METHODS] = {0}; @@ -363,35 +365,67 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) (void)type; (void)user; + SSH_LOG(SSH_LOG_TRACE, "KEXINIT received"); + if (session->session_state == SSH_SESSION_STATE_AUTHENTICATED) { - SSH_LOG(SSH_LOG_INFO, "Initiating key re-exchange"); + if (session->dh_handshake_state == DH_STATE_FINISHED) { + SSH_LOG(SSH_LOG_DEBUG, "Peer initiated key re-exchange"); + /* Reset the sent flag if the re-kex was initiated by the peer */ + session->flags &= ~SSH_SESSION_FLAG_KEXINIT_SENT; + } else if (session->flags & SSH_SESSION_FLAG_KEXINIT_SENT && + session->dh_handshake_state == DH_STATE_INIT_SENT) { + /* This happens only when we are sending our-guessed first kex + * packet right after our KEXINIT packet. */ + SSH_LOG(SSH_LOG_DEBUG, "Received peer kexinit answer."); + } else if (session->session_state != SSH_SESSION_STATE_INITIAL_KEX) { + ssh_set_error(session, SSH_FATAL, + "SSH_KEXINIT received in wrong state"); + goto error; + } } else if (session->session_state != SSH_SESSION_STATE_INITIAL_KEX) { - ssh_set_error(session,SSH_FATAL,"SSH_KEXINIT received in wrong state"); + ssh_set_error(session, SSH_FATAL, + "SSH_KEXINIT received in wrong state"); goto error; } if (server_kex) { - len = ssh_buffer_get_data(packet,session->next_crypto->client_kex.cookie, 16); +#ifdef WITH_SERVER + len = ssh_buffer_get_data(packet, crypto->client_kex.cookie, 16); if (len != 16) { - ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: no cookie in packet"); + ssh_set_error(session, SSH_FATAL, + "ssh_packet_kexinit: no cookie in packet"); goto error; } - ok = ssh_hashbufin_add_cookie(session, session->next_crypto->client_kex.cookie); + ok = ssh_hashbufin_add_cookie(session, crypto->client_kex.cookie); if (ok < 0) { - ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed"); + ssh_set_error(session, SSH_FATAL, + "ssh_packet_kexinit: adding cookie failed"); + goto error; + } + + ok = server_set_kex(session); + if (ok == SSH_ERROR) { goto error; } +#endif /* WITH_SERVER */ } else { - len = ssh_buffer_get_data(packet,session->next_crypto->server_kex.cookie, 16); + len = ssh_buffer_get_data(packet, crypto->server_kex.cookie, 16); if (len != 16) { - ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: no cookie in packet"); + ssh_set_error(session, SSH_FATAL, + "ssh_packet_kexinit: no cookie in packet"); goto error; } - ok = ssh_hashbufin_add_cookie(session, session->next_crypto->server_kex.cookie); + ok = ssh_hashbufin_add_cookie(session, crypto->server_kex.cookie); if (ok < 0) { - ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed"); + ssh_set_error(session, SSH_FATAL, + "ssh_packet_kexinit: adding cookie failed"); + goto error; + } + + ok = ssh_set_client_kex(session); + if (ok == SSH_ERROR) { goto error; } } @@ -404,7 +438,8 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) rc = ssh_buffer_add_ssh_string(session->in_hashbuf, str); if (rc < 0) { - ssh_set_error(session, SSH_FATAL, "Error adding string in hash buffer"); + ssh_set_error(session, SSH_FATAL, + "Error adding string in hash buffer"); goto error; } @@ -417,14 +452,16 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) str = NULL; } - /* copy the server kex info into an array of strings */ + /* copy the peer kex info into an array of strings */ if (server_kex) { +#ifdef WITH_SERVER for (i = 0; i < SSH_KEX_METHODS; i++) { - session->next_crypto->client_kex.methods[i] = strings[i]; + crypto->client_kex.methods[i] = strings[i]; } +#endif /* WITH_SERVER */ } else { /* client */ for (i = 0; i < SSH_KEX_METHODS; i++) { - session->next_crypto->server_kex.methods[i] = strings[i]; + crypto->server_kex.methods[i] = strings[i]; } } @@ -438,30 +475,70 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) * that its value is included when computing the session ID (see * 'make_sessionid'). */ - if (server_kex) { - rc = ssh_buffer_get_u8(packet, &first_kex_packet_follows); - if (rc != 1) { - goto error; - } - rc = ssh_buffer_add_u8(session->in_hashbuf, first_kex_packet_follows); - if (rc < 0) { - goto error; - } + rc = ssh_buffer_get_u8(packet, &first_kex_packet_follows); + if (rc != 1) { + goto error; + } - rc = ssh_buffer_add_u32(session->in_hashbuf, kexinit_reserved); - if (rc < 0) { - goto error; - } + rc = ssh_buffer_add_u8(session->in_hashbuf, first_kex_packet_follows); + if (rc < 0) { + goto error; + } + + rc = ssh_buffer_add_u32(session->in_hashbuf, kexinit_reserved); + if (rc < 0) { + goto error; + } + /* + * Remember whether 'first_kex_packet_follows' was set and the client + * guess was wrong: in this case the next SSH_MSG_KEXDH_INIT message + * must be ignored on the server side. + * Client needs to start the Key exchange over with the correct method + */ + if (first_kex_packet_follows || session->send_first_kex_follows) { + char **client_methods = crypto->client_kex.methods; + char **server_methods = crypto->server_kex.methods; + session->first_kex_follows_guess_wrong = + cmp_first_kex_algo(client_methods[SSH_KEX], + server_methods[SSH_KEX]) || + cmp_first_kex_algo(client_methods[SSH_HOSTKEYS], + server_methods[SSH_HOSTKEYS]); + SSH_LOG(SSH_LOG_DEBUG, "The initial guess was %s.", + session->first_kex_follows_guess_wrong ? "wrong" : "right"); + } + + /* + * handle the "strict KEX" feature. If supported by peer, then set up the + * flag and verify packet sequence numbers. + */ + if (server_kex) { + ok = ssh_match_group(crypto->client_kex.methods[SSH_KEX], + KEX_STRICT_CLIENT); + if (ok) { + SSH_LOG(SSH_LOG_DEBUG, "Client supports strict kex, enabling."); + session->flags |= SSH_SESSION_FLAG_KEX_STRICT; + } + } else { + /* client kex */ + ok = ssh_match_group(crypto->server_kex.methods[SSH_KEX], + KEX_STRICT_SERVER); + if (ok) { + SSH_LOG(SSH_LOG_DEBUG, "Server supports strict kex, enabling."); + session->flags |= SSH_SESSION_FLAG_KEX_STRICT; + } + } +#ifdef WITH_SERVER + if (server_kex) { /* * If client sent a ext-info-c message in the kex list, it supports * RFC 8308 extension negotiation. */ - ok = ssh_match_group(session->next_crypto->client_kex.methods[SSH_KEX], + ok = ssh_match_group(crypto->client_kex.methods[SSH_KEX], KEX_EXTENSION_CLIENT); if (ok) { - const char *hostkeys = NULL; + const char *hostkeys = NULL, *wanted_hostkeys = NULL; /* The client supports extension negotiation */ session->extensions |= SSH_EXT_NEGOTIATION; @@ -471,14 +548,14 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) * by the client and enable the respective extensions to provide * correct signature in the next packet if RSA is negotiated */ - hostkeys = session->next_crypto->client_kex.methods[SSH_HOSTKEYS]; + hostkeys = crypto->client_kex.methods[SSH_HOSTKEYS]; + wanted_hostkeys = session->opts.wanted_methods[SSH_HOSTKEYS]; ok = ssh_match_group(hostkeys, "rsa-sha2-512"); if (ok) { /* Check if rsa-sha2-512 is allowed by config */ - if (session->opts.wanted_methods[SSH_HOSTKEYS] != NULL) { - char *is_allowed = - ssh_find_matching(session->opts.wanted_methods[SSH_HOSTKEYS], - "rsa-sha2-512"); + if (wanted_hostkeys != NULL) { + char *is_allowed = ssh_find_matching(wanted_hostkeys, + "rsa-sha2-512"); if (is_allowed != NULL) { session->extensions |= SSH_EXT_SIG_RSA_SHA512; } @@ -488,10 +565,9 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) ok = ssh_match_group(hostkeys, "rsa-sha2-256"); if (ok) { /* Check if rsa-sha2-256 is allowed by config */ - if (session->opts.wanted_methods[SSH_HOSTKEYS] != NULL) { - char *is_allowed = - ssh_find_matching(session->opts.wanted_methods[SSH_HOSTKEYS], - "rsa-sha2-256"); + if (wanted_hostkeys != NULL) { + char *is_allowed = ssh_find_matching(wanted_hostkeys, + "rsa-sha2-256"); if (is_allowed != NULL) { session->extensions |= SSH_EXT_SIG_RSA_SHA256; } @@ -507,7 +583,7 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) (session->extensions & SSH_EXT_SIG_RSA_SHA512)) { session->extensions &= ~(SSH_EXT_SIG_RSA_SHA256 | SSH_EXT_SIG_RSA_SHA512); rsa_sig_ext = ssh_find_matching("rsa-sha2-512,rsa-sha2-256", - session->next_crypto->client_kex.methods[SSH_HOSTKEYS]); + hostkeys); if (rsa_sig_ext == NULL) { goto error; /* should never happen */ } else if (strcmp(rsa_sig_ext, "rsa-sha2-512") == 0) { @@ -526,24 +602,17 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) session->extensions & SSH_EXT_SIG_RSA_SHA256 ? "SHA256" : "", session->extensions & SSH_EXT_SIG_RSA_SHA512 ? " SHA512" : ""); } - - /* - * Remember whether 'first_kex_packet_follows' was set and the client - * guess was wrong: in this case the next SSH_MSG_KEXDH_INIT message - * must be ignored. - */ - if (first_kex_packet_follows) { - session->first_kex_follows_guess_wrong = - cmp_first_kex_algo(session->next_crypto->client_kex.methods[SSH_KEX], - session->next_crypto->server_kex.methods[SSH_KEX]) || - cmp_first_kex_algo(session->next_crypto->client_kex.methods[SSH_HOSTKEYS], - session->next_crypto->server_kex.methods[SSH_HOSTKEYS]); - } } +#endif /* WITH_SERVER */ /* Note, that his overwrites authenticated state in case of rekeying */ session->session_state = SSH_SESSION_STATE_KEXINIT_RECEIVED; - session->dh_handshake_state = DH_STATE_INIT; + /* if we already sent our initial key exchange packet, do not reset the + * DH state. We will know if we were right with our guess only in + * dh_handshake_state() */ + if (session->send_first_kex_follows == false) { + session->dh_handshake_state = DH_STATE_INIT; + } session->ssh_connection_callback(session); return SSH_PACKET_USED; @@ -551,7 +620,9 @@ error: SSH_STRING_FREE(str); for (i = 0; i < SSH_KEX_METHODS; i++) { if (server_kex) { +#ifdef WITH_SERVER session->next_crypto->client_kex.methods[i] = NULL; +#endif /* WITH_SERVER */ } else { /* client */ session->next_crypto->server_kex.methods[i] = NULL; } @@ -609,7 +680,7 @@ char *ssh_client_select_hostkeys(ssh_session session) /* This removes the certificate types, unsupported for now */ wanted_without_certs = ssh_find_all_matching(HOSTKEYS, wanted); if (wanted_without_certs == NULL) { - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_TRACE, "List of allowed host key algorithms is empty or contains only " "unsupported algorithms"); return NULL; @@ -662,7 +733,7 @@ char *ssh_client_select_hostkeys(ssh_session session) fips_hostkeys = ssh_keep_fips_algos(SSH_HOSTKEYS, new_hostkeys); SAFE_FREE(new_hostkeys); if (fips_hostkeys == NULL) { - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_TRACE, "None of the wanted host keys or keys in known_hosts files " "is allowed in FIPS mode."); return NULL; @@ -685,11 +756,14 @@ int ssh_set_client_kex(ssh_session session) { struct ssh_kex_struct *client = &session->next_crypto->client_kex; const char *wanted; - char *kex = NULL; - char *kex_tmp = NULL; int ok; int i; - size_t kex_len, len; + + /* Skip if already set, for example for the rekey or when we do the guessing + * it could have been already used to make some protocol decisions. */ + if (client->methods[0] != NULL) { + return SSH_OK; + } ok = ssh_get_random(client->cookie, 16, 0); if (!ok) { @@ -697,8 +771,6 @@ int ssh_set_client_kex(ssh_session session) return SSH_ERROR; } - memset(client->methods, 0, SSH_KEX_METHODS * sizeof(char **)); - /* Set the list of allowed algorithms in order of preference, if it hadn't * been set yet. */ for (i = 0; i < SSH_KEX_METHODS; i++) { @@ -734,81 +806,205 @@ int ssh_set_client_kex(ssh_session session) return SSH_OK; } - /* Here we append ext-info-c to the list of kex algorithms */ - kex = client->methods[SSH_KEX]; + ok = ssh_kex_append_extensions(session, client); + if (ok != SSH_OK){ + return ok; + } + + return SSH_OK; +} + +int ssh_kex_append_extensions(ssh_session session, struct ssh_kex_struct *pkex) +{ + char *kex = NULL; + char *kex_tmp = NULL; + size_t kex_len, len; + + /* Here we append ext-info-c and kex-strict-c-v00@openssh.com for client + * and kex-strict-s-v00@openssh.com for server to the list of kex algorithms + */ + kex = pkex->methods[SSH_KEX]; len = strlen(kex); - if (len + strlen(KEX_EXTENSION_CLIENT) + 2 < len) { + if (session->server) { + /* Comma, nul byte */ + kex_len = len + 1 + strlen(KEX_STRICT_SERVER) + 1; + } else { + /* Comma, comma, nul byte */ + kex_len = len + 1 + strlen(KEX_EXTENSION_CLIENT) + 1 + + strlen(KEX_STRICT_CLIENT) + 1; + } + if (kex_len >= MAX_PACKET_LEN) { /* Overflow */ return SSH_ERROR; } - kex_len = len + strlen(KEX_EXTENSION_CLIENT) + 2; /* comma, NULL */ kex_tmp = realloc(kex, kex_len); if (kex_tmp == NULL) { - free(kex); ssh_set_error_oom(session); return SSH_ERROR; } - snprintf(kex_tmp + len, kex_len - len, ",%s", KEX_EXTENSION_CLIENT); - client->methods[SSH_KEX] = kex_tmp; - + if (session->server){ + snprintf(kex_tmp + len, kex_len - len, ",%s", KEX_STRICT_SERVER); + } else { + snprintf(kex_tmp + len, + kex_len - len, + ",%s,%s", + KEX_EXTENSION_CLIENT, + KEX_STRICT_CLIENT); + } + pkex->methods[SSH_KEX] = kex_tmp; return SSH_OK; } +static const char *ssh_find_aead_hmac(const char *cipher) +{ + if (cipher == NULL) { + return NULL; + } else if (strcmp(cipher, "chacha20-poly1305@openssh.com") == 0) { + return "aead-poly1305"; + } else if (strcmp(cipher, "aes256-gcm@openssh.com") == 0) { + return "aead-gcm"; + } else if (strcmp(cipher, "aes128-gcm@openssh.com") == 0) { + return "aead-gcm"; + } + return NULL; +} + +static enum ssh_key_exchange_e +kex_select_kex_type(const char *kex) +{ + if (strcmp(kex, "diffie-hellman-group1-sha1") == 0) { + return SSH_KEX_DH_GROUP1_SHA1; + } else if (strcmp(kex, "diffie-hellman-group14-sha1") == 0) { + return SSH_KEX_DH_GROUP14_SHA1; + } else if (strcmp(kex, "diffie-hellman-group14-sha256") == 0) { + return SSH_KEX_DH_GROUP14_SHA256; + } else if (strcmp(kex, "diffie-hellman-group16-sha512") == 0) { + return SSH_KEX_DH_GROUP16_SHA512; + } else if (strcmp(kex, "diffie-hellman-group18-sha512") == 0) { + return SSH_KEX_DH_GROUP18_SHA512; +#ifdef WITH_GEX + } else if (strcmp(kex, "diffie-hellman-group-exchange-sha1") == 0) { + return SSH_KEX_DH_GEX_SHA1; + } else if (strcmp(kex, "diffie-hellman-group-exchange-sha256") == 0) { + return SSH_KEX_DH_GEX_SHA256; +#endif /* WITH_GEX */ + } else if (strcmp(kex, "ecdh-sha2-nistp256") == 0) { + return SSH_KEX_ECDH_SHA2_NISTP256; + } else if (strcmp(kex, "ecdh-sha2-nistp384") == 0) { + return SSH_KEX_ECDH_SHA2_NISTP384; + } else if (strcmp(kex, "ecdh-sha2-nistp521") == 0) { + return SSH_KEX_ECDH_SHA2_NISTP521; + } else if (strcmp(kex, "curve25519-sha256@libssh.org") == 0) { + return SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG; + } else if (strcmp(kex, "curve25519-sha256") == 0) { + return SSH_KEX_CURVE25519_SHA256; + } + /* should not happen. We should be getting only valid names at this stage */ + return 0; +} + + +/** @internal + * @brief Reverts guessed callbacks set during the dh_handshake() + * @param session session handle + * @returns void + */ +static void revert_kex_callbacks(ssh_session session) +{ + switch (session->next_crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_KEX_DH_GROUP18_SHA512: + ssh_client_dh_remove_callbacks(session); + break; +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + ssh_client_dhgex_remove_callbacks(session); + break; +#endif /* WITH_GEX */ +#ifdef HAVE_ECDH + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_ECDH_SHA2_NISTP384: + case SSH_KEX_ECDH_SHA2_NISTP521: + ssh_client_ecdh_remove_callbacks(session); + break; +#endif +#ifdef HAVE_CURVE25519 + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + ssh_client_curve25519_remove_callbacks(session); + break; +#endif + } +} + /** @brief Select the different methods on basis of client's and * server's kex messages, and watches out if a match is possible. */ -int ssh_kex_select_methods (ssh_session session){ - struct ssh_kex_struct *server = &session->next_crypto->server_kex; - struct ssh_kex_struct *client = &session->next_crypto->client_kex; +int ssh_kex_select_methods (ssh_session session) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + struct ssh_kex_struct *server = &crypto->server_kex; + struct ssh_kex_struct *client = &crypto->client_kex; char *ext_start = NULL; + const char *aead_hmac = NULL; + enum ssh_key_exchange_e kex_type; int i; - /* Here we should drop the ext-info-c from the list so we avoid matching. + /* Here we should drop the extensions from the list so we avoid matching. * it. We added it to the end, so we can just truncate the string here */ - ext_start = strstr(client->methods[SSH_KEX], ","KEX_EXTENSION_CLIENT); - if (ext_start != NULL) { - ext_start[0] = '\0'; + if (session->client) { + ext_start = strstr(client->methods[SSH_KEX], "," KEX_EXTENSION_CLIENT); + if (ext_start != NULL) { + ext_start[0] = '\0'; + } + } + if (session->server) { + ext_start = strstr(server->methods[SSH_KEX], "," KEX_STRICT_SERVER); + if (ext_start != NULL) { + ext_start[0] = '\0'; + } } for (i = 0; i < SSH_KEX_METHODS; i++) { - session->next_crypto->kex_methods[i]=ssh_find_matching(server->methods[i],client->methods[i]); - if(session->next_crypto->kex_methods[i] == NULL && i < SSH_LANG_C_S){ - ssh_set_error(session,SSH_FATAL,"kex error : no match for method %s: server [%s], client [%s]", - ssh_kex_descriptions[i],server->methods[i],client->methods[i]); + crypto->kex_methods[i] = ssh_find_matching(server->methods[i], + client->methods[i]); + + if (i == SSH_MAC_C_S || i == SSH_MAC_S_C) { + aead_hmac = ssh_find_aead_hmac(crypto->kex_methods[i - 2]); + if (aead_hmac) { + free(crypto->kex_methods[i]); + crypto->kex_methods[i] = strdup(aead_hmac); + } + } + if (crypto->kex_methods[i] == NULL && i < SSH_LANG_C_S) { + ssh_set_error(session, SSH_FATAL, + "kex error : no match for method %s: server [%s], " + "client [%s]", ssh_kex_descriptions[i], + server->methods[i], client->methods[i]); return SSH_ERROR; - } else if ((i >= SSH_LANG_C_S) && (session->next_crypto->kex_methods[i] == NULL)) { + } else if ((i >= SSH_LANG_C_S) && (crypto->kex_methods[i] == NULL)) { /* we can safely do that for languages */ - session->next_crypto->kex_methods[i] = strdup(""); + crypto->kex_methods[i] = strdup(""); } } - if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group1-sha1") == 0){ - session->next_crypto->kex_type=SSH_KEX_DH_GROUP1_SHA1; - } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group14-sha1") == 0){ - session->next_crypto->kex_type=SSH_KEX_DH_GROUP14_SHA1; - } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group14-sha256") == 0){ - session->next_crypto->kex_type=SSH_KEX_DH_GROUP14_SHA256; - } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group16-sha512") == 0){ - session->next_crypto->kex_type=SSH_KEX_DH_GROUP16_SHA512; - } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group18-sha512") == 0){ - session->next_crypto->kex_type=SSH_KEX_DH_GROUP18_SHA512; -#ifdef WITH_GEX - } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group-exchange-sha1") == 0){ - session->next_crypto->kex_type=SSH_KEX_DH_GEX_SHA1; - } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group-exchange-sha256") == 0){ - session->next_crypto->kex_type=SSH_KEX_DH_GEX_SHA256; -#endif /* WITH_GEX */ - } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp256") == 0){ - session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP256; - } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp384") == 0){ - session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP384; - } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp521") == 0){ - session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP521; - } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "curve25519-sha256@libssh.org") == 0){ - session->next_crypto->kex_type=SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG; - } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "curve25519-sha256") == 0){ - session->next_crypto->kex_type=SSH_KEX_CURVE25519_SHA256; - } - SSH_LOG(SSH_LOG_INFO, "Negotiated %s,%s,%s,%s,%s,%s,%s,%s,%s,%s", + + /* We can not set this value directly as the old value is needed to revert + * callbacks if we are client */ + kex_type = kex_select_kex_type(crypto->kex_methods[SSH_KEX]); + if (session->client && session->first_kex_follows_guess_wrong) { + SSH_LOG(SSH_LOG_DEBUG, "Our guess was wrong. Restarting the KEX"); + /* We need to remove the wrong callbacks and start kex again */ + revert_kex_callbacks(session); + session->dh_handshake_state = DH_STATE_INIT; + session->first_kex_follows_guess_wrong = false; + } + crypto->kex_type = kex_type; + + SSH_LOG(SSH_LOG_DEBUG, "Negotiated %s,%s,%s,%s,%s,%s,%s,%s,%s,%s", session->next_crypto->kex_methods[SSH_KEX], session->next_crypto->kex_methods[SSH_HOSTKEYS], session->next_crypto->kex_methods[SSH_CRYPT_C_S], @@ -825,62 +1021,116 @@ int ssh_kex_select_methods (ssh_session session){ /* this function only sends the predefined set of kex methods */ -int ssh_send_kex(ssh_session session, int server_kex) { - struct ssh_kex_struct *kex = (server_kex ? &session->next_crypto->server_kex : - &session->next_crypto->client_kex); - ssh_string str = NULL; - int i; - int rc; - - rc = ssh_buffer_pack(session->out_buffer, - "bP", - SSH2_MSG_KEXINIT, - 16, - kex->cookie); /* cookie */ - if (rc != SSH_OK) - goto error; - if (ssh_hashbufout_add_cookie(session) < 0) { - goto error; - } +int ssh_send_kex(ssh_session session) +{ + struct ssh_kex_struct *kex = (session->server ? + &session->next_crypto->server_kex : + &session->next_crypto->client_kex); + ssh_string str = NULL; + int i; + int rc; + int first_kex_packet_follows = 0; + + /* Only client can initiate the handshake methods we implement. If we + * already received the peer mechanisms, there is no point in guessing */ + if (session->client && + session->session_state != SSH_SESSION_STATE_KEXINIT_RECEIVED && + session->send_first_kex_follows) { + first_kex_packet_follows = 1; + } + + SSH_LOG(SSH_LOG_TRACE, + "Sending KEXINIT packet, first_kex_packet_follows = %d", + first_kex_packet_follows); + + rc = ssh_buffer_pack(session->out_buffer, + "bP", + SSH2_MSG_KEXINIT, + 16, + kex->cookie); /* cookie */ + if (rc != SSH_OK) + goto error; + if (ssh_hashbufout_add_cookie(session) < 0) { + goto error; + } + + ssh_list_kex(kex); - ssh_list_kex(kex); + for (i = 0; i < SSH_KEX_METHODS; i++) { + str = ssh_string_from_char(kex->methods[i]); + if (str == NULL) { + goto error; + } - for (i = 0; i < SSH_KEX_METHODS; i++) { - str = ssh_string_from_char(kex->methods[i]); - if (str == NULL) { - goto error; + rc = ssh_buffer_add_ssh_string(session->out_hashbuf, str); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_ssh_string(session->out_buffer, str); + if (rc < 0) { + goto error; + } + SSH_STRING_FREE(str); + str = NULL; } - if (ssh_buffer_add_ssh_string(session->out_hashbuf, str) < 0) { - goto error; + rc = ssh_buffer_pack(session->out_buffer, + "bd", + first_kex_packet_follows, + 0); + if (rc != SSH_OK) { + goto error; } - if (ssh_buffer_add_ssh_string(session->out_buffer, str) < 0) { - goto error; + + /* Prepare also the first_kex_packet_follows and reserved to 0 */ + rc = ssh_buffer_add_u8(session->out_hashbuf, first_kex_packet_follows); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u32(session->out_hashbuf, 0); + if (rc < 0) { + goto error; } - SSH_STRING_FREE(str); - str = NULL; - } - rc = ssh_buffer_pack(session->out_buffer, - "bd", - 0, - 0); - if (rc != SSH_OK) { - goto error; - } + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return -1; + } - if (ssh_packet_send(session) == SSH_ERROR) { - return -1; - } + session->flags |= SSH_SESSION_FLAG_KEXINIT_SENT; + SSH_LOG(SSH_LOG_PACKET, "SSH_MSG_KEXINIT sent"); + + /* If we indicated that we are sending the guessed key exchange packet, + * do it now. The packet is simple, but we need to do some preparations */ + if (first_kex_packet_follows == 1) { + char *list = kex->methods[SSH_KEX]; + char *colon = strchr(list, ','); + size_t kex_name_len = colon ? (size_t)(colon - list) : strlen(list); + char *kex_name = calloc(kex_name_len + 1, 1); + if (kex_name == NULL) { + ssh_set_error_oom(session); + goto error; + } + snprintf(kex_name, kex_name_len + 1, "%.*s", (int)kex_name_len, list); + SSH_LOG(SSH_LOG_TRACE, "Sending the first kex packet for %s", kex_name); + + session->next_crypto->kex_type = kex_select_kex_type(kex_name); + free(kex_name); + + /* run the first step of the DH handshake */ + session->dh_handshake_state = DH_STATE_INIT; + if (dh_handshake(session) == SSH_ERROR) { + goto error; + } + } + return 0; - SSH_LOG(SSH_LOG_PACKET, "SSH_MSG_KEXINIT sent"); - return 0; error: - ssh_buffer_reinit(session->out_buffer); - ssh_buffer_reinit(session->out_hashbuf); - SSH_STRING_FREE(str); + ssh_buffer_reinit(session->out_buffer); + ssh_buffer_reinit(session->out_hashbuf); + SSH_STRING_FREE(str); - return -1; + return -1; } /* @@ -923,7 +1173,7 @@ int ssh_send_rekex(ssh_session session) } session->dh_handshake_state = DH_STATE_INIT; - rc = ssh_send_kex(session, session->server); + rc = ssh_send_kex(session); if (rc < 0) { SSH_LOG(SSH_LOG_PACKET, "Failed to send kex"); return rc; @@ -948,13 +1198,13 @@ char *ssh_keep_known_algos(enum ssh_kex_types_e algo, const char *list) /** * @internal * - * @brief Return a new allocated string containing only the FIPS allowed + * @brief Return a newly allocated string containing only the FIPS allowed * algorithms from the list. * * @param[in] algo The type of the methods to filter * @param[in] list The list to be filtered * - * @return A new allocated list containing only the FIPS allowed algorithms from + * @return A newly allocated list containing only the FIPS allowed algorithms from * the list; NULL in case of error. */ char *ssh_keep_fips_algos(enum ssh_kex_types_e algo, const char *list) @@ -966,6 +1216,111 @@ char *ssh_keep_fips_algos(enum ssh_kex_types_e algo, const char *list) return ssh_find_all_matching(fips_methods[algo], list); } +/** + * @internal + * + * @brief Return a newly allocated string containing the default + * algorithms plus the algorithms specified in list. If the system + * runs in fips mode, this will add only fips approved algorithms. + * Empty list will cause error. + * + * @param[in] algo The type of the methods to filter + * @param[in] list The list to be appended + * + * @return A newly allocated list containing the default algorithms and the + * algorithms in list at the end; NULL in case of error. + */ +char *ssh_add_to_default_algos(enum ssh_kex_types_e algo, const char *list) +{ + char *tmp = NULL, *ret = NULL; + + if (algo > SSH_LANG_S_C || list == NULL || list[0] == '\0') { + return NULL; + } + + if (ssh_fips_mode()) { + tmp = ssh_append_without_duplicates(fips_methods[algo], list); + ret = ssh_find_all_matching(fips_methods[algo], tmp); + } else { + tmp = ssh_append_without_duplicates(default_methods[algo], list); + ret = ssh_find_all_matching(supported_methods[algo], tmp); + } + + free(tmp); + return ret; +} + +/** + * @internal + * + * @brief Return a newly allocated string containing the default + * algorithms excluding the algorithms specified in list. If the system + * runs in fips mode, this will remove from the fips_methods list. + * + * @param[in] algo The type of the methods to filter + * @param[in] list The list to be exclude + * + * @return A newly allocated list containing the default algorithms without the + * algorithms in list; NULL in case of error. + */ +char *ssh_remove_from_default_algos(enum ssh_kex_types_e algo, const char *list) +{ + if (algo > SSH_LANG_S_C) { + return NULL; + } + + if (list == NULL || list[0] == '\0') { + if (ssh_fips_mode()) { + return strdup(fips_methods[algo]); + } else { + return strdup(default_methods[algo]); + } + } + + if (ssh_fips_mode()) { + return ssh_remove_all_matching(fips_methods[algo], list); + } else { + return ssh_remove_all_matching(default_methods[algo], list); + } +} + +/** + * @internal + * + * @brief Return a newly allocated string containing the default + * algorithms with prioritized algorithms specified in list. If the + * algorithms are present in the default list they get prioritized, if not + * they are added to the front of the default list. If the system + * runs in fips mode, this will work with the fips_methods list. + * Empty list will cause error. + * + * @param[in] algo The type of the methods to filter + * @param[in] list The list to be pushed to priority + * + * @return A newly allocated list containing the default algorithms prioritized + * with the algorithms in list at the beginning of the list; NULL in case + * of error. + */ +char *ssh_prefix_default_algos(enum ssh_kex_types_e algo, const char *list) +{ + char *ret = NULL, *tmp = NULL; + + if (algo > SSH_LANG_S_C || list == NULL || list[0] == '\0') { + return NULL; + } + + if (ssh_fips_mode()) { + tmp = ssh_prefix_without_duplicates(fips_methods[algo], list); + ret = ssh_find_all_matching(fips_methods[algo], tmp); + } else { + tmp = ssh_prefix_without_duplicates(default_methods[algo], list); + ret = ssh_find_all_matching(supported_methods[algo], tmp); + } + + free(tmp); + return ret; +} + int ssh_make_sessionid(ssh_session session) { ssh_string num = NULL; @@ -973,10 +1328,18 @@ int ssh_make_sessionid(ssh_session session) ssh_buffer client_hash = NULL; ssh_buffer buf = NULL; ssh_string server_pubkey_blob = NULL; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum client_pubkey, server_pubkey; +#else + bignum client_pubkey = NULL, server_pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ #ifdef WITH_GEX +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum modulus, generator; -#endif +#else + bignum modulus = NULL, generator = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ +#endif /* WITH_GEX */ int rc = SSH_ERROR; buf = ssh_buffer_new(); @@ -1000,33 +1363,6 @@ int ssh_make_sessionid(ssh_session session) client_hash = session->in_hashbuf; } - /* - * Handle the two final fields for the KEXINIT message (RFC 4253 7.1): - * - * boolean first_kex_packet_follows - * uint32 0 (reserved for future extension) - */ - rc = ssh_buffer_add_u8(server_hash, 0); - if (rc < 0) { - goto error; - } - rc = ssh_buffer_add_u32(server_hash, 0); - if (rc < 0) { - goto error; - } - - /* These fields are handled for the server case in ssh_packet_kexinit. */ - if (session->client) { - rc = ssh_buffer_add_u8(client_hash, 0); - if (rc < 0) { - goto error; - } - rc = ssh_buffer_add_u32(client_hash, 0); - if (rc < 0) { - goto error; - } - } - rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); if (rc != SSH_OK) { goto error; @@ -1042,7 +1378,7 @@ int ssh_make_sessionid(ssh_session session) ssh_buffer_get(server_hash), server_pubkey_blob); SSH_STRING_FREE(server_pubkey_blob); - if(rc != SSH_OK){ + if (rc != SSH_OK){ goto error; } @@ -1069,6 +1405,10 @@ int ssh_make_sessionid(ssh_session session) if (rc != SSH_OK) { goto error; } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(client_pubkey); + bignum_safe_free(server_pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ break; #ifdef WITH_GEX case SSH_KEX_DH_GEX_SHA1: @@ -1100,6 +1440,10 @@ int ssh_make_sessionid(ssh_session session) if (rc != SSH_OK) { goto error; } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(modulus); + bignum_safe_free(generator); +#endif /* OPENSSL_VERSION_NUMBER */ break; #endif /* WITH_GEX */ #ifdef HAVE_ECDH @@ -1108,7 +1452,7 @@ int ssh_make_sessionid(ssh_session session) case SSH_KEX_ECDH_SHA2_NISTP521: if (session->next_crypto->ecdh_client_pubkey == NULL || session->next_crypto->ecdh_server_pubkey == NULL) { - SSH_LOG(SSH_LOG_WARNING, "ECDH parameted missing"); + SSH_LOG(SSH_LOG_TRACE, "ECDH parameter missing"); goto error; } rc = ssh_buffer_pack(buf, @@ -1119,7 +1463,7 @@ int ssh_make_sessionid(ssh_session session) goto error; } break; -#endif +#endif /* HAVE_ECDH */ #ifdef HAVE_CURVE25519 case SSH_KEX_CURVE25519_SHA256: case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: @@ -1134,7 +1478,7 @@ int ssh_make_sessionid(ssh_session session) goto error; } break; -#endif +#endif /* HAVE_CURVE25519 */ } rc = ssh_buffer_pack(buf, "B", session->next_crypto->shared_secret); if (rc != SSH_OK) { @@ -1216,12 +1560,14 @@ int ssh_make_sessionid(ssh_session session) } memcpy(session->next_crypto->session_id, session->next_crypto->secret_hash, session->next_crypto->digest_len); + /* Initial length is the same as secret hash */ + session->next_crypto->session_id_len = session->next_crypto->digest_len; } #ifdef DEBUG_CRYPTO - printf("Session hash: \n"); + SSH_LOG(SSH_LOG_DEBUG, "Session hash: \n"); ssh_log_hexdump("secret hash", session->next_crypto->secret_hash, session->next_crypto->digest_len); - ssh_log_hexdump("session id", session->next_crypto->session_id, session->next_crypto->digest_len); -#endif + ssh_log_hexdump("session id", session->next_crypto->session_id, session->next_crypto->session_id_len); +#endif /* DEBUG_CRYPTO */ rc = SSH_OK; error: @@ -1233,6 +1579,10 @@ error: session->out_hashbuf = NULL; SSH_STRING_FREE(num); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(client_pubkey); + bignum_safe_free(server_pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ return rc; } @@ -1417,7 +1767,7 @@ int ssh_generate_session_keys(ssh_session session) intkey_cli_to_srv_len); ssh_log_hexdump("Server to Client Integrity Key", intkey_srv_to_cli, intkey_srv_to_cli_len); -#endif +#endif /* DEBUG_CRYPTO */ rc = 0; error: diff --git a/src/known_hosts.c b/src/known_hosts.c index ec6da308..a1d9a432 100644 --- a/src/known_hosts.c +++ b/src/known_hosts.c @@ -45,6 +45,10 @@ # include <arpa/inet.h> #endif +#ifndef MAX_LINE_SIZE +#define MAX_LINE_SIZE 4096 +#endif + /** * @addtogroup libssh_session * @@ -61,12 +65,12 @@ * @param[out] file A pointer to the known host file. Could be pointing to * NULL at start. * - * @param[in] filename The file name of the known host file. + * @param[in] filename The filename of the known host file. * * @param[out] found_type A pointer to a string to be set with the found key * type. * - * @returns The found_type type of key (ie "dsa","ssh-rsa"). Don't + * @returns The found_type type of key (ie "ssh-rsa"). Don't * free that value. NULL if no match was found or the file * was not found. */ @@ -74,7 +78,7 @@ static struct ssh_tokens_st *ssh_get_knownhost_line(FILE **file, const char *filename, const char **found_type) { - char buffer[4096] = {0}; + char buffer[MAX_LINE_SIZE] = {0}; char *ptr; struct ssh_tokens_st *tokens; @@ -148,7 +152,7 @@ static int check_public_key(ssh_session session, char **tokens) { char *pubkey_64; int rc; - /* ssh-dss or ssh-rsa */ + /* ssh-rsa, ssh-ed25519, .. */ pubkey_64 = tokens[2]; pubkey_buffer = base64_to_bin(pubkey_64); @@ -206,8 +210,8 @@ static int match_hashed_host(const char *host, const char *sourcehash) HMACCTX mac; char *source; char *b64hash; - int match; - unsigned int size; + int match, rc; + size_t size; if (strncmp(sourcehash, "|1|", 3) != 0) { return 0; @@ -252,8 +256,20 @@ static int match_hashed_host(const char *host, const char *sourcehash) return 0; } size = sizeof(buffer); - hmac_update(mac, host, strlen(host)); - hmac_final(mac, buffer, &size); + rc = hmac_update(mac, host, strlen(host)); + if (rc != 1) { + ssh_buffer_free(salt); + ssh_buffer_free(hash); + + return 0; + } + rc = hmac_final(mac, buffer, &size); + if (rc != 1) { + ssh_buffer_free(salt); + ssh_buffer_free(hash); + + return 0; + } if (size == ssh_buffer_get_len(hash) && memcmp(buffer, ssh_buffer_get(hash), size) == 0) { @@ -431,7 +447,6 @@ char * ssh_dump_knownhost(ssh_session session) { ssh_key server_pubkey = NULL; char *host; char *hostport; - size_t len = 4096; char *buffer; char *b64_key; int rc; @@ -467,7 +482,7 @@ char * ssh_dump_knownhost(ssh_session session) { return NULL; } - buffer = calloc (1, 4096); + buffer = calloc (1, MAX_LINE_SIZE); if (!buffer) { SAFE_FREE(host); return NULL; @@ -480,7 +495,7 @@ char * ssh_dump_knownhost(ssh_session session) { return NULL; } - snprintf(buffer, len, + snprintf(buffer, MAX_LINE_SIZE, "%s %s %s\n", host, server_pubkey->type_c, @@ -513,10 +528,12 @@ int ssh_write_knownhost(ssh_session session) errno = 0; file = fopen(session->opts.knownhosts, "a"); if (file == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; if (errno == ENOENT) { dir = ssh_dirname(session->opts.knownhosts); if (dir == NULL) { - ssh_set_error(session, SSH_FATAL, "%s", strerror(errno)); + ssh_set_error(session, SSH_FATAL, + "%s", ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } @@ -524,7 +541,7 @@ int ssh_write_knownhost(ssh_session session) if (rc < 0) { ssh_set_error(session, SSH_FATAL, "Cannot create %s directory: %s", - dir, strerror(errno)); + dir, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); SAFE_FREE(dir); return SSH_ERROR; } @@ -536,13 +553,15 @@ int ssh_write_knownhost(ssh_session session) ssh_set_error(session, SSH_FATAL, "Couldn't open known_hosts file %s" " for appending: %s", - session->opts.knownhosts, strerror(errno)); + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } } else { ssh_set_error(session, SSH_FATAL, "Couldn't open known_hosts file %s for appending: %s", - session->opts.knownhosts, strerror(errno)); + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } } diff --git a/src/knownhosts.c b/src/knownhosts.c index fed75f90..c073b266 100644 --- a/src/knownhosts.c +++ b/src/knownhosts.c @@ -44,6 +44,10 @@ #include "libssh/knownhosts.h" #include "libssh/token.h" +#ifndef MAX_LINE_SIZE +#define MAX_LINE_SIZE 8192 +#endif + /** * @addtogroup libssh_session * @@ -54,8 +58,9 @@ static int hash_hostname(const char *name, unsigned char *salt, unsigned int salt_size, unsigned char **hash, - unsigned int *hash_size) + size_t *hash_size) { + int rc; HMACCTX mac_ctx; mac_ctx = hmac_init(salt, salt_size, SSH_HMAC_SHA1); @@ -63,8 +68,13 @@ static int hash_hostname(const char *name, return SSH_ERROR; } - hmac_update(mac_ctx, name, strlen(name)); - hmac_final(mac_ctx, *hash, hash_size); + rc = hmac_update(mac_ctx, name, strlen(name)); + if (rc != 1) + return SSH_ERROR; + + rc = hmac_final(mac_ctx, *hash, hash_size); + if (rc != 1) + return SSH_ERROR; return SSH_OK; } @@ -77,7 +87,7 @@ static int match_hashed_hostname(const char *host, const char *hashed_host) ssh_buffer hash = NULL; unsigned char hashed_buf[256] = {0}; unsigned char *hashed_buf_ptr = hashed_buf; - unsigned int hashed_buf_size = sizeof(hashed_buf); + size_t hashed_buf_size = sizeof(hashed_buf); int cmp; int rc; int match = 0; @@ -216,7 +226,7 @@ static int ssh_known_hosts_read_entries(const char *match, const char *filename, struct ssh_list **entries) { - char line[8192]; + char line[MAX_LINE_SIZE]; size_t lineno = 0; size_t len = 0; FILE *fp; @@ -224,8 +234,9 @@ static int ssh_known_hosts_read_entries(const char *match, fp = fopen(filename, "r"); if (fp == NULL) { - SSH_LOG(SSH_LOG_WARN, "Failed to open the known_hosts file '%s': %s", - filename, strerror(errno)); + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + SSH_LOG(SSH_LOG_TRACE, "Failed to open the known_hosts file '%s': %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); /* The missing file is not an error here */ return SSH_OK; } @@ -372,6 +383,7 @@ struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session) list = ssh_list_new(); if (list == NULL) { + ssh_set_error_oom(session); SAFE_FREE(host_port); return NULL; } @@ -468,24 +480,33 @@ static const char *ssh_known_host_sigs_from_hostkey_type(enum ssh_keytypes_e typ return "rsa-sha2-512,rsa-sha2-256,ssh-rsa"; case SSH_KEYTYPE_ED25519: return "ssh-ed25519"; -#ifdef HAVE_DSA - case SSH_KEYTYPE_DSS: - return "ssh-dss"; -#endif -#ifdef HAVE_ECDH + case SSH_KEYTYPE_SK_ED25519: + return "sk-ssh-ed25519@openssh.com"; +#ifdef HAVE_ECC case SSH_KEYTYPE_ECDSA_P256: return "ecdsa-sha2-nistp256"; case SSH_KEYTYPE_ECDSA_P384: return "ecdsa-sha2-nistp384"; case SSH_KEYTYPE_ECDSA_P521: return "ecdsa-sha2-nistp521"; + case SSH_KEYTYPE_SK_ECDSA: + return "sk-ecdsa-sha2-nistp256@openssh.com"; +#else + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + SSH_LOG(SSH_LOG_WARN, "ECDSA keys are not supported by this build"); + break; #endif case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_WARN, "The given type %d is not a base private key type " - "or is unsupported", type); - return NULL; + SSH_LOG(SSH_LOG_TRACE, + "The given type %d is not a base private key type " + "or is unsupported", + type); } + + return NULL; } /** @@ -567,6 +588,8 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session) entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); algo = ssh_known_host_sigs_from_hostkey_type(entry->publickey->type); if (algo == NULL) { + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); continue; } @@ -595,7 +618,7 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session) /** * @brief Parse a line from a known_hosts entry into a structure * - * This parses an known_hosts entry into a structure with the key in a libssh + * This parses a known_hosts entry into a structure with the key in a libssh * consumeable form. You can use the PKI key function to further work with it. * * @param[in] hostname The hostname to match the line to @@ -603,7 +626,7 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session) * @param[in] line The line to compare and parse if we have a hostname * match. * - * @param[in] entry A pointer to store the the allocated known_hosts + * @param[in] entry A pointer to store the allocated known_hosts * entry structure. The user needs to free the memory * using SSH_KNOWNHOSTS_ENTRY_FREE(). * @@ -616,6 +639,7 @@ int ssh_known_hosts_parse_line(const char *hostname, struct ssh_knownhosts_entry *e = NULL; char *known_host = NULL; char *p; + char *save_tok = NULL; enum ssh_keytypes_e key_type; int match = 0; int rc = SSH_OK; @@ -626,7 +650,7 @@ int ssh_known_hosts_parse_line(const char *hostname, } /* match pattern for hostname or hashed hostname */ - p = strtok(known_host, " "); + p = strtok_r(known_host, " ", &save_tok); if (p == NULL ) { free(known_host); return SSH_ERROR; @@ -647,14 +671,16 @@ int ssh_known_hosts_parse_line(const char *hostname, match = match_hashed_hostname(hostname, p); } - for (q = strtok(p, ","); + save_tok = NULL; + + for (q = strtok_r(p, ",", &save_tok); q != NULL; - q = strtok(NULL, ",")) { + q = strtok_r(NULL, ",", &save_tok)) { int cmp; if (q[0] == '[' && hostname[0] != '[') { /* Corner case: We have standard port so we do not have - * hostname in square braces. But the patern is enclosed + * hostname in square braces. But the pattern is enclosed * in braces with, possibly standard or wildcard, port. * We need to test against [host]:port pair here. */ @@ -697,7 +723,9 @@ int ssh_known_hosts_parse_line(const char *hostname, goto out; } - p = strtok(known_host, " "); + save_tok = NULL; + + p = strtok_r(known_host, " ", &save_tok); if (p == NULL ) { rc = SSH_ERROR; goto out; @@ -710,7 +738,7 @@ int ssh_known_hosts_parse_line(const char *hostname, } /* pubkey type */ - p = strtok(NULL, " "); + p = strtok_r(NULL, " ", &save_tok); if (p == NULL) { rc = SSH_ERROR; goto out; @@ -718,13 +746,13 @@ int ssh_known_hosts_parse_line(const char *hostname, key_type = ssh_key_type_from_name(p); if (key_type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "key type '%s' unknown!", p); + SSH_LOG(SSH_LOG_TRACE, "key type '%s' unknown!", p); rc = SSH_ERROR; goto out; } /* public key */ - p = strtok(NULL, " "); + p = strtok_r(NULL, " ", &save_tok); if (p == NULL) { rc = SSH_ERROR; goto out; @@ -734,7 +762,7 @@ int ssh_known_hosts_parse_line(const char *hostname, key_type, &e->publickey); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Failed to parse %s key for entry: %s!", ssh_key_type_to_char(key_type), e->unparsed); @@ -742,7 +770,7 @@ int ssh_known_hosts_parse_line(const char *hostname, } /* comment */ - p = strtok(NULL, " "); + p = strtok_r(NULL, " ", &save_tok); if (p != NULL) { p = strstr(line, p); if (p != NULL) { @@ -765,12 +793,12 @@ out: } /** - * @brief Check if the set hostname and port matches an entry in known_hosts. + * @brief Check if the set hostname and port match an entry in known_hosts. * - * This check if the set hostname and port has an entry in the known_hosts file. + * This check if the set hostname and port have an entry in the known_hosts file. * You need to set at least the hostname using ssh_options_set(). * - * @param[in] session The session with with the values set to check. + * @param[in] session The session with the values set to check. * * @return A ssh_known_hosts_e return value. */ @@ -805,7 +833,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) if (session->opts.knownhosts != NULL) { known_hosts_found = ssh_file_readaccess_ok(session->opts.knownhosts); if (!known_hosts_found) { - SSH_LOG(SSH_LOG_WARN, "Cannot access file %s", + SSH_LOG(SSH_LOG_TRACE, "Cannot access file %s", session->opts.knownhosts); } } @@ -814,7 +842,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) global_known_hosts_found = ssh_file_readaccess_ok(session->opts.global_knownhosts); if (!global_known_hosts_found) { - SSH_LOG(SSH_LOG_WARN, "Cannot access file %s", + SSH_LOG(SSH_LOG_TRACE, "Cannot access file %s", session->opts.global_knownhosts); } } @@ -883,18 +911,18 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) * * @param[in] session The session with information to export. * - * @param[in] pentry_string A pointer to a string to store the alloocated + * @param[in] pentry_string A pointer to a string to store the allocated * line of the entry. The user must free it using * ssh_string_free_char(). * - * @return SSH_OK on succcess, SSH_ERROR otherwise. + * @return SSH_OK on success, SSH_ERROR otherwise. */ int ssh_session_export_known_hosts_entry(ssh_session session, char **pentry_string) { ssh_key server_pubkey = NULL; char *host = NULL; - char entry_buf[4096] = {0}; + char entry_buf[MAX_LINE_SIZE] = {0}; char *b64_key = NULL; int rc; @@ -952,7 +980,7 @@ int ssh_session_export_known_hosts_entry(ssh_session session, } /** - * @brief Add the current connected server to the user known_hosts file. + * @brief Adds the currently connected server to the user known_hosts file. * * This adds the currently connected server to the known_hosts file by * appending a new line at the end. The global known_hosts file is considered @@ -970,6 +998,7 @@ int ssh_session_update_known_hosts(ssh_session session) size_t nwritten; size_t len; int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; if (session->opts.knownhosts == NULL) { rc = ssh_options_apply(session); @@ -985,7 +1014,8 @@ int ssh_session_update_known_hosts(ssh_session session) if (errno == ENOENT) { dir = ssh_dirname(session->opts.knownhosts); if (dir == NULL) { - ssh_set_error(session, SSH_FATAL, "%s", strerror(errno)); + ssh_set_error(session, SSH_FATAL, "%s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } @@ -993,7 +1023,8 @@ int ssh_session_update_known_hosts(ssh_session session) if (rc < 0) { ssh_set_error(session, SSH_FATAL, "Cannot create %s directory: %s", - dir, strerror(errno)); + dir, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); SAFE_FREE(dir); return SSH_ERROR; } @@ -1005,7 +1036,8 @@ int ssh_session_update_known_hosts(ssh_session session) ssh_set_error(session, SSH_FATAL, "Couldn't open known_hosts file %s" " for appending: %s", - session->opts.knownhosts, strerror(errno)); + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } } else { @@ -1028,7 +1060,8 @@ int ssh_session_update_known_hosts(ssh_session session) if (nwritten != len || ferror(fp)) { ssh_set_error(session, SSH_FATAL, "Couldn't append to known_hosts file %s: %s", - session->opts.knownhosts, strerror(errno)); + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); fclose(fp); return SSH_ERROR; } @@ -1103,7 +1136,7 @@ ssh_known_hosts_check_server_key(const char *hosts_entry, } /** - * @brief Get the known_hosts entry for the current connected session. + * @brief Get the known_hosts entry for the currently connected session. * * @param[in] session The session to validate. * @@ -1122,7 +1155,7 @@ ssh_known_hosts_check_server_key(const char *hosts_entry, * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The * host is thus unknown. File will be * created if host key is accepted.\n - * SSH_KNOWN_HOSTS_ERROR: There had been an eror checking the host. + * SSH_KNOWN_HOSTS_ERROR: There had been an error checking the host. * * @see ssh_knownhosts_entry_free() */ @@ -1191,7 +1224,7 @@ ssh_session_get_known_hosts_entry(ssh_session session, * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The * host is thus unknown. File will be * created if host key is accepted.\n - * SSH_KNOWN_HOSTS_ERROR: There had been an eror checking the host. + * SSH_KNOWN_HOSTS_ERROR: There had been an error checking the host. * * @see ssh_knownhosts_entry_free() */ diff --git a/src/legacy.c b/src/legacy.c index 8ae3d920..72ff7f36 100644 --- a/src/legacy.c +++ b/src/legacy.c @@ -20,7 +20,7 @@ */ /** functions in that file are wrappers to the newly named functions. All - * of them are depreciated, but these wrapper will avoid breaking backward + * of them are depreciated, but these wrappers will avoid breaking backward * compatibility */ @@ -83,12 +83,18 @@ int ssh_userauth_pubkey(ssh_session session, key->type = privatekey->type; key->type_c = ssh_key_type_to_char(key->type); key->flags = SSH_KEY_FLAG_PRIVATE|SSH_KEY_FLAG_PUBLIC; - key->dsa = privatekey->dsa_priv; +#ifndef HAVE_LIBCRYPTO key->rsa = privatekey->rsa_priv; +#else + key->key = privatekey->key_priv; +#endif /* HAVE_LIBCRYPTO */ rc = ssh_userauth_publickey(session, username, key); - key->dsa = NULL; +#ifndef HAVE_LIBCRYPTO key->rsa = NULL; +#else + key->key = NULL; +#endif /* HAVE_LIBCRYPTO */ ssh_key_free(key); return rc; @@ -164,7 +170,7 @@ int channel_change_pty_size(ssh_channel channel,int cols,int rows){ } ssh_channel channel_forward_accept(ssh_session session, int timeout_ms){ - return ssh_channel_accept_forward(session, timeout_ms, NULL); + return ssh_channel_open_forward_port(session, timeout_ms, NULL, NULL, NULL); } int channel_close(ssh_channel channel){ @@ -350,22 +356,15 @@ void publickey_free(ssh_public_key key) { } switch(key->type) { - case SSH_KEYTYPE_DSS: -#ifdef HAVE_LIBGCRYPT - gcry_sexp_release(key->dsa_pub); -#elif defined HAVE_LIBCRYPTO - DSA_free(key->dsa_pub); -#endif - break; case SSH_KEYTYPE_RSA: #ifdef HAVE_LIBGCRYPT gcry_sexp_release(key->rsa_pub); #elif defined HAVE_LIBCRYPTO - RSA_free(key->rsa_pub); + EVP_PKEY_free(key->key_pub); #elif defined HAVE_LIBMBEDCRYPTO mbedtls_pk_free(key->rsa_pub); SAFE_FREE(key->rsa_pub); -#endif +#endif /* HAVE_LIBGCRYPT */ break; default: break; @@ -387,12 +386,18 @@ ssh_public_key publickey_from_privatekey(ssh_private_key prv) { privkey->type = prv->type; privkey->type_c = ssh_key_type_to_char(privkey->type); privkey->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; - privkey->dsa = prv->dsa_priv; +#ifndef HAVE_LIBCRYPTO privkey->rsa = prv->rsa_priv; +#else + privkey->key = prv->key_priv; +#endif /* HAVE_LIBCRYPTO */ rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); - privkey->dsa = NULL; +#ifndef HAVE_LIBCRYPTO privkey->rsa = NULL; +#else + privkey->key = NULL; +#endif /* HAVE_LIBCRYPTO */ ssh_key_free(privkey); if (rc < 0) { return NULL; @@ -438,11 +443,15 @@ ssh_private_key privatekey_from_file(ssh_session session, } privkey->type = key->type; - privkey->dsa_priv = key->dsa; +#ifndef HAVE_LIBCRYPTO privkey->rsa_priv = key->rsa; - key->dsa = NULL; key->rsa = NULL; +#else + privkey->key_priv = key->key; + + key->key = NULL; +#endif /* HAVE_LIBCRYPTO */ ssh_key_free(key); @@ -461,15 +470,13 @@ void privatekey_free(ssh_private_key prv) { } #ifdef HAVE_LIBGCRYPT - gcry_sexp_release(prv->dsa_priv); gcry_sexp_release(prv->rsa_priv); #elif defined HAVE_LIBCRYPTO - DSA_free(prv->dsa_priv); - RSA_free(prv->rsa_priv); + EVP_PKEY_free(prv->key_priv); #elif defined HAVE_LIBMBEDCRYPTO mbedtls_pk_free(prv->rsa_priv); SAFE_FREE(prv->rsa_priv); -#endif +#endif /* HAVE_LIBGCRYPT */ memset(prv, 0, sizeof(struct ssh_private_key_struct)); SAFE_FREE(prv); } @@ -530,10 +537,13 @@ ssh_public_key publickey_from_string(ssh_session session, ssh_string pubkey_s) { pubkey->type = key->type; pubkey->type_c = key->type_c; - pubkey->dsa_pub = key->dsa; - key->dsa = NULL; +#ifndef HAVE_LIBCRYPTO pubkey->rsa_pub = key->rsa; key->rsa = NULL; +#else + pubkey->key_pub = key->key; + key->key = NULL; +#endif /* HAVE_LIBCRYPTO */ ssh_key_free(key); @@ -557,16 +567,22 @@ ssh_string publickey_to_string(ssh_public_key pubkey) { key->type = pubkey->type; key->type_c = pubkey->type_c; - key->dsa = pubkey->dsa_pub; +#ifndef HAVE_LIBCRYPTO key->rsa = pubkey->rsa_pub; +#else + key->key = pubkey->key_pub; +#endif /* HAVE_LIBCRYPTO */ rc = ssh_pki_export_pubkey_blob(key, &key_blob); if (rc < 0) { key_blob = NULL; } - key->dsa = NULL; +#ifndef HAVE_LIBCRYPTO key->rsa = NULL; +#else + key->key = NULL; +#endif /* HAVE_LIBCRYPTO */ ssh_key_free(key); return key_blob; @@ -622,8 +638,12 @@ int ssh_publickey_to_file(ssh_session session, fp = fopen(file, "w+"); if (fp == NULL) { - ssh_set_error(session, SSH_REQUEST_DENIED, - "Error opening %s: %s", file, strerror(errno)); + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Error opening %s: %s", + file, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } @@ -735,7 +755,7 @@ int ssh_accept(ssh_session session) { } int channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) { - return ssh_channel_write(channel, data, len); + return ssh_channel_write_stderr(channel, data, len); } /** @deprecated diff --git a/src/libcrypto-compat.c b/src/libcrypto-compat.c deleted file mode 100644 index 01ca70e7..00000000 --- a/src/libcrypto-compat.c +++ /dev/null @@ -1,411 +0,0 @@ -/* - * Copyright 2016 The OpenSSL Project Authors. All Rights Reserved. - * - * Licensed under the OpenSSL license (the "License"). You may not use - * this file except in compliance with the License. You can obtain a copy - * in the file LICENSE in the source distribution or at - * https://www.openssl.org/source/license.html - */ - -#include "config.h" - -#include <string.h> -#include "libcrypto-compat.h" - -#ifndef OPENSSL_NO_ENGINE -#include <openssl/engine.h> -#endif - -static void *OPENSSL_zalloc(size_t num) -{ - void *ret = OPENSSL_malloc(num); - - if (ret != NULL) - memset(ret, 0, num); - return ret; -} - -int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) -{ - /* If the fields n and e in r are NULL, the corresponding input - * parameters MUST be non-NULL for n and e. d may be - * left NULL (in case only the public key is used). - */ - if ((r->n == NULL && n == NULL) - || (r->e == NULL && e == NULL)) - return 0; - - if (n != NULL) { - BN_free(r->n); - r->n = n; - } - if (e != NULL) { - BN_free(r->e); - r->e = e; - } - if (d != NULL) { - BN_free(r->d); - r->d = d; - } - - return 1; -} - -int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q) -{ - /* If the fields p and q in r are NULL, the corresponding input - * parameters MUST be non-NULL. - */ - if ((r->p == NULL && p == NULL) - || (r->q == NULL && q == NULL)) - return 0; - - if (p != NULL) { - BN_free(r->p); - r->p = p; - } - if (q != NULL) { - BN_free(r->q); - r->q = q; - } - - return 1; -} - -int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) -{ - /* If the fields dmp1, dmq1 and iqmp in r are NULL, the corresponding input - * parameters MUST be non-NULL. - */ - if ((r->dmp1 == NULL && dmp1 == NULL) - || (r->dmq1 == NULL && dmq1 == NULL) - || (r->iqmp == NULL && iqmp == NULL)) - return 0; - - if (dmp1 != NULL) { - BN_free(r->dmp1); - r->dmp1 = dmp1; - } - if (dmq1 != NULL) { - BN_free(r->dmq1); - r->dmq1 = dmq1; - } - if (iqmp != NULL) { - BN_free(r->iqmp); - r->iqmp = iqmp; - } - - return 1; -} - -void RSA_get0_key(const RSA *r, - const BIGNUM **n, const BIGNUM **e, const BIGNUM **d) -{ - if (n != NULL) - *n = r->n; - if (e != NULL) - *e = r->e; - if (d != NULL) - *d = r->d; -} - -void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q) -{ - if (p != NULL) - *p = r->p; - if (q != NULL) - *q = r->q; -} - -void RSA_get0_crt_params(const RSA *r, - const BIGNUM **dmp1, const BIGNUM **dmq1, - const BIGNUM **iqmp) -{ - if (dmp1 != NULL) - *dmp1 = r->dmp1; - if (dmq1 != NULL) - *dmq1 = r->dmq1; - if (iqmp != NULL) - *iqmp = r->iqmp; -} - -void DSA_get0_pqg(const DSA *d, - const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) -{ - if (p != NULL) - *p = d->p; - if (q != NULL) - *q = d->q; - if (g != NULL) - *g = d->g; -} - -int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g) -{ - /* If the fields p, q and g in d are NULL, the corresponding input - * parameters MUST be non-NULL. - */ - if ((d->p == NULL && p == NULL) - || (d->q == NULL && q == NULL) - || (d->g == NULL && g == NULL)) - return 0; - - if (p != NULL) { - BN_free(d->p); - d->p = p; - } - if (q != NULL) { - BN_free(d->q); - d->q = q; - } - if (g != NULL) { - BN_free(d->g); - d->g = g; - } - - return 1; -} - -void DSA_get0_key(const DSA *d, - const BIGNUM **pub_key, const BIGNUM **priv_key) -{ - if (pub_key != NULL) - *pub_key = d->pub_key; - if (priv_key != NULL) - *priv_key = d->priv_key; -} - -int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key) -{ - /* If the field pub_key in d is NULL, the corresponding input - * parameters MUST be non-NULL. The priv_key field may - * be left NULL. - */ - if (d->pub_key == NULL && pub_key == NULL) - return 0; - - if (pub_key != NULL) { - BN_free(d->pub_key); - d->pub_key = pub_key; - } - if (priv_key != NULL) { - BN_free(d->priv_key); - d->priv_key = priv_key; - } - - return 1; -} - -void DSA_SIG_get0(const DSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) -{ - if (pr != NULL) - *pr = sig->r; - if (ps != NULL) - *ps = sig->s; -} - -int DSA_SIG_set0(DSA_SIG *sig, BIGNUM *r, BIGNUM *s) -{ - if (r == NULL || s == NULL) - return 0; - BN_clear_free(sig->r); - BN_clear_free(sig->s); - sig->r = r; - sig->s = s; - return 1; -} - -void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) -{ - if (pr != NULL) - *pr = sig->r; - if (ps != NULL) - *ps = sig->s; -} - -int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s) -{ - if (r == NULL || s == NULL) - return 0; - BN_clear_free(sig->r); - BN_clear_free(sig->s); - sig->r = r; - sig->s = s; - return 1; -} - -EVP_MD_CTX *EVP_MD_CTX_new(void) -{ - return OPENSSL_zalloc(sizeof(EVP_MD_CTX)); -} - -static void OPENSSL_clear_free(void *str, size_t num) -{ - if (str == NULL) - return; - if (num) - OPENSSL_cleanse(str, num); - OPENSSL_free(str); -} - -/* This call frees resources associated with the context */ -int EVP_MD_CTX_reset(EVP_MD_CTX *ctx) -{ - if (ctx == NULL) - return 1; - - /* - * Don't assume ctx->md_data was cleaned in EVP_Digest_Final, because - * sometimes only copies of the context are ever finalised. - */ - if (ctx->digest && ctx->digest->cleanup - && !EVP_MD_CTX_test_flags(ctx, EVP_MD_CTX_FLAG_CLEANED)) - ctx->digest->cleanup(ctx); - if (ctx->digest && ctx->digest->ctx_size && ctx->md_data - && !EVP_MD_CTX_test_flags(ctx, EVP_MD_CTX_FLAG_REUSE)) { - OPENSSL_clear_free(ctx->md_data, ctx->digest->ctx_size); - } - EVP_PKEY_CTX_free(ctx->pctx); -#ifndef OPENSSL_NO_ENGINE - ENGINE_finish(ctx->engine); -#endif - OPENSSL_cleanse(ctx, sizeof(*ctx)); - - return 1; -} - -void EVP_MD_CTX_free(EVP_MD_CTX *ctx) -{ - EVP_MD_CTX_reset(ctx); - OPENSSL_free(ctx); -} - -int EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX *ctx) -{ - EVP_CIPHER_CTX_init(ctx); - return 1; -} - -HMAC_CTX *HMAC_CTX_new(void) -{ - HMAC_CTX *ctx = OPENSSL_zalloc(sizeof(HMAC_CTX)); - - if (ctx != NULL) { - if (!HMAC_CTX_reset(ctx)) { - HMAC_CTX_free(ctx); - return NULL; - } - } - return ctx; -} - -static void hmac_ctx_cleanup(HMAC_CTX *ctx) -{ - EVP_MD_CTX_reset(&ctx->i_ctx); - EVP_MD_CTX_reset(&ctx->o_ctx); - EVP_MD_CTX_reset(&ctx->md_ctx); - ctx->md = NULL; - ctx->key_length = 0; - OPENSSL_cleanse(ctx->key, sizeof(ctx->key)); -} - -void HMAC_CTX_free(HMAC_CTX *ctx) -{ - if (ctx != NULL) { - hmac_ctx_cleanup(ctx); -#if OPENSSL_VERSION_NUMBER > 0x10100000L - EVP_MD_CTX_free(&ctx->i_ctx); - EVP_MD_CTX_free(&ctx->o_ctx); - EVP_MD_CTX_free(&ctx->md_ctx); -#endif - OPENSSL_free(ctx); - } -} - -int HMAC_CTX_reset(HMAC_CTX *ctx) -{ - HMAC_CTX_init(ctx); - return 1; -} - -#ifndef HAVE_OPENSSL_EVP_CIPHER_CTX_NEW -EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void) -{ - return OPENSSL_zalloc(sizeof(EVP_CIPHER_CTX)); -} - -void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx) -{ - /* EVP_CIPHER_CTX_reset(ctx); alias */ - EVP_CIPHER_CTX_init(ctx); - OPENSSL_free(ctx); -} -#endif - -void DH_get0_pqg(const DH *dh, - const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) -{ - if (p) { - *p = dh->p; - } - if (q) { - *q = NULL; - } - if (g) { - *g = dh->g; - } -} - -int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) -{ - if (p) { - if (dh->p) { - BN_free(dh->p); - } - dh->p = p; - } - if (g) { - if (dh->g) { - BN_free(dh->g); - } - dh->g = g; - } - return 1; -} - -void DH_get0_key(const DH *dh, - const BIGNUM **pub_key, const BIGNUM **priv_key) -{ - if (pub_key) { - *pub_key = dh->pub_key; - } - if (priv_key) { - *priv_key = dh->priv_key; - } -} - -int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key) -{ - if (pub_key) { - if (dh->pub_key) { - BN_free(dh->pub_key); - } - dh->pub_key = pub_key; - } - if (priv_key) { - if (dh->priv_key) { - BN_free(dh->priv_key); - } - dh->priv_key = priv_key; - } - return 1; -} - -const char *OpenSSL_version(int type) -{ - return SSLeay_version(type); -} -unsigned long OpenSSL_version_num(void) -{ - return SSLeay(); -} diff --git a/src/libcrypto-compat.h b/src/libcrypto-compat.h index 0082e207..0f2dc184 100644 --- a/src/libcrypto-compat.h +++ b/src/libcrypto-compat.h @@ -2,54 +2,13 @@ #define LIBCRYPTO_COMPAT_H #include <openssl/opensslv.h> -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#include <openssl/rsa.h> -#include <openssl/dsa.h> -#include <openssl/ecdsa.h> -#include <openssl/dh.h> -#include <openssl/evp.h> -#include <openssl/hmac.h> -#include <openssl/bn.h> - -int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d); -int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q); -int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp); -void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d); -void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q); -void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp); - -void DSA_get0_pqg(const DSA *d, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g); -int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g); -void DSA_get0_key(const DSA *d, const BIGNUM **pub_key, const BIGNUM **priv_key); -int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key); - -void DSA_SIG_get0(const DSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps); -int DSA_SIG_set0(DSA_SIG *sig, BIGNUM *r, BIGNUM *s); - -void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps); -int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s); - -int EVP_MD_CTX_reset(EVP_MD_CTX *ctx); -EVP_MD_CTX *EVP_MD_CTX_new(void); -void EVP_MD_CTX_free(EVP_MD_CTX *ctx); - -int EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX *ctx); - -HMAC_CTX *HMAC_CTX_new(void); -int HMAC_CTX_reset(HMAC_CTX *ctx); -void HMAC_CTX_free(HMAC_CTX *ctx); - -void DH_get0_pqg(const DH *dh, - const BIGNUM **p, const BIGNUM **q, const BIGNUM **g); -int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g); -void DH_get0_key(const DH *dh, - const BIGNUM **pub_key, const BIGNUM **priv_key); -int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key); - -const char *OpenSSL_version(int type); -unsigned long OpenSSL_version_num(void); +#define NISTP256 "P-256" +#define NISTP384 "P-384" +#define NISTP521 "P-521" +#if OPENSSL_VERSION_NUMBER < 0x30000000L +#define EVP_PKEY_eq EVP_PKEY_cmp #endif /* OPENSSL_VERSION_NUMBER */ #endif /* LIBCRYPTO_COMPAT_H */ diff --git a/src/libcrypto.c b/src/libcrypto.c index 96abec14..f45ffa96 100644 --- a/src/libcrypto.c +++ b/src/libcrypto.c @@ -26,26 +26,31 @@ #include <string.h> #ifdef HAVE_SYS_TIME_H #include <sys/time.h> -#endif +#endif /* HAVE_SYS_TIME_H */ #include "libssh/priv.h" #include "libssh/session.h" #include "libssh/crypto.h" #include "libssh/wrapper.h" #include "libssh/libcrypto.h" -#if defined(HAVE_OPENSSL_EVP_CHACHA20) && defined(HAVE_OPENSSL_EVP_POLY1305) +#include "libssh/pki.h" +#ifdef HAVE_OPENSSL_EVP_CHACHA20 #include "libssh/bytearray.h" #include "libssh/chacha20-poly1305-common.h" #endif #ifdef HAVE_LIBCRYPTO +#include <openssl/opensslv.h> #include <openssl/sha.h> #include <openssl/md5.h> -#include <openssl/dsa.h> +#if OPENSSL_VERSION_NUMBER < 0x30000000L #include <openssl/rsa.h> #include <openssl/hmac.h> -#include <openssl/opensslv.h> +#else +#include <openssl/param_build.h> +#include <openssl/core_names.h> +#endif /* OPENSSL_VERSION_NUMBER */ #include <openssl/rand.h> #include <openssl/engine.h> @@ -54,11 +59,11 @@ #ifdef HAVE_OPENSSL_AES_H #define HAS_AES #include <openssl/aes.h> -#endif +#endif /* HAVE_OPENSSL_AES_H */ #ifdef HAVE_OPENSSL_DES_H #define HAS_DES #include <openssl/des.h> -#endif +#endif /* HAVE_OPENSSL_DES_H */ #if (defined(HAVE_VALGRIND_VALGRIND_H) && defined(HAVE_OPENSSL_IA32CAP_LOC)) #include <valgrind/valgrind.h> @@ -67,18 +72,19 @@ #include "libssh/crypto.h" -#ifdef HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID +#ifdef HAVE_OPENSSL_EVP_KDF_CTX #include <openssl/kdf.h> -#endif - -#ifdef HAVE_OPENSSL_CRYPTO_CTR128_ENCRYPT -#include <openssl/modes.h> -#endif +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include <openssl/param_build.h> +#include <openssl/core_names.h> +#endif /* OPENSSL_VERSION_NUMBER */ +#endif /* HAVE_OPENSSL_EVP_KDF_CTX */ #include "libssh/crypto.h" static int libcrypto_initialized = 0; + void ssh_reseed(void){ #ifndef _WIN32 struct timeval tv; @@ -87,270 +93,42 @@ void ssh_reseed(void){ #endif } -/** - * @brief Get random bytes - * - * Make sure to always check the return code of this function! - * - * @param[in] where The buffer to fill with random bytes - * - * @param[in] len The size of the buffer to fill. - * - * @param[in] strong Use a strong or private RNG source. - * - * @return 1 on success, 0 on error. - */ -int ssh_get_random(void *where, int len, int strong) -{ -#ifdef HAVE_OPENSSL_RAND_PRIV_BYTES - if (strong) { - /* Returns -1 when not supported, 0 on error, 1 on success */ - return !!RAND_priv_bytes(where, len); - } -#else - (void)strong; -#endif /* HAVE_RAND_PRIV_BYTES */ - - /* Returns -1 when not supported, 0 on error, 1 on success */ - return !!RAND_bytes(where, len); -} +#ifndef WITH_PKCS11_PROVIDER +static ENGINE *engine = NULL; -SHACTX sha1_init(void) +ENGINE *pki_get_engine(void) { - int rc; - SHACTX c = EVP_MD_CTX_create(); - if (c == NULL) { - return NULL; - } - EVP_MD_CTX_init(c); - rc = EVP_DigestInit_ex(c, EVP_sha1(), NULL); - if (rc == 0) { - EVP_MD_CTX_destroy(c); - c = NULL; - } - return c; -} - -void sha1_update(SHACTX c, const void *data, unsigned long len) -{ - EVP_DigestUpdate(c, data, len); -} + int ok; -void sha1_final(unsigned char *md, SHACTX c) -{ - unsigned int mdlen = 0; - - EVP_DigestFinal(c, md, &mdlen); - EVP_MD_CTX_destroy(c); -} - -void sha1(const unsigned char *digest, int len, unsigned char *hash) -{ - SHACTX c = sha1_init(); - if (c != NULL) { - sha1_update(c, digest, len); - sha1_final(hash, c); - } -} + if (engine == NULL) { + ENGINE_load_builtin_engines(); -#ifdef HAVE_OPENSSL_ECC -static const EVP_MD *nid_to_evpmd(int nid) -{ - switch (nid) { - case NID_X9_62_prime256v1: - return EVP_sha256(); - case NID_secp384r1: - return EVP_sha384(); - case NID_secp521r1: - return EVP_sha512(); - default: + engine = ENGINE_by_id("pkcs11"); + if (engine == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Could not load the engine: %s", + ERR_error_string(ERR_get_error(), NULL)); return NULL; - } - - return NULL; -} - -void evp(int nid, unsigned char *digest, int len, unsigned char *hash, unsigned int *hlen) -{ - const EVP_MD *evp_md = nid_to_evpmd(nid); - EVP_MD_CTX *md = EVP_MD_CTX_new(); - - EVP_DigestInit(md, evp_md); - EVP_DigestUpdate(md, digest, len); - EVP_DigestFinal(md, hash, hlen); - EVP_MD_CTX_free(md); -} - -EVPCTX evp_init(int nid) -{ - const EVP_MD *evp_md = nid_to_evpmd(nid); - - EVPCTX ctx = EVP_MD_CTX_new(); - if (ctx == NULL) { - return NULL; - } - - EVP_DigestInit(ctx, evp_md); - - return ctx; -} - -void evp_update(EVPCTX ctx, const void *data, unsigned long len) -{ - EVP_DigestUpdate(ctx, data, len); -} - -void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) -{ - EVP_DigestFinal(ctx, md, mdlen); - EVP_MD_CTX_free(ctx); -} -#endif - -SHA256CTX sha256_init(void) -{ - int rc; - SHA256CTX c = EVP_MD_CTX_create(); - if (c == NULL) { - return NULL; - } - EVP_MD_CTX_init(c); - rc = EVP_DigestInit_ex(c, EVP_sha256(), NULL); - if (rc == 0) { - EVP_MD_CTX_destroy(c); - c = NULL; - } - return c; -} - -void sha256_update(SHA256CTX c, const void *data, unsigned long len) -{ - EVP_DigestUpdate(c, data, len); -} - -void sha256_final(unsigned char *md, SHA256CTX c) -{ - unsigned int mdlen = 0; - - EVP_DigestFinal(c, md, &mdlen); - EVP_MD_CTX_destroy(c); -} - -void sha256(const unsigned char *digest, int len, unsigned char *hash) -{ - SHA256CTX c = sha256_init(); - if (c != NULL) { - sha256_update(c, digest, len); - sha256_final(hash, c); - } -} - -SHA384CTX sha384_init(void) -{ - int rc; - SHA384CTX c = EVP_MD_CTX_create(); - if (c == NULL) { - return NULL; - } - EVP_MD_CTX_init(c); - rc = EVP_DigestInit_ex(c, EVP_sha384(), NULL); - if (rc == 0) { - EVP_MD_CTX_destroy(c); - c = NULL; - } - return c; -} - -void sha384_update(SHA384CTX c, const void *data, unsigned long len) -{ - EVP_DigestUpdate(c, data, len); -} - -void sha384_final(unsigned char *md, SHA384CTX c) -{ - unsigned int mdlen = 0; - - EVP_DigestFinal(c, md, &mdlen); - EVP_MD_CTX_destroy(c); -} - -void sha384(const unsigned char *digest, int len, unsigned char *hash) -{ - SHA384CTX c = sha384_init(); - if (c != NULL) { - sha384_update(c, digest, len); - sha384_final(hash, c); - } -} - -SHA512CTX sha512_init(void) -{ - int rc = 0; - SHA512CTX c = EVP_MD_CTX_create(); - if (c == NULL) { - return NULL; - } - EVP_MD_CTX_init(c); - rc = EVP_DigestInit_ex(c, EVP_sha512(), NULL); - if (rc == 0) { - EVP_MD_CTX_destroy(c); - c = NULL; - } - return c; -} - -void sha512_update(SHA512CTX c, const void *data, unsigned long len) -{ - EVP_DigestUpdate(c, data, len); -} - -void sha512_final(unsigned char *md, SHA512CTX c) -{ - unsigned int mdlen = 0; - - EVP_DigestFinal(c, md, &mdlen); - EVP_MD_CTX_destroy(c); -} - -void sha512(const unsigned char *digest, int len, unsigned char *hash) -{ - SHA512CTX c = sha512_init(); - if (c != NULL) { - sha512_update(c, digest, len); - sha512_final(hash, c); - } -} + } + SSH_LOG(SSH_LOG_DEBUG, "Engine loaded successfully"); + + ok = ENGINE_init(engine); + if (!ok) { + SSH_LOG(SSH_LOG_TRACE, + "Could not initialize the engine: %s", + ERR_error_string(ERR_get_error(), NULL)); + ENGINE_free(engine); + return NULL; + } -MD5CTX md5_init(void) -{ - int rc; - MD5CTX c = EVP_MD_CTX_create(); - if (c == NULL) { - return NULL; - } - EVP_MD_CTX_init(c); - rc = EVP_DigestInit_ex(c, EVP_md5(), NULL); - if(rc == 0) { - EVP_MD_CTX_destroy(c); - c = NULL; + SSH_LOG(SSH_LOG_DEBUG, "Engine init success"); } - return c; -} - -void md5_update(MD5CTX c, const void *data, unsigned long len) -{ - EVP_DigestUpdate(c, data, len); -} - -void md5_final(unsigned char *md, MD5CTX c) -{ - unsigned int mdlen = 0; - - EVP_DigestFinal(c, md, &mdlen); - EVP_MD_CTX_destroy(c); + return engine; } +#endif /* WITH_PKCS11_PROVIDER */ -#ifdef HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID +#ifdef HAVE_OPENSSL_EVP_KDF_CTX +#if OPENSSL_VERSION_NUMBER < 0x30000000L static const EVP_MD *sshkdf_digest_to_md(enum ssh_kdf_digest digest_type) { switch (digest_type) { @@ -365,19 +143,50 @@ static const EVP_MD *sshkdf_digest_to_md(enum ssh_kdf_digest digest_type) } return NULL; } +#else +static const char *sshkdf_digest_to_md(enum ssh_kdf_digest digest_type) +{ + switch (digest_type) { + case SSH_KDF_SHA1: + return SN_sha1; + case SSH_KDF_SHA256: + return SN_sha256; + case SSH_KDF_SHA384: + return SN_sha384; + case SSH_KDF_SHA512: + return SN_sha512; + } + return NULL; +} +#endif /* OPENSSL_VERSION_NUMBER */ int ssh_kdf(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, - int key_type, unsigned char *output, + uint8_t key_type, unsigned char *output, size_t requested_len) { + int rc = -1; +#if OPENSSL_VERSION_NUMBER < 0x30000000L EVP_KDF_CTX *ctx = EVP_KDF_CTX_new_id(EVP_KDF_SSHKDF); - int rc; +#else + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "SSHKDF", NULL); + EVP_KDF_CTX *ctx = EVP_KDF_CTX_new(kdf); + OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); + OSSL_PARAM *params = NULL; + const char *md = sshkdf_digest_to_md(crypto->digest_type); + + EVP_KDF_free(kdf); + if (param_bld == NULL) { + EVP_KDF_CTX_free(ctx); + return -1; + } +#endif /* OPENSSL_VERSION_NUMBER */ if (ctx == NULL) { - return -1; + goto out; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L rc = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_MD, sshkdf_digest_to_md(crypto->digest_type)); if (rc != 1) { @@ -397,7 +206,7 @@ int ssh_kdf(struct ssh_crypto_struct *crypto, goto out; } rc = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_SSHKDF_SESSION_ID, - crypto->session_id, crypto->digest_len); + crypto->session_id, crypto->session_id_len); if (rc != 1) { goto out; } @@ -405,8 +214,60 @@ int ssh_kdf(struct ssh_crypto_struct *crypto, if (rc != 1) { goto out; } +#else + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_KDF_PARAM_DIGEST, + md, strlen(md)); + if (rc != 1) { + rc = -1; + goto out; + } + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_KDF_PARAM_KEY, + key, key_len); + if (rc != 1) { + rc = -1; + goto out; + } + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, + OSSL_KDF_PARAM_SSHKDF_XCGHASH, + crypto->secret_hash, + crypto->digest_len); + if (rc != 1) { + rc = -1; + goto out; + } + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, + OSSL_KDF_PARAM_SSHKDF_SESSION_ID, + crypto->session_id, + crypto->session_id_len); + if (rc != 1) { + rc = -1; + goto out; + } + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_KDF_PARAM_SSHKDF_TYPE, + (const char*)&key_type, 1); + if (rc != 1) { + rc = -1; + goto out; + } + + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + rc = -1; + goto out; + } + + rc = EVP_KDF_derive(ctx, output, requested_len, params); + if (rc != 1) { + rc = -1; + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ out: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_BLD_free(param_bld); + OSSL_PARAM_free(params); +#endif EVP_KDF_CTX_free(ctx); if (rc < 0) { return rc; @@ -417,64 +278,83 @@ out: #else int ssh_kdf(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, - int key_type, unsigned char *output, + uint8_t key_type, unsigned char *output, size_t requested_len) { return sshkdf_derive_key(crypto, key, key_len, key_type, output, requested_len); } -#endif +#endif /* HAVE_OPENSSL_EVP_KDF_CTX */ -HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) { - HMACCTX ctx = NULL; +HMACCTX hmac_init(const void *key, size_t len, enum ssh_hmac_e type) +{ + HMACCTX ctx = NULL; + EVP_PKEY *pkey = NULL; + int rc = -1; - ctx = HMAC_CTX_new(); - if (ctx == NULL) { - return NULL; - } + ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + return NULL; + } + pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, key, len); + if (pkey == NULL) { + goto error; + } - switch(type) { + switch (type) { case SSH_HMAC_SHA1: - HMAC_Init_ex(ctx, key, len, EVP_sha1(), NULL); - break; + rc = EVP_DigestSignInit(ctx, NULL, EVP_sha1(), NULL, pkey); + break; case SSH_HMAC_SHA256: - HMAC_Init_ex(ctx, key, len, EVP_sha256(), NULL); - break; + rc = EVP_DigestSignInit(ctx, NULL, EVP_sha256(), NULL, pkey); + break; case SSH_HMAC_SHA512: - HMAC_Init_ex(ctx, key, len, EVP_sha512(), NULL); - break; + rc = EVP_DigestSignInit(ctx, NULL, EVP_sha512(), NULL, pkey); + break; case SSH_HMAC_MD5: - HMAC_Init_ex(ctx, key, len, EVP_md5(), NULL); - break; + rc = EVP_DigestSignInit(ctx, NULL, EVP_md5(), NULL, pkey); + break; default: - HMAC_CTX_free(ctx); - ctx = NULL; - } + rc = -1; + break; + } + + EVP_PKEY_free(pkey); + if (rc != 1) { + goto error; + } + return ctx; - return ctx; +error: + EVP_MD_CTX_free(ctx); + return NULL; } -void hmac_update(HMACCTX ctx, const void *data, unsigned long len) { - HMAC_Update(ctx, data, len); +int hmac_update(HMACCTX ctx, const void *data, size_t len) +{ + return EVP_DigestSignUpdate(ctx, data, len); } -void hmac_final(HMACCTX ctx, unsigned char *hashmacbuf, unsigned int *len) { - HMAC_Final(ctx,hashmacbuf,len); +int hmac_final(HMACCTX ctx, unsigned char *hashmacbuf, size_t *len) +{ + size_t res = *len; + int rc; + rc = EVP_DigestSignFinal(ctx, hashmacbuf, &res); + EVP_MD_CTX_free(ctx); + if (rc == 1) { + *len = res; + } -#if OPENSSL_VERSION_NUMBER > 0x10100000L - HMAC_CTX_free(ctx); - ctx = NULL; -#else - HMAC_cleanup(ctx); - SAFE_FREE(ctx); - ctx = NULL; -#endif + return rc; } -static void evp_cipher_init(struct ssh_cipher_struct *cipher) { +static void evp_cipher_init(struct ssh_cipher_struct *cipher) +{ if (cipher->ctx == NULL) { cipher->ctx = EVP_CIPHER_CTX_new(); + } else { + EVP_CIPHER_CTX_init(cipher->ctx); } switch(cipher->ciphertype){ @@ -487,7 +367,6 @@ static void evp_cipher_init(struct ssh_cipher_struct *cipher) { case SSH_AES256_CBC: cipher->cipher = EVP_aes_256_cbc(); break; -#ifdef HAVE_OPENSSL_EVP_AES_CTR case SSH_AES128_CTR: cipher->cipher = EVP_aes_128_ctr(); break; @@ -497,26 +376,12 @@ static void evp_cipher_init(struct ssh_cipher_struct *cipher) { case SSH_AES256_CTR: cipher->cipher = EVP_aes_256_ctr(); break; -#else - case SSH_AES128_CTR: - case SSH_AES192_CTR: - case SSH_AES256_CTR: - SSH_LOG(SSH_LOG_WARNING, "This cipher is not available in evp_cipher_init"); - break; -#endif -#ifdef HAVE_OPENSSL_EVP_AES_GCM case SSH_AEAD_AES128_GCM: cipher->cipher = EVP_aes_128_gcm(); break; case SSH_AEAD_AES256_GCM: cipher->cipher = EVP_aes_256_gcm(); break; -#else - case SSH_AEAD_AES128_GCM: - case SSH_AEAD_AES256_GCM: - SSH_LOG(SSH_LOG_WARNING, "This cipher is not available in evp_cipher_init"); - break; -#endif /* HAVE_OPENSSL_EVP_AES_GCM */ case SSH_3DES_CBC: cipher->cipher = EVP_des_ede3_cbc(); break; @@ -525,12 +390,12 @@ static void evp_cipher_init(struct ssh_cipher_struct *cipher) { cipher->cipher = EVP_bf_cbc(); break; /* ciphers not using EVP */ -#endif +#endif /* WITH_BLOWFISH_CIPHER */ case SSH_AEAD_CHACHA20_POLY1305: - SSH_LOG(SSH_LOG_WARNING, "The ChaCha cipher cannot be handled here"); + SSH_LOG(SSH_LOG_TRACE, "The ChaCha cipher cannot be handled here"); break; case SSH_NO_CIPHER: - SSH_LOG(SSH_LOG_WARNING, "No valid ciphertype found"); + SSH_LOG(SSH_LOG_TRACE, "No valid ciphertype found"); break; } } @@ -541,15 +406,13 @@ static int evp_cipher_set_encrypt_key(struct ssh_cipher_struct *cipher, int rc; evp_cipher_init(cipher); - EVP_CIPHER_CTX_reset(cipher->ctx); rc = EVP_EncryptInit_ex(cipher->ctx, cipher->cipher, NULL, key, IV); if (rc != 1){ - SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptInit_ex failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptInit_ex failed"); return SSH_ERROR; } -#ifdef HAVE_OPENSSL_EVP_AES_GCM /* For AES-GCM we need to set IV in specific way */ if (cipher->ciphertype == SSH_AEAD_AES128_GCM || cipher->ciphertype == SSH_AEAD_AES256_GCM) { @@ -558,11 +421,10 @@ static int evp_cipher_set_encrypt_key(struct ssh_cipher_struct *cipher, -1, (uint8_t *)IV); if (rc != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_SET_IV_FIXED failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_SET_IV_FIXED failed"); return SSH_ERROR; } } -#endif /* HAVE_OPENSSL_EVP_AES_GCM */ EVP_CIPHER_CTX_set_padding(cipher->ctx, 0); @@ -574,15 +436,13 @@ static int evp_cipher_set_decrypt_key(struct ssh_cipher_struct *cipher, int rc; evp_cipher_init(cipher); - EVP_CIPHER_CTX_reset(cipher->ctx); rc = EVP_DecryptInit_ex(cipher->ctx, cipher->cipher, NULL, key, IV); if (rc != 1){ - SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptInit_ex failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptInit_ex failed"); return SSH_ERROR; } -#ifdef HAVE_OPENSSL_EVP_AES_GCM /* For AES-GCM we need to set IV in specific way */ if (cipher->ciphertype == SSH_AEAD_AES128_GCM || cipher->ciphertype == SSH_AEAD_AES256_GCM) { @@ -591,11 +451,10 @@ static int evp_cipher_set_decrypt_key(struct ssh_cipher_struct *cipher, -1, (uint8_t *)IV); if (rc != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_SET_IV_FIXED failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_SET_IV_FIXED failed"); return SSH_ERROR; } } -#endif /* HAVE_OPENSSL_EVP_AES_GCM */ EVP_CIPHER_CTX_set_padding(cipher->ctx, 0); @@ -617,11 +476,11 @@ static void evp_cipher_encrypt(struct ssh_cipher_struct *cipher, (unsigned char *)in, (int)len); if (rc != 1){ - SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptUpdate failed"); return; } if (outlen != (int)len){ - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_DEBUG, "EVP_EncryptUpdate: output size %d for %zu in", outlen, len); @@ -643,11 +502,11 @@ static void evp_cipher_decrypt(struct ssh_cipher_struct *cipher, (unsigned char *)in, (int)len); if (rc != 1){ - SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptUpdate failed"); return; } if (outlen != (int)len){ - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_DEBUG, "EVP_DecryptUpdate: output size %d for %zu in", outlen, len); @@ -661,68 +520,6 @@ static void evp_cipher_cleanup(struct ssh_cipher_struct *cipher) { } } -#ifndef HAVE_OPENSSL_EVP_AES_CTR -/* Some OS (osx, OpenIndiana, ...) have no support for CTR ciphers in EVP_aes */ - -struct ssh_aes_key_schedule { - AES_KEY key; - uint8_t IV[AES_BLOCK_SIZE]; -}; - -static int aes_ctr_set_key(struct ssh_cipher_struct *cipher, void *key, - void *IV) { - int rc; - - if (cipher->aes_key == NULL) { - cipher->aes_key = malloc(sizeof (struct ssh_aes_key_schedule)); - } - if (cipher->aes_key == NULL) { - return SSH_ERROR; - } - ZERO_STRUCTP(cipher->aes_key); - /* CTR doesn't need a decryption key */ - rc = AES_set_encrypt_key(key, cipher->keysize, &cipher->aes_key->key); - if (rc < 0) { - SAFE_FREE(cipher->aes_key); - return SSH_ERROR; - } - memcpy(cipher->aes_key->IV, IV, AES_BLOCK_SIZE); - return SSH_OK; -} - -static void -aes_ctr_encrypt(struct ssh_cipher_struct *cipher, - void *in, - void *out, - size_t len) -{ - unsigned char tmp_buffer[AES_BLOCK_SIZE]; - unsigned int num=0; - /* Some things are special with ctr128 : - * In this case, tmp_buffer is not being used, because it is used to store temporary data - * when an encryption is made on lengths that are not multiple of blocksize. - * Same for num, which is being used to store the current offset in blocksize in CTR - * function. - */ -#ifdef HAVE_OPENSSL_CRYPTO_CTR128_ENCRYPT - CRYPTO_ctr128_encrypt(in, out, len, &cipher->aes_key->key, cipher->aes_key->IV, tmp_buffer, &num, (block128_f)AES_encrypt); -#else - AES_ctr128_encrypt(in, out, len, &cipher->aes_key->key, cipher->aes_key->IV, tmp_buffer, &num); -#endif /* HAVE_OPENSSL_CRYPTO_CTR128_ENCRYPT */ -} - -static void aes_ctr_cleanup(struct ssh_cipher_struct *cipher){ - if (cipher != NULL) { - if (cipher->aes_key != NULL) { - explicit_bzero(cipher->aes_key, sizeof(*cipher->aes_key)); - } - SAFE_FREE(cipher->aes_key); - } -} - -#endif /* HAVE_OPENSSL_EVP_AES_CTR */ - -#ifdef HAVE_OPENSSL_EVP_AES_GCM static int evp_cipher_aead_get_length(struct ssh_cipher_struct *cipher, void *in, @@ -764,7 +561,7 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, 1, lastiv); if (rc == 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_IV_GEN failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_IV_GEN failed"); return; } @@ -776,7 +573,7 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, (int)aadlen); outlen = tmplen; if (rc == 0 || outlen != aadlen) { - SSH_LOG(SSH_LOG_WARNING, "Failed to pass authenticated data"); + SSH_LOG(SSH_LOG_TRACE, "Failed to pass authenticated data"); return; } memcpy(out, in, aadlen); @@ -789,7 +586,7 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, (int)len - aadlen); outlen = tmplen; if (rc != 1 || outlen != (int)len - aadlen) { - SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptUpdate failed"); return; } @@ -798,7 +595,7 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, NULL, &tmplen); if (rc < 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptFinal failed: Failed to create a tag"); + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptFinal failed: Failed to create a tag"); return; } @@ -807,7 +604,7 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, authlen, (unsigned char *)tag); if (rc != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_GET_TAG failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_GET_TAG failed"); return; } } @@ -835,7 +632,7 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, 1, lastiv); if (rc == 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_IV_GEN failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_IV_GEN failed"); return SSH_ERROR; } @@ -845,7 +642,7 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, authlen, (unsigned char *)complete_packet + aadlen + encrypted_size); if (rc == 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_SET_TAG failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_SET_TAG failed"); return SSH_ERROR; } @@ -856,7 +653,7 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, (unsigned char *)complete_packet, (int)aadlen); if (rc == 0) { - SSH_LOG(SSH_LOG_WARNING, "Failed to pass authenticated data"); + SSH_LOG(SSH_LOG_TRACE, "Failed to pass authenticated data"); return SSH_ERROR; } /* Do not copy the length to the target buffer, because it is already processed */ @@ -867,14 +664,14 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, (unsigned char *)out, &outlen, (unsigned char *)complete_packet + aadlen, - encrypted_size /* already substracted aadlen*/); + encrypted_size /* already subtracted aadlen */); if (rc != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptUpdate failed"); return SSH_ERROR; } if (outlen != (int)encrypted_size) { - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptUpdate: output size %d for %zd in", outlen, encrypted_size); @@ -886,28 +683,31 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, NULL, &outlen); if (rc < 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptFinal failed: Failed authentication"); + SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptFinal failed: Failed authentication"); return SSH_ERROR; } return SSH_OK; } -#endif /* HAVE_OPENSSL_EVP_AES_GCM */ - -#if defined(HAVE_OPENSSL_EVP_CHACHA20) && defined(HAVE_OPENSSL_EVP_POLY1305) +#ifdef HAVE_OPENSSL_EVP_CHACHA20 struct chacha20_poly1305_keysched { /* cipher handle used for encrypting the packets */ EVP_CIPHER_CTX *main_evp; /* cipher handle used for encrypting the length field */ EVP_CIPHER_CTX *header_evp; +#if OPENSSL_VERSION_NUMBER < 0x30000000L /* mac handle used for authenticating the packets */ EVP_PKEY_CTX *pctx; /* Poly1305 key */ EVP_PKEY *key; /* MD context for digesting data in poly1305 */ EVP_MD_CTX *mctx; +#else + /* MAC context used to do poly1305 */ + EVP_MAC_CTX *mctx; +#endif /* OPENSSL_VERSION_NUMBER */ }; static void @@ -925,11 +725,16 @@ chacha20_poly1305_cleanup(struct ssh_cipher_struct *cipher) ctx->main_evp = NULL; EVP_CIPHER_CTX_free(ctx->header_evp); ctx->header_evp = NULL; +#if OPENSSL_VERSION_NUMBER < 0x30000000L /* ctx->pctx is freed as part of MD context */ EVP_PKEY_free(ctx->key); ctx->key = NULL; EVP_MD_CTX_free(ctx->mctx); ctx->mctx = NULL; +#else + EVP_MAC_CTX_free(ctx->mctx); + ctx->mctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ SAFE_FREE(cipher->chacha20_schedule); } @@ -942,6 +747,9 @@ chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher, struct chacha20_poly1305_keysched *ctx = NULL; uint8_t *u8key = key; int ret = SSH_ERROR, rv; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MAC *mac = NULL; +#endif if (cipher->chacha20_schedule == NULL) { ctx = calloc(1, sizeof(*ctx)); @@ -957,38 +765,54 @@ chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher, /* K2 uses the first half of the key */ ctx->main_evp = EVP_CIPHER_CTX_new(); if (ctx->main_evp == NULL) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CIPHER_CTX_new failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CIPHER_CTX_new failed"); goto out; } rv = EVP_EncryptInit_ex(ctx->main_evp, EVP_chacha20(), NULL, u8key, NULL); if (rv != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherInit failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherInit failed"); goto out; } /* K1 uses the second half of the key */ ctx->header_evp = EVP_CIPHER_CTX_new(); if (ctx->header_evp == NULL) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CIPHER_CTX_new failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CIPHER_CTX_new failed"); goto out; } ret = EVP_EncryptInit_ex(ctx->header_evp, EVP_chacha20(), NULL, u8key + CHACHA20_KEYLEN, NULL); if (ret != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherInit failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherInit failed"); goto out; } /* The Poly1305 key initialization is delayed to the time we know * the actual key for packet so we do not need to create a bogus keys */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L ctx->mctx = EVP_MD_CTX_new(); if (ctx->mctx == NULL) { - SSH_LOG(SSH_LOG_WARNING, "EVP_MD_CTX_new failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_MD_CTX_new failed"); return SSH_ERROR; } +#else + mac = EVP_MAC_fetch(NULL, "poly1305", NULL); + if (mac == NULL) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_fetch failed"); + goto out; + } + ctx->mctx = EVP_MAC_CTX_new(mac); + if (ctx->mctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_CTX_new failed"); + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ ret = SSH_OK; out: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MAC_free(mac); +#endif if (ret != SSH_OK) { chacha20_poly1305_cleanup(cipher); } @@ -1017,13 +841,13 @@ chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher, ret = EVP_CipherInit_ex(ctx->header_evp, NULL, NULL, NULL, seqbuf, do_encrypt); if (ret != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherInit_ex(header_evp) failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherInit_ex(header_evp) failed"); return SSH_ERROR; } ret = EVP_CipherInit_ex(ctx->main_evp, NULL, NULL, NULL, seqbuf, do_encrypt); if (ret != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherInit_ex(main_evp) failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherInit_ex(main_evp) failed"); return SSH_ERROR; } @@ -1052,7 +876,7 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, rv = EVP_CipherUpdate(ctx->main_evp, poly_key, &len, (unsigned char *)zero_block, sizeof(zero_block)); if (rv != 1 || len != CHACHA20_BLOCKSIZE) { - SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptUpdate failed"); goto out; } #ifdef DEBUG_CRYPTO @@ -1060,17 +884,18 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, #endif /* DEBUG_CRYPTO */ /* Set the Poly1305 key */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L if (ctx->key == NULL) { /* Poly1305 Initialization needs to know the actual key */ ctx->key = EVP_PKEY_new_mac_key(EVP_PKEY_POLY1305, NULL, poly_key, POLY1305_KEYLEN); if (ctx->key == NULL) { - SSH_LOG(SSH_LOG_WARNING, "EVP_PKEY_new_mac_key failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_PKEY_new_mac_key failed"); goto out; } rv = EVP_DigestSignInit(ctx->mctx, &ctx->pctx, NULL, NULL, ctx->key); if (rv != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_DigestSignInit failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignInit failed"); goto out; } } else { @@ -1079,10 +904,17 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, EVP_PKEY_CTRL_SET_MAC_KEY, POLY1305_KEYLEN, (void *)poly_key); if (rv <= 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_PKEY_CTX_ctrl failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_PKEY_CTX_ctrl failed"); goto out; } } +#else + rv = EVP_MAC_init(ctx->mctx, poly_key, POLY1305_KEYLEN, NULL); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_init failed"); + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ ret = SSH_OK; out: @@ -1116,7 +948,7 @@ chacha20_poly1305_aead_decrypt_length(struct ssh_cipher_struct *cipher, rv = EVP_CipherUpdate(ctx->header_evp, out, &outlen, in, len); if (rv != 1 || outlen != sizeof(uint32_t)) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); return SSH_ERROR; } @@ -1126,7 +958,7 @@ chacha20_poly1305_aead_decrypt_length(struct ssh_cipher_struct *cipher, rv = EVP_CipherFinal_ex(ctx->header_evp, out + outlen, &outlen); if (rv != 1 || outlen != 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherFinal_ex failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherFinal_ex failed"); return SSH_ERROR; } @@ -1151,7 +983,7 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, /* Prepare the Poly1305 key */ rv = chacha20_poly1305_packet_setup(cipher, seq, 0); if (rv != SSH_OK) { - SSH_LOG(SSH_LOG_WARNING, "Failed to setup packet"); + SSH_LOG(SSH_LOG_TRACE, "Failed to setup packet"); goto out; } @@ -1160,25 +992,40 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, #endif /* DEBUG_CRYPTO */ /* Calculate MAC of received data */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L rv = EVP_DigestSignUpdate(ctx->mctx, complete_packet, encrypted_size + sizeof(uint32_t)); if (rv != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_DigestSignUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignUpdate failed"); goto out; } rv = EVP_DigestSignFinal(ctx->mctx, tag, &taglen); if (rv != 1) { - SSH_LOG(SSH_LOG_WARNING, "poly1305 verify error"); + SSH_LOG(SSH_LOG_TRACE, "poly1305 verify error"); + goto out; + } +#else + rv = EVP_MAC_update(ctx->mctx, complete_packet, + encrypted_size + sizeof(uint32_t)); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_update failed"); goto out; } + rv = EVP_MAC_final(ctx->mctx, tag, &taglen, POLY1305_TAGLEN); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_final failed"); + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ + #ifdef DEBUG_CRYPTO ssh_log_hexdump("calculated mac", tag, POLY1305_TAGLEN); #endif /* DEBUG_CRYPTO */ /* Verify the calculated MAC matches the attached MAC */ - cmp = memcmp(tag, mac, POLY1305_TAGLEN); + cmp = CRYPTO_memcmp(tag, mac, POLY1305_TAGLEN); if (cmp != 0) { /* mac error */ SSH_LOG(SSH_LOG_PACKET, "poly1305 verify error"); @@ -1190,13 +1037,13 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, (uint8_t *)complete_packet + sizeof(uint32_t), encrypted_size); if (rv != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); goto out; } rv = EVP_CipherFinal_ex(ctx->main_evp, out + len, &len); if (rv != 1 || len != 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherFinal_ex failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherFinal_ex failed"); goto out; } @@ -1221,7 +1068,7 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, /* Prepare the Poly1305 key */ ret = chacha20_poly1305_packet_setup(cipher, seq, 1); if (ret != SSH_OK) { - SSH_LOG(SSH_LOG_WARNING, "Failed to setup packet"); + SSH_LOG(SSH_LOG_TRACE, "Failed to setup packet"); return; } @@ -1236,7 +1083,7 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, (unsigned char *)&in_packet->length, sizeof(uint32_t)); if (ret != 1 || outlen != sizeof(uint32_t)) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); return; } #ifdef DEBUG_CRYPTO @@ -1245,7 +1092,7 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, #endif /* DEBUG_CRYPTO */ ret = EVP_CipherFinal_ex(ctx->header_evp, (uint8_t *)out + outlen, &outlen); if (ret != 1 || outlen != 0) { - SSH_LOG(SSH_LOG_PACKET, "EVP_EncryptFinal_ex failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptFinal_ex failed"); return; } @@ -1257,23 +1104,37 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, in_packet->payload, len - sizeof(uint32_t)); if (ret != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); return; } /* step 4, compute the MAC */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L ret = EVP_DigestSignUpdate(ctx->mctx, out_packet, len); if (ret <= 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_DigestSignUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignUpdate failed"); return; } ret = EVP_DigestSignFinal(ctx->mctx, tag, &taglen); if (ret <= 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_DigestSignFinal failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignFinal failed"); return; } +#else + ret = EVP_MAC_update(ctx->mctx, (void*)out_packet, len); + if (ret != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_update failed"); + return; + } + + ret = EVP_MAC_final(ctx->mctx, tag, &taglen, POLY1305_TAGLEN); + if (ret != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_final failed"); + return; + } +#endif /* OPENSSL_VERSION_NUMBER */ } -#endif /* defined(HAVE_OPENSSL_EVP_CHACHA20) && defined(HAVE_OPENSSL_EVP_POLY1305) */ +#endif /* HAVE_OPENSSL_EVP_CHACHA20 */ #ifdef WITH_INSECURE_NONE static void @@ -1302,13 +1163,8 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .decrypt = evp_cipher_decrypt, .cleanup = evp_cipher_cleanup }, -#endif +#endif /* WITH_BLOWFISH_CIPHER */ #ifdef HAS_AES -#ifndef BROKEN_AES_CTR -/* OpenSSL until 0.9.7c has a broken AES_ctr128_encrypt implementation which - * increments the counter from 2^64 instead of 1. It's better not to use it - */ -#ifdef HAVE_OPENSSL_EVP_AES_CTR { .name = "aes128-ctr", .blocksize = AES_BLOCK_SIZE, @@ -1342,42 +1198,6 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .decrypt = evp_cipher_decrypt, .cleanup = evp_cipher_cleanup }, -#else /* HAVE_OPENSSL_EVP_AES_CTR */ - { - .name = "aes128-ctr", - .blocksize = AES_BLOCK_SIZE, - .ciphertype = SSH_AES128_CTR, - .keysize = 128, - .set_encrypt_key = aes_ctr_set_key, - .set_decrypt_key = aes_ctr_set_key, - .encrypt = aes_ctr_encrypt, - .decrypt = aes_ctr_encrypt, - .cleanup = aes_ctr_cleanup - }, - { - .name = "aes192-ctr", - .blocksize = AES_BLOCK_SIZE, - .ciphertype = SSH_AES192_CTR, - .keysize = 192, - .set_encrypt_key = aes_ctr_set_key, - .set_decrypt_key = aes_ctr_set_key, - .encrypt = aes_ctr_encrypt, - .decrypt = aes_ctr_encrypt, - .cleanup = aes_ctr_cleanup - }, - { - .name = "aes256-ctr", - .blocksize = AES_BLOCK_SIZE, - .ciphertype = SSH_AES256_CTR, - .keysize = 256, - .set_encrypt_key = aes_ctr_set_key, - .set_decrypt_key = aes_ctr_set_key, - .encrypt = aes_ctr_encrypt, - .decrypt = aes_ctr_encrypt, - .cleanup = aes_ctr_cleanup - }, -#endif /* HAVE_OPENSSL_EVP_AES_CTR */ -#endif /* BROKEN_AES_CTR */ { .name = "aes128-cbc", .blocksize = AES_BLOCK_SIZE, @@ -1411,7 +1231,6 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .decrypt = evp_cipher_decrypt, .cleanup = evp_cipher_cleanup }, -#ifdef HAVE_OPENSSL_EVP_AES_GCM { .name = "aes128-gcm@openssh.com", .blocksize = AES_BLOCK_SIZE, @@ -1440,7 +1259,6 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .aead_decrypt = evp_cipher_aead_decrypt, .cleanup = evp_cipher_cleanup }, -#endif /* HAVE_OPENSSL_EVP_AES_GCM */ #endif /* HAS_AES */ #ifdef HAS_DES { @@ -1456,7 +1274,7 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { }, #endif /* HAS_DES */ { -#if defined(HAVE_OPENSSL_EVP_CHACHA20) && defined(HAVE_OPENSSL_EVP_POLY1305) +#ifdef HAVE_OPENSSL_EVP_CHACHA20 .ciphertype = SSH_AEAD_CHACHA20_POLY1305, .name = "chacha20-poly1305@openssh.com", .blocksize = CHACHA20_BLOCKSIZE/8, @@ -1472,7 +1290,7 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .cleanup = chacha20_poly1305_cleanup #else .name = "chacha20-poly1305@openssh.com" -#endif /* defined(HAVE_OPENSSL_EVP_CHACHA20) && defined(HAVE_OPENSSL_EVP_POLY1305) */ +#endif /* HAVE_OPENSSL_EVP_CHACHA20 */ }, #ifdef WITH_INSECURE_NONE { @@ -1499,13 +1317,15 @@ struct ssh_cipher_struct *ssh_get_ciphertab(void) */ int ssh_crypto_init(void) { - UNUSED_VAR(size_t i); +#ifndef HAVE_OPENSSL_EVP_CHACHA20 + size_t i; +#endif if (libcrypto_initialized) { return SSH_OK; } if (OpenSSL_version_num() != OPENSSL_VERSION_NUMBER){ - SSH_LOG(SSH_LOG_WARNING, "libssh compiled with %s " + SSH_LOG(SSH_LOG_DEBUG, "libssh compiled with %s " "headers, currently running with %s.", OPENSSL_VERSION_TEXT, OpenSSL_version(OpenSSL_version_num()) @@ -1521,12 +1341,9 @@ int ssh_crypto_init(void) /* Bit #57 denotes AES-NI instruction set extension */ OPENSSL_ia32cap &= ~(1LL << 57); } -#endif -#if OPENSSL_VERSION_NUMBER < 0x10100000L - OpenSSL_add_all_algorithms(); -#endif +#endif /* CAN_DISABLE_AESNI */ -#if !defined(HAVE_OPENSSL_EVP_CHACHA20) || !defined(HAVE_OPENSSL_EVP_POLY1305) +#ifndef HAVE_OPENSSL_EVP_CHACHA20 for (i = 0; ssh_ciphertab[i].name != NULL; i++) { int cmp; @@ -1538,7 +1355,7 @@ int ssh_crypto_init(void) break; } } -#endif /* !defined(HAVE_OPENSSL_EVP_CHACHA20) || !defined(HAVE_OPENSSL_EVP_POLY1305) */ +#endif /* HAVE_OPENSSL_EVP_CHACHA20 */ libcrypto_initialized = 1; @@ -1555,13 +1372,210 @@ void ssh_crypto_finalize(void) return; } - ENGINE_cleanup(); -#if OPENSSL_VERSION_NUMBER < 0x10100000L - EVP_cleanup(); - CRYPTO_cleanup_all_ex_data(); +/* TODO this should finalize engine if it was started, but during atexit calls, + * we are crashing. AFAIK this is related to the dlopened pkcs11 modules calling + * the crypto cleanups earlier. */ +#if 0 + if (engine != NULL) { + ENGINE_finish(engine); + ENGINE_free(engine); + engine = NULL; + } #endif libcrypto_initialized = 0; } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +/** + * @internal + * @brief Create EVP_PKEY from parameters + * + * @param[in] name Algorithm to use. For more info see manpage of EVP_PKEY_CTX_new_from_name + * + * @param[in] param_bld Constructed param builder for the pkey + * + * @param[out] pkey Created EVP_PKEY variable + * + * @param[in] selection Reference selections at man EVP_PKEY_FROMDATA + * + * @return 0 on success, -1 on error + */ +int evp_build_pkey(const char* name, OSSL_PARAM_BLD *param_bld, + EVP_PKEY **pkey, int selection) +{ + int rc; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, name, NULL); + OSSL_PARAM *params = NULL; + + if (ctx == NULL) { + return -1; + } + + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + EVP_PKEY_CTX_free(ctx); + return -1; + } + + rc = EVP_PKEY_fromdata_init(ctx); + if (rc != 1) { + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + return -1; + } + + rc = EVP_PKEY_fromdata(ctx, pkey, selection, params); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to import private key: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + return -1; + } + + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + + return SSH_OK; +} + +/** + * @brief creates a copy of EVP_PKEY + * + * @param[in] name Algorithm to use. For more info see manpage of + * EVP_PKEY_CTX_new_from_name + * + * @param[in] key Key being duplicated from + * + * @param[in] demote Same as at pki_key_dup, only the public + * part of the key gets duplicated if true + * + * @param[out] new_key The key where the duplicate is saved + * + * @return 0 on success, -1 on error + */ +static int +evp_dup_pkey(const char *name, const ssh_key key, int demote, ssh_key new_key) +{ + int rc; + EVP_PKEY_CTX *ctx = NULL; + OSSL_PARAM *params = NULL; + + /* The simple case -- just reference the existing key */ + if (!demote || (key->flags & SSH_KEY_FLAG_PRIVATE) == 0) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + return -1; + } + new_key->key = key->key; + return SSH_OK; + } + + /* demote == 1 */ + ctx = EVP_PKEY_CTX_new_from_name(NULL, name, NULL); + if (ctx == NULL) { + return -1; + } + + rc = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, ¶ms); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + return -1; + } + + if (strcmp(name, "EC") == 0) { + OSSL_PARAM *locate_param = NULL; + /* For ECC keys provided by engine or provider, we need to have the + * explicit public part available, otherwise the key will not be + * usable */ + locate_param = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PUB_KEY); + if (locate_param == NULL) { + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return -1; + } + } + rc = EVP_PKEY_fromdata_init(ctx); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return -1; + } + + rc = EVP_PKEY_fromdata(ctx, &(new_key->key), EVP_PKEY_PUBLIC_KEY, params); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return -1; + } + + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + + return SSH_OK; +} + +int evp_dup_rsa_pkey(const ssh_key key, ssh_key new_key, int demote) +{ + return evp_dup_pkey("RSA", key, demote, new_key); +} + +int evp_dup_ecdsa_pkey(const ssh_key key, ssh_key new_key, int demote) +{ + return evp_dup_pkey("EC", key, demote, new_key); +} +#endif /* OPENSSL_VERSION_NUMBER */ + +ssh_string +pki_key_make_ecpoint_string(const EC_GROUP *g, const EC_POINT *p) +{ + ssh_string s = NULL; + size_t len; + + len = EC_POINT_point2oct(g, + p, + POINT_CONVERSION_UNCOMPRESSED, + NULL, + 0, + NULL); + if (len == 0) { + return NULL; + } + + s = ssh_string_new(len); + if (s == NULL) { + return NULL; + } + + len = EC_POINT_point2oct(g, + p, + POINT_CONVERSION_UNCOMPRESSED, + ssh_string_data(s), + ssh_string_len(s), + NULL); + if (len != ssh_string_len(s)) { + SSH_STRING_FREE(s); + return NULL; + } + + return s; +} + +int pki_key_ecgroup_name_to_nid(const char *group) +{ + if (strcmp(group, NISTP256) == 0 || + strcmp(group, "secp256r1") == 0 || + strcmp(group, "prime256v1") == 0) { + return NID_X9_62_prime256v1; + } else if (strcmp(group, NISTP384) == 0 || + strcmp(group, "secp384r1") == 0) { + return NID_secp384r1; + } else if (strcmp(group, NISTP521) == 0 || + strcmp(group, "secp521r1") == 0) { + return NID_secp521r1; + } + return -1; +} #endif /* LIBCRYPTO */ diff --git a/src/libgcrypt.c b/src/libgcrypt.c index 2383ffa0..4feda00c 100644 --- a/src/libgcrypt.c +++ b/src/libgcrypt.c @@ -69,181 +69,16 @@ static int alloc_key(struct ssh_cipher_struct *cipher) { void ssh_reseed(void){ } -int ssh_get_random(void *where, int len, int strong) -{ - /* variable not used in gcrypt */ - (void) strong; - - /* not using GCRY_VERY_STRONG_RANDOM which is a bit overkill */ - gcry_randomize(where,len,GCRY_STRONG_RANDOM); - - return 1; -} - -SHACTX sha1_init(void) { - SHACTX ctx = NULL; - gcry_md_open(&ctx, GCRY_MD_SHA1, 0); - - return ctx; -} - -void sha1_update(SHACTX c, const void *data, unsigned long len) { - gcry_md_write(c, data, len); -} - -void sha1_final(unsigned char *md, SHACTX c) { - gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA_DIGEST_LEN); - gcry_md_close(c); -} - -void sha1(const unsigned char *digest, int len, unsigned char *hash) { - gcry_md_hash_buffer(GCRY_MD_SHA1, hash, digest, len); -} - -#ifdef HAVE_GCRYPT_ECC -static int nid_to_md_algo(int nid) -{ - switch (nid) { - case NID_gcrypt_nistp256: - return GCRY_MD_SHA256; - case NID_gcrypt_nistp384: - return GCRY_MD_SHA384; - case NID_gcrypt_nistp521: - return GCRY_MD_SHA512; - } - return GCRY_MD_NONE; -} - -void evp(int nid, unsigned char *digest, int len, - unsigned char *hash, unsigned int *hlen) -{ - int algo = nid_to_md_algo(nid); - - /* Note: What gcrypt calls 'hash' is called 'digest' here and - vice-versa. */ - gcry_md_hash_buffer(algo, hash, digest, len); - *hlen = gcry_md_get_algo_dlen(algo); -} - -EVPCTX evp_init(int nid) -{ - gcry_error_t err; - int algo = nid_to_md_algo(nid); - EVPCTX ctx; - - err = gcry_md_open(&ctx, algo, 0); - if (err) { - return NULL; - } - - return ctx; -} - -void evp_update(EVPCTX ctx, const void *data, unsigned long len) -{ - gcry_md_write(ctx, data, len); -} - -void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) -{ - int algo = gcry_md_get_algo(ctx); - *mdlen = gcry_md_get_algo_dlen(algo); - memcpy(md, gcry_md_read(ctx, algo), *mdlen); - gcry_md_close(ctx); -} -#endif - -SHA256CTX sha256_init(void) { - SHA256CTX ctx = NULL; - gcry_md_open(&ctx, GCRY_MD_SHA256, 0); - - return ctx; -} - -void sha256_update(SHACTX c, const void *data, unsigned long len) { - gcry_md_write(c, data, len); -} - -void sha256_final(unsigned char *md, SHACTX c) { - gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA256_DIGEST_LEN); - gcry_md_close(c); -} - -void sha256(const unsigned char *digest, int len, unsigned char *hash){ - gcry_md_hash_buffer(GCRY_MD_SHA256, hash, digest, len); -} - -SHA384CTX sha384_init(void) { - SHA384CTX ctx = NULL; - gcry_md_open(&ctx, GCRY_MD_SHA384, 0); - - return ctx; -} - -void sha384_update(SHACTX c, const void *data, unsigned long len) { - gcry_md_write(c, data, len); -} - -void sha384_final(unsigned char *md, SHACTX c) { - gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA384_DIGEST_LEN); - gcry_md_close(c); -} - -void sha384(const unsigned char *digest, int len, unsigned char *hash) { - gcry_md_hash_buffer(GCRY_MD_SHA384, hash, digest, len); -} - -SHA512CTX sha512_init(void) { - SHA512CTX ctx = NULL; - gcry_md_open(&ctx, GCRY_MD_SHA512, 0); - - return ctx; -} - -void sha512_update(SHACTX c, const void *data, unsigned long len) { - gcry_md_write(c, data, len); -} - -void sha512_final(unsigned char *md, SHACTX c) { - gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA512_DIGEST_LEN); - gcry_md_close(c); -} - -void sha512(const unsigned char *digest, int len, unsigned char *hash) { - gcry_md_hash_buffer(GCRY_MD_SHA512, hash, digest, len); -} - -MD5CTX md5_init(void) { - MD5CTX c = NULL; - gcry_md_open(&c, GCRY_MD_MD5, 0); - - return c; -} - -void md5_update(MD5CTX c, const void *data, unsigned long len) { - gcry_md_write(c,data,len); -} - -void md5_final(unsigned char *md, MD5CTX c) { - gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), MD5_DIGEST_LEN); - gcry_md_close(c); -} - int ssh_kdf(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, - int key_type, unsigned char *output, + uint8_t key_type, unsigned char *output, size_t requested_len) { return sshkdf_derive_key(crypto, key, key_len, key_type, output, requested_len); } -HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) { +HMACCTX hmac_init(const void *key, size_t len, enum ssh_hmac_e type) { HMACCTX c = NULL; switch(type) { @@ -268,14 +103,17 @@ HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) { return c; } -void hmac_update(HMACCTX c, const void *data, unsigned long len) { +int hmac_update(HMACCTX c, const void *data, size_t len) { gcry_md_write(c, data, len); + return 1; } -void hmac_final(HMACCTX c, unsigned char *hashmacbuf, unsigned int *len) { - *len = gcry_md_get_algo_dlen(gcry_md_get_algo(c)); +int hmac_final(HMACCTX c, unsigned char *hashmacbuf, size_t *len) { + unsigned int tmp = gcry_md_get_algo_dlen(gcry_md_get_algo(c)); + *len = (size_t)tmp; memcpy(hashmacbuf, gcry_md_read(c, 0), *len); gcry_md_close(c); + return 1; } #ifdef WITH_BLOWFISH_CIPHER @@ -307,12 +145,12 @@ static int blowfish_set_key(struct ssh_cipher_struct *cipher, void *key, void *I } static void blowfish_encrypt(struct ssh_cipher_struct *cipher, void *in, - void *out, unsigned long len) { + void *out, size_t len) { gcry_cipher_encrypt(cipher->key[0], out, len, in, len); } static void blowfish_decrypt(struct ssh_cipher_struct *cipher, void *in, - void *out, unsigned long len) { + void *out, size_t len) { gcry_cipher_decrypt(cipher->key[0], out, len, in, len); } #endif /* WITH_BLOWFISH_CIPHER */ @@ -350,7 +188,7 @@ static int aes_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { } break; default: - SSH_LOG(SSH_LOG_WARNING, "Unksupported key length %u.", cipher->keysize); + SSH_LOG(SSH_LOG_TRACE, "Unsupported key length %u.", cipher->keysize); SAFE_FREE(cipher->key); return -1; } @@ -434,7 +272,7 @@ aes_gcm_encrypt(struct ssh_cipher_struct *cipher, err = gcry_cipher_setiv(cipher->key[0], cipher->last_iv, AES_GCM_IVLEN); - /* This actualy does not increment the packet counter for the + /* This actually does not increment the packet counter for the * current encryption operation, but for the next one. The first * operation needs to be completed with the derived IV. * @@ -443,7 +281,7 @@ aes_gcm_encrypt(struct ssh_cipher_struct *cipher, */ uint64_inc(cipher->last_iv + 4); if (err) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", gpg_strerror(err)); return; } @@ -451,7 +289,7 @@ aes_gcm_encrypt(struct ssh_cipher_struct *cipher, /* Pass the authenticated data (packet_length) */ err = gcry_cipher_authenticate(cipher->key[0], in, aadlen); if (err) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_authenticate failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_authenticate failed: %s", gpg_strerror(err)); return; } @@ -464,7 +302,7 @@ aes_gcm_encrypt(struct ssh_cipher_struct *cipher, (unsigned char *)in + aadlen, len - aadlen); if (err) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", gpg_strerror(err)); return; } @@ -474,7 +312,7 @@ aes_gcm_encrypt(struct ssh_cipher_struct *cipher, (void *)tag, authlen); if (err) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_gettag failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_gettag failed: %s", gpg_strerror(err)); return; } @@ -499,7 +337,7 @@ aes_gcm_decrypt(struct ssh_cipher_struct *cipher, err = gcry_cipher_setiv(cipher->key[0], cipher->last_iv, AES_GCM_IVLEN); - /* This actualy does not increment the packet counter for the + /* This actually does not increment the packet counter for the * current encryption operation, but for the next one. The first * operation needs to be completed with the derived IV. * @@ -508,7 +346,7 @@ aes_gcm_decrypt(struct ssh_cipher_struct *cipher, */ uint64_inc(cipher->last_iv + 4); if (err) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", gpg_strerror(err)); return SSH_ERROR; } @@ -518,7 +356,7 @@ aes_gcm_decrypt(struct ssh_cipher_struct *cipher, complete_packet, aadlen); if (err) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_authenticate failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_authenticate failed: %s", gpg_strerror(err)); return SSH_ERROR; } @@ -532,7 +370,7 @@ aes_gcm_decrypt(struct ssh_cipher_struct *cipher, (unsigned char *)complete_packet + aadlen, encrypted_size); if (err) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_decrypt failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_decrypt failed: %s", gpg_strerror(err)); return SSH_ERROR; } @@ -542,10 +380,10 @@ aes_gcm_decrypt(struct ssh_cipher_struct *cipher, (unsigned char *)complete_packet + aadlen + encrypted_size, authlen); if (gpg_err_code(err) == GPG_ERR_CHECKSUM) { - SSH_LOG(SSH_LOG_WARNING, "The authentication tag does not match"); + SSH_LOG(SSH_LOG_DEBUG, "The authentication tag does not match"); return SSH_ERROR; } else if (err != GPG_ERR_NO_ERROR) { - SSH_LOG(SSH_LOG_WARNING, "General error while decryption: %s", + SSH_LOG(SSH_LOG_TRACE, "General error while decryption: %s", gpg_strerror(err)); return SSH_ERROR; } @@ -578,12 +416,12 @@ static int des3_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { } static void des3_encrypt(struct ssh_cipher_struct *cipher, void *in, - void *out, unsigned long len) { + void *out, size_t len) { gcry_cipher_encrypt(cipher->key[0], out, len, in, len); } static void des3_decrypt(struct ssh_cipher_struct *cipher, void *in, - void *out, unsigned long len) { + void *out, size_t len) { gcry_cipher_decrypt(cipher->key[0], out, len, in, len); } @@ -631,7 +469,7 @@ static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, err = gcry_cipher_open(&ctx->main_hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_STREAM, 0); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_open failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_open failed: %s", gpg_strerror(err)); SAFE_FREE(cipher->chacha20_schedule); return -1; @@ -639,7 +477,7 @@ static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, err = gcry_cipher_open(&ctx->header_hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_STREAM, 0); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_open failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_open failed: %s", gpg_strerror(err)); gcry_cipher_close(ctx->main_hd); SAFE_FREE(cipher->chacha20_schedule); @@ -647,7 +485,7 @@ static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, } err = gcry_mac_open(&ctx->mac_hd, GCRY_MAC_POLY1305, 0, NULL); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_mac_open failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_open failed: %s", gpg_strerror(err)); gcry_cipher_close(ctx->main_hd); gcry_cipher_close(ctx->header_hd); @@ -660,7 +498,7 @@ static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, err = gcry_cipher_setkey(ctx->main_hd, u8key, CHACHA20_KEYLEN); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setkey failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setkey failed: %s", gpg_strerror(err)); chacha20_cleanup(cipher); return -1; @@ -669,7 +507,7 @@ static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, err = gcry_cipher_setkey(ctx->header_hd, u8key + CHACHA20_KEYLEN, CHACHA20_KEYLEN); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setkey failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setkey failed: %s", gpg_strerror(err)); chacha20_cleanup(cipher); return -1; @@ -696,7 +534,7 @@ static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, /* step 1, prepare the poly1305 key */ err = gcry_cipher_setiv(ctx->main_hd, (uint8_t *)&seq, sizeof(seq)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", gpg_strerror(err)); goto out; } @@ -708,13 +546,13 @@ static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, zero_block, sizeof(zero_block)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", gpg_strerror(err)); goto out; } err = gcry_mac_setkey(ctx->mac_hd, poly_key, POLY1305_KEYLEN); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_mac_setkey failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_setkey failed: %s", gpg_strerror(err)); goto out; } @@ -722,7 +560,7 @@ static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, /* step 2, encrypt length field */ err = gcry_cipher_setiv(ctx->header_hd, (uint8_t *)&seq, sizeof(seq)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", gpg_strerror(err)); goto out; } @@ -732,7 +570,7 @@ static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, (uint8_t *)&in_packet->length, sizeof(uint32_t)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", gpg_strerror(err)); goto out; } @@ -744,7 +582,7 @@ static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, in_packet->payload, len - sizeof(uint32_t)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", gpg_strerror(err)); goto out; } @@ -752,13 +590,13 @@ static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, /* step 4, compute the MAC */ err = gcry_mac_write(ctx->mac_hd, (uint8_t *)out_packet, len); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_mac_write failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_write failed: %s", gpg_strerror(err)); goto out; } err = gcry_mac_read(ctx->mac_hd, tag, &taglen); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_mac_read failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_read failed: %s", gpg_strerror(err)); goto out; } @@ -784,7 +622,7 @@ static int chacha20_poly1305_aead_decrypt_length( err = gcry_cipher_setiv(ctx->header_hd, (uint8_t *)&seq, sizeof(seq)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", gpg_strerror(err)); return SSH_ERROR; } @@ -794,7 +632,7 @@ static int chacha20_poly1305_aead_decrypt_length( in, sizeof(uint32_t)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_decrypt failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_decrypt failed: %s", gpg_strerror(err)); return SSH_ERROR; } @@ -820,7 +658,7 @@ static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, /* step 1, prepare the poly1305 key */ err = gcry_cipher_setiv(ctx->main_hd, (uint8_t *)&seq, sizeof(seq)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", gpg_strerror(err)); goto out; } @@ -832,13 +670,13 @@ static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, zero_block, sizeof(zero_block)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", gpg_strerror(err)); goto out; } err = gcry_mac_setkey(ctx->mac_hd, poly_key, POLY1305_KEYLEN); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_mac_setkey failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_setkey failed: %s", gpg_strerror(err)); goto out; } @@ -847,7 +685,7 @@ static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, err = gcry_mac_write(ctx->mac_hd, (uint8_t *)complete_packet, encrypted_size + sizeof(uint32_t)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_mac_write failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_write failed: %s", gpg_strerror(err)); goto out; } @@ -856,7 +694,7 @@ static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, SSH_LOG(SSH_LOG_PACKET, "poly1305 verify error"); goto out; } else if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_mac_verify failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_verify failed: %s", gpg_strerror(err)); goto out; } @@ -868,7 +706,7 @@ static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, (uint8_t *)complete_packet + sizeof(uint32_t), encrypted_size); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_decrypt failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_decrypt failed: %s", gpg_strerror(err)); goto out; } diff --git a/src/libmbedcrypto.c b/src/libmbedcrypto.c index ee3fad79..8fb36e53 100644 --- a/src/libmbedcrypto.c +++ b/src/libmbedcrypto.c @@ -27,6 +27,7 @@ #include "libssh/crypto.h" #include "libssh/priv.h" #include "libssh/misc.h" +#include "mbedcrypto-compat.h" #if defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) #include "libssh/bytearray.h" #include "libssh/chacha20-poly1305-common.h" @@ -41,7 +42,7 @@ #endif /* MBEDTLS_GCM_C */ static mbedtls_entropy_context ssh_mbedtls_entropy; -static mbedtls_ctr_drbg_context ssh_mbedtls_ctr_drbg; +extern mbedtls_ctr_drbg_context ssh_mbedtls_ctr_drbg; static int libmbedcrypto_initialized = 0; @@ -50,354 +51,16 @@ void ssh_reseed(void) mbedtls_ctr_drbg_reseed(&ssh_mbedtls_ctr_drbg, NULL, 0); } -int ssh_get_random(void *where, int len, int strong) -{ - return ssh_mbedtls_random(where, len, strong); -} - -SHACTX sha1_init(void) -{ - SHACTX ctx = NULL; - int rc; - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); - - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if (ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - -void sha1_update(SHACTX c, const void *data, unsigned long len) -{ - mbedtls_md_update(c, data, len); -} - -void sha1_final(unsigned char *md, SHACTX c) -{ - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); -} - -void sha1(const unsigned char *digest, int len, unsigned char *hash) -{ - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); - } -} - -static mbedtls_md_type_t nid_to_md_algo(int nid) -{ - switch (nid) { - case NID_mbedtls_nistp256: - return MBEDTLS_MD_SHA256; - case NID_mbedtls_nistp384: - return MBEDTLS_MD_SHA384; - case NID_mbedtls_nistp521: - return MBEDTLS_MD_SHA512; - } - return MBEDTLS_MD_NONE; -} - -void evp(int nid, unsigned char *digest, int len, - unsigned char *hash, unsigned int *hlen) -{ - mbedtls_md_type_t algo = nid_to_md_algo(nid); - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(algo); - - - if (md_info != NULL) { - *hlen = mbedtls_md_get_size(md_info); - mbedtls_md(md_info, digest, len, hash); - } -} - -EVPCTX evp_init(int nid) -{ - EVPCTX ctx = NULL; - int rc; - mbedtls_md_type_t algo = nid_to_md_algo(nid); - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(algo); - - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if (ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - -void evp_update(EVPCTX ctx, const void *data, unsigned long len) -{ - mbedtls_md_update(ctx, data, len); -} - -void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) -{ - *mdlen = mbedtls_md_get_size(ctx->md_info); - mbedtls_md_finish(ctx, md); - mbedtls_md_free(ctx); - SAFE_FREE(ctx); -} - -SHA256CTX sha256_init(void) -{ - SHA256CTX ctx = NULL; - int rc; - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); - - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if(ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - -void sha256_update(SHA256CTX c, const void *data, unsigned long len) -{ - mbedtls_md_update(c, data, len); -} - -void sha256_final(unsigned char *md, SHA256CTX c) -{ - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); -} - -void sha256(const unsigned char *digest, int len, unsigned char *hash) -{ - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); - } -} - -SHA384CTX sha384_init(void) -{ - SHA384CTX ctx = NULL; - int rc; - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); - - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if (ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - -void sha384_update(SHA384CTX c, const void *data, unsigned long len) -{ - mbedtls_md_update(c, data, len); -} - -void sha384_final(unsigned char *md, SHA384CTX c) -{ - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); -} - -void sha384(const unsigned char *digest, int len, unsigned char *hash) -{ - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); - } -} - -SHA512CTX sha512_init(void) -{ - SHA512CTX ctx = NULL; - int rc; - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if (ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - -void sha512_update(SHA512CTX c, const void *data, unsigned long len) -{ - mbedtls_md_update(c, data, len); -} - -void sha512_final(unsigned char *md, SHA512CTX c) -{ - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); -} - -void sha512(const unsigned char *digest, int len, unsigned char *hash) -{ - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); - } -} - -MD5CTX md5_init(void) -{ - MD5CTX ctx = NULL; - int rc; - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_MD5); - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if (ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - - -void md5_update(MD5CTX c, const void *data, unsigned long len) { - mbedtls_md_update(c, data, len); -} - -void md5_final(unsigned char *md, MD5CTX c) -{ - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); -} - int ssh_kdf(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, - int key_type, unsigned char *output, + uint8_t key_type, unsigned char *output, size_t requested_len) { return sshkdf_derive_key(crypto, key, key_len, key_type, output, requested_len); } -HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) +HMACCTX hmac_init(const void *key, size_t len, enum ssh_hmac_e type) { HMACCTX ctx = NULL; const mbedtls_md_info_t *md_info = NULL; @@ -446,17 +109,21 @@ error: return NULL; } -void hmac_update(HMACCTX c, const void *data, unsigned long len) +/* mbedtls returns 0 on success, but in this context + * success is 1 */ +int hmac_update(HMACCTX c, const void *data, size_t len) { - mbedtls_md_hmac_update(c, data, len); + return !mbedtls_md_hmac_update(c, data, len); } -void hmac_final(HMACCTX c, unsigned char *hashmacbuf, unsigned int *len) +int hmac_final(HMACCTX c, unsigned char *hashmacbuf, size_t *len) { - *len = mbedtls_md_get_size(c->md_info); - mbedtls_md_hmac_finish(c, hashmacbuf); + int rc; + *len = (unsigned int)mbedtls_md_get_size(c->MBEDTLS_PRIVATE(md_info)); + rc = !mbedtls_md_hmac_finish(c, hashmacbuf); mbedtls_md_free(c); SAFE_FREE(c); + return rc; } static int @@ -467,6 +134,8 @@ cipher_init(struct ssh_cipher_struct *cipher, { const mbedtls_cipher_info_t *cipher_info = NULL; mbedtls_cipher_context_t *ctx; + size_t key_bitlen = 0; + size_t iv_size = 0; int rc; if (operation == MBEDTLS_ENCRYPT) { @@ -474,7 +143,7 @@ cipher_init(struct ssh_cipher_struct *cipher, } else if (operation == MBEDTLS_DECRYPT) { ctx = &cipher->decrypt_ctx; } else { - SSH_LOG(SSH_LOG_WARNING, "unknown operation"); + SSH_LOG(SSH_LOG_TRACE, "unknown operation"); return 1; } @@ -483,21 +152,21 @@ cipher_init(struct ssh_cipher_struct *cipher, rc = mbedtls_cipher_setup(ctx, cipher_info); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_setup failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_setup failed"); goto error; } - rc = mbedtls_cipher_setkey(ctx, key, - cipher_info->key_bitlen, - operation); + key_bitlen = mbedtls_cipher_info_get_key_bitlen(cipher_info); + rc = mbedtls_cipher_setkey(ctx, key, key_bitlen, operation); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_setkey failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_setkey failed"); goto error; } - rc = mbedtls_cipher_set_iv(ctx, IV, cipher_info->iv_size); + iv_size = mbedtls_cipher_info_get_iv_size(cipher_info); + rc = mbedtls_cipher_set_iv(ctx, IV, iv_size); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_set_iv failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_set_iv failed"); goto error; } @@ -516,13 +185,13 @@ cipher_set_encrypt_key(struct ssh_cipher_struct *cipher, rc = cipher_init(cipher, MBEDTLS_ENCRYPT, key, IV); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "cipher_init failed"); + SSH_LOG(SSH_LOG_TRACE, "cipher_init failed"); goto error; } rc = mbedtls_cipher_reset(&cipher->encrypt_ctx); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_reset failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed"); goto error; } @@ -540,23 +209,23 @@ cipher_set_encrypt_key_cbc(struct ssh_cipher_struct *cipher, rc = cipher_init(cipher, MBEDTLS_ENCRYPT, key, IV); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "cipher_init failed"); + SSH_LOG(SSH_LOG_TRACE, "cipher_init failed"); goto error; } - /* libssh only encypts and decrypts packets that are multiples of a block + /* libssh only encrypts and decrypts packets that are multiples of a block * size, and no padding is used */ rc = mbedtls_cipher_set_padding_mode(&cipher->encrypt_ctx, MBEDTLS_PADDING_NONE); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_set_padding_mode failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_set_padding_mode failed"); goto error; } rc = mbedtls_cipher_reset(&cipher->encrypt_ctx); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_reset failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed"); goto error; } @@ -573,17 +242,18 @@ cipher_set_key_gcm(struct ssh_cipher_struct *cipher, void *IV) { const mbedtls_cipher_info_t *cipher_info = NULL; + size_t key_bitlen = 0; int rc; mbedtls_gcm_init(&cipher->gcm_ctx); cipher_info = mbedtls_cipher_info_from_type(cipher->type); - rc = mbedtls_gcm_setkey(&cipher->gcm_ctx, - MBEDTLS_CIPHER_ID_AES, - key, - cipher_info->key_bitlen); + key_bitlen = mbedtls_cipher_info_get_key_bitlen(cipher_info); + rc = mbedtls_gcm_setkey(&cipher->gcm_ctx, MBEDTLS_CIPHER_ID_AES, + key, key_bitlen); + if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_gcm_setkey failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_gcm_setkey failed"); goto error; } @@ -606,13 +276,13 @@ cipher_set_decrypt_key(struct ssh_cipher_struct *cipher, rc = cipher_init(cipher, MBEDTLS_DECRYPT, key, IV); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "cipher_init failed"); + SSH_LOG(SSH_LOG_TRACE, "cipher_init failed"); goto error; } mbedtls_cipher_reset(&cipher->decrypt_ctx); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_reset failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed"); goto error; } @@ -631,20 +301,20 @@ cipher_set_decrypt_key_cbc(struct ssh_cipher_struct *cipher, rc = cipher_init(cipher, MBEDTLS_DECRYPT, key, IV); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "cipher_init failed"); + SSH_LOG(SSH_LOG_TRACE, "cipher_init failed"); goto error; } rc = mbedtls_cipher_set_padding_mode(&cipher->decrypt_ctx, MBEDTLS_PADDING_NONE); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_set_padding_mode failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_set_padding_mode failed"); goto error; } mbedtls_cipher_reset(&cipher->decrypt_ctx); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_reset failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed"); goto error; } @@ -664,7 +334,7 @@ static void cipher_encrypt(struct ssh_cipher_struct *cipher, int rc = 0; rc = mbedtls_cipher_update(&cipher->encrypt_ctx, in, len, out, &outlen); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update failed during encryption"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_update failed during encryption"); return; } @@ -680,12 +350,12 @@ static void cipher_encrypt(struct ssh_cipher_struct *cipher, total_len += outlen; if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_finish failed during encryption"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_finish failed during encryption"); return; } if (total_len != len) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update: output size %zu for %zu", + SSH_LOG(SSH_LOG_DEBUG, "mbedtls_cipher_update: output size %zu for %zu", outlen, len); return; } @@ -693,18 +363,18 @@ static void cipher_encrypt(struct ssh_cipher_struct *cipher, } static void cipher_encrypt_cbc(struct ssh_cipher_struct *cipher, void *in, void *out, - unsigned long len) + size_t len) { size_t outlen = 0; int rc = 0; rc = mbedtls_cipher_update(&cipher->encrypt_ctx, in, len, out, &outlen); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update failed during encryption"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_update failed during encryption"); return; } if (outlen != len) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update: output size %zu for %zu", + SSH_LOG(SSH_LOG_DEBUG, "mbedtls_cipher_update: output size %zu for %zu", outlen, len); return; } @@ -722,7 +392,7 @@ static void cipher_decrypt(struct ssh_cipher_struct *cipher, rc = mbedtls_cipher_update(&cipher->decrypt_ctx, in, len, out, &outlen); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update failed during decryption"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_update failed during decryption"); return; } @@ -736,14 +406,14 @@ static void cipher_decrypt(struct ssh_cipher_struct *cipher, outlen, &outlen); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_reset failed during decryption"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed during decryption"); return; } total_len += outlen; if (total_len != len) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update: output size %zu for %zu", + SSH_LOG(SSH_LOG_DEBUG, "mbedtls_cipher_update: output size %zu for %zu", outlen, len); return; } @@ -751,13 +421,13 @@ static void cipher_decrypt(struct ssh_cipher_struct *cipher, } static void cipher_decrypt_cbc(struct ssh_cipher_struct *cipher, void *in, void *out, - unsigned long len) + size_t len) { size_t outlen = 0; int rc = 0; rc = mbedtls_cipher_update(&cipher->decrypt_ctx, in, len, out, &outlen); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update failed during decryption"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_update failed during decryption"); return; } @@ -776,19 +446,19 @@ static void cipher_decrypt_cbc(struct ssh_cipher_struct *cipher, void *in, void } if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_finish failed during decryption"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_finish failed during decryption"); return; } rc = mbedtls_cipher_reset(&cipher->decrypt_ctx); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_reset failed during decryption"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed during decryption"); return; } if (outlen != len) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update: output size %zu for %zu", + SSH_LOG(SSH_LOG_DEBUG, "mbedtls_cipher_update: output size %zu for %zu", outlen, len); return; } @@ -842,7 +512,7 @@ cipher_encrypt_gcm(struct ssh_cipher_struct *cipher, authlen, tag); /* tag */ if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_gcm_crypt_and_tag failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_gcm_crypt_and_tag failed"); return; } @@ -876,7 +546,7 @@ cipher_decrypt_gcm(struct ssh_cipher_struct *cipher, (const uint8_t *)complete_packet + aadlen, /* input */ (unsigned char *)out); /* output */ if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_gcm_auth_decrypt failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_gcm_auth_decrypt failed"); return SSH_ERROR; } @@ -950,14 +620,14 @@ chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher, /* K2 uses the first half of the key */ rv = mbedtls_chacha20_setkey(&ctx->main_ctx, u8key); if (rv != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_setkey(main_ctx) failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_setkey(main_ctx) failed"); goto out; } /* K1 uses the second half of the key */ rv = mbedtls_chacha20_setkey(&ctx->header_ctx, u8key + CHACHA20_KEYLEN); if (rv != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_setkey(header_ctx) failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_setkey(header_ctx) failed"); goto out; } @@ -981,7 +651,7 @@ chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher, /* The nonce in mbedTLS is 96 b long. The counter is passed through separate * parameter of 32 b size. - * Encode the seqence number into the last 8 bytes. + * Encode the sequence number into the last 8 bytes. */ PUSH_BE_U64(seqbuf, 4, seq); #ifdef DEBUG_CRYPTO @@ -990,13 +660,13 @@ chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher, ret = mbedtls_chacha20_starts(&ctx->header_ctx, seqbuf, 0); if (ret != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_starts(header_ctx) failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_starts(header_ctx) failed"); return SSH_ERROR; } - ret = mbedtls_chacha20_starts(&ctx->header_ctx, seqbuf, 0); + ret = mbedtls_chacha20_starts(&ctx->main_ctx, seqbuf, 0); if (ret != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_starts(header_ctx) failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_starts(main_ctx) failed"); return SSH_ERROR; } @@ -1025,7 +695,7 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, rv = mbedtls_chacha20_update(&ctx->main_ctx, sizeof(zero_block), zero_block, poly_key); if (rv != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); goto out; } #ifdef DEBUG_CRYPTO @@ -1035,7 +705,7 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, /* Set the Poly1305 key */ rv = mbedtls_poly1305_starts(&ctx->poly_ctx, poly_key); if (rv != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_starts failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_starts failed"); goto out; } @@ -1071,7 +741,7 @@ chacha20_poly1305_aead_decrypt_length(struct ssh_cipher_struct *cipher, rv = mbedtls_chacha20_update(&ctx->header_ctx, sizeof(uint32_t), in, out); if (rv != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); return SSH_ERROR; } @@ -1099,7 +769,7 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, /* Prepare the Poly1305 key */ rv = chacha20_poly1305_packet_setup(cipher, seq, 0); if (rv != SSH_OK) { - SSH_LOG(SSH_LOG_WARNING, "Failed to setup packet"); + SSH_LOG(SSH_LOG_TRACE, "Failed to setup packet"); goto out; } @@ -1111,13 +781,13 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, rv = mbedtls_poly1305_update(&ctx->poly_ctx, complete_packet, encrypted_size + sizeof(uint32_t)); if (rv != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_update failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_update failed"); goto out; } rv = mbedtls_poly1305_finish(&ctx->poly_ctx, tag); if (rv != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_finish failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_finish failed"); goto out; } @@ -1126,7 +796,7 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, #endif /* DEBUG_CRYPTO */ /* Verify the calculated MAC matches the attached MAC */ - cmp = memcmp(tag, mac, POLY1305_TAGLEN); + cmp = secure_memcmp(tag, mac, POLY1305_TAGLEN); if (cmp != 0) { /* mac error */ SSH_LOG(SSH_LOG_PACKET, "poly1305 verify error"); @@ -1138,7 +808,7 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, (uint8_t *)complete_packet + sizeof(uint32_t), out); if (rv != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); goto out; } @@ -1162,7 +832,7 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, /* Prepare the Poly1305 key */ ret = chacha20_poly1305_packet_setup(cipher, seq, 1); if (ret != SSH_OK) { - SSH_LOG(SSH_LOG_WARNING, "Failed to setup packet"); + SSH_LOG(SSH_LOG_TRACE, "Failed to setup packet"); return; } @@ -1175,7 +845,7 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, (unsigned char *)&in_packet->length, (unsigned char *)&out_packet->length); if (ret != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); return; } #ifdef DEBUG_CRYPTO @@ -1187,20 +857,20 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, /* We already did encrypt one block so the counter should be in the correct position */ ret = mbedtls_chacha20_update(&ctx->main_ctx, len - sizeof(uint32_t), in_packet->payload, out_packet->payload); - if (ret != 1) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + if (ret != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); return; } /* step 4, compute the MAC */ ret = mbedtls_poly1305_update(&ctx->poly_ctx, (const unsigned char *)out_packet, len); if (ret != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_update failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_update failed"); return; } ret = mbedtls_poly1305_finish(&ctx->poly_ctx, tag); if (ret != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_finish failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_finish failed"); return; } } @@ -1411,7 +1081,7 @@ int ssh_crypto_init(void) mbedtls_ctr_drbg_free(&ssh_mbedtls_ctr_drbg); } -#if defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) +#if !(defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C)) for (i = 0; ssh_ciphertab[i].name != NULL; i++) { int cmp; @@ -1430,22 +1100,6 @@ int ssh_crypto_init(void) return SSH_OK; } -int ssh_mbedtls_random(void *where, int len, int strong) -{ - int rc = 0; - if (strong) { - mbedtls_ctr_drbg_set_prediction_resistance(&ssh_mbedtls_ctr_drbg, - MBEDTLS_CTR_DRBG_PR_ON); - rc = mbedtls_ctr_drbg_random(&ssh_mbedtls_ctr_drbg, where, len); - mbedtls_ctr_drbg_set_prediction_resistance(&ssh_mbedtls_ctr_drbg, - MBEDTLS_CTR_DRBG_PR_OFF); - } else { - rc = mbedtls_ctr_drbg_random(&ssh_mbedtls_ctr_drbg, where, len); - } - - return !rc; -} - mbedtls_ctr_drbg_context *ssh_get_mbedtls_ctr_drbg_context(void) { return &ssh_mbedtls_ctr_drbg; diff --git a/src/libssh.map b/src/libssh.map index c9bedee0..558f921d 100644 --- a/src/libssh.map +++ b/src/libssh.map @@ -1,4 +1,4 @@ -# This map file was updated with abimap-0.3.1 +# This map file was updated with abimap-0.3.2 LIBSSH_4_5_0 # Released { @@ -447,3 +447,30 @@ LIBSSH_4_8_1 # Released ssh_session_get_known_hosts_entry; ssh_threads_get_default; } LIBSSH_4_8_0; + +LIBSSH_4_9_0 # Released +{ + global: + ssh_channel_open_forward_port; + ssh_key_dup; + ssh_send_issue_banner; + ssh_session_set_disconnect_message; + ssh_userauth_publickey_auto_get_current_identity; + ssh_vlog; +} LIBSSH_4_8_1; + +LIBSSH_AFTER_4_9_0 +{ + global: + sftp_channel_default_data_callback; + sftp_channel_default_subsystem_request; + sftp_aio_begin_read; + sftp_aio_begin_write; + sftp_aio_free; + sftp_aio_wait_read; + sftp_aio_wait_write; + ssh_pki_export_privkey_base64_format; + ssh_pki_export_privkey_file_format; + ssh_channel_request_pty_size_modes; +} LIBSSH_4_9_0; + @@ -38,12 +38,16 @@ #include "libssh/misc.h" #include "libssh/session.h" +#ifndef LOG_SIZE +#define LOG_SIZE 1024 +#endif + static LIBSSH_THREAD int ssh_log_level; static LIBSSH_THREAD ssh_logging_callback ssh_log_cb; static LIBSSH_THREAD void *ssh_log_userdata; /** - * @defgroup libssh_log The SSH logging functions. + * @defgroup libssh_log The SSH logging functions * @ingroup libssh * * Logging functions for debugging and problem resolving. @@ -94,38 +98,52 @@ static void ssh_log_stderr(int verbosity, fprintf(stderr, " %s\n", buffer); } +static void ssh_log_custom(ssh_logging_callback log_fn, + int verbosity, + const char *function, + const char *buffer) +{ + char buf[LOG_SIZE + 64]; + + snprintf(buf, sizeof(buf), "%s: %s", function, buffer); + log_fn(verbosity, function, buf, ssh_get_log_userdata()); +} + void ssh_log_function(int verbosity, const char *function, const char *buffer) { ssh_logging_callback log_fn = ssh_get_log_callback(); - if (log_fn) { - char buf[1024]; - snprintf(buf, sizeof(buf), "%s: %s", function, buffer); - - log_fn(verbosity, - function, - buf, - ssh_get_log_userdata()); + if (log_fn) { + ssh_log_custom(log_fn, verbosity, function, buffer); return; } ssh_log_stderr(verbosity, function, buffer); } +void ssh_vlog(int verbosity, + const char *function, + const char *format, + va_list *va) +{ + char buffer[LOG_SIZE]; + + vsnprintf(buffer, sizeof(buffer), format, *va); + ssh_log_function(verbosity, function, buffer); +} + void _ssh_log(int verbosity, const char *function, const char *format, ...) { - char buffer[1024]; va_list va; if (verbosity <= ssh_get_log_level()) { va_start(va, format); - vsnprintf(buffer, sizeof(buffer), format, va); + ssh_vlog(verbosity, function, format, &va); va_end(va); - ssh_log_function(verbosity, function, buffer); } } @@ -135,14 +153,12 @@ void ssh_log(ssh_session session, int verbosity, const char *format, ...) { - char buffer[1024]; va_list va; if (verbosity <= session->common.log_verbosity) { va_start(va, format); - vsnprintf(buffer, sizeof(buffer), format, va); + ssh_vlog(verbosity, "", format, &va); va_end(va); - ssh_log_function(verbosity, "", buffer); } } @@ -157,14 +173,12 @@ void ssh_log_common(struct ssh_common_struct *common, const char *function, const char *format, ...) { - char buffer[1024]; va_list va; if (verbosity <= common->log_verbosity) { va_start(va, format); - vsnprintf(buffer, sizeof(buffer), format, va); + ssh_vlog(verbosity, function, format, &va); va_end(va); - ssh_log_function(verbosity, function, buffer); } } diff --git a/src/match.c b/src/match.c index 1a60d732..3e58f733 100644 --- a/src/match.c +++ b/src/match.c @@ -43,7 +43,7 @@ #include "libssh/priv.h" -#define MAX_MATCH_RECURSION 32 +#define MAX_MATCH_RECURSION 16 /* * Returns true if the given string matches the pattern (which may contain ? @@ -51,74 +51,77 @@ */ static int match_pattern(const char *s, const char *pattern, size_t limit) { - bool had_asterisk = false; - if (s == NULL || pattern == NULL || limit <= 0) { - return 0; - } + bool had_asterisk = false; - for (;;) { - /* If at end of pattern, accept if also at end of string. */ - if (*pattern == '\0') { - return (*s == '\0'); + if (s == NULL || pattern == NULL || limit <= 0) { + return 0; } - while (*pattern == '*') { - /* Skip the asterisk. */ - had_asterisk = true; - pattern++; - } + for (;;) { + /* If at end of pattern, accept if also at end of string. */ + if (*pattern == '\0') { + return (*s == '\0'); + } - if (had_asterisk) { - /* If at end of pattern, accept immediately. */ - if (!*pattern) - return 1; + /* Skip all the asterisks and adjacent question marks */ + while (*pattern == '*' || (had_asterisk && *pattern == '?')) { + if (*pattern == '*') { + had_asterisk = true; + } + pattern++; + } - /* If next character in pattern is known, optimize. */ - if (*pattern != '?') { + if (had_asterisk) { + /* If at end of pattern, accept immediately. */ + if (!*pattern) + return 1; + + /* If next character in pattern is known, optimize. */ + if (*pattern != '?') { + /* + * Look instances of the next character in + * pattern, and try to match starting from + * those. + */ + for (; *s; s++) + if (*s == *pattern && match_pattern(s + 1, pattern + 1, limit - 1)) { + return 1; + } + /* Failed. */ + return 0; + } + /* + * Move ahead one character at a time and try to + * match at each position. + */ + for (; *s; s++) { + if (match_pattern(s, pattern, limit - 1)) { + return 1; + } + } + /* Failed. */ + return 0; + } /* - * Look instances of the next character in - * pattern, and try to match starting from - * those. + * There must be at least one more character in the string. + * If we are at the end, fail. */ - for (; *s; s++) - if (*s == *pattern && match_pattern(s + 1, pattern + 1, limit - 1)) { - return 1; - } - /* Failed. */ - return 0; - } - /* - * Move ahead one character at a time and try to - * match at each position. - */ - for (; *s; s++) { - if (match_pattern(s, pattern, limit - 1)) { - return 1; + if (!*s) { + return 0; } - } - /* Failed. */ - return 0; - } - /* - * There must be at least one more character in the string. - * If we are at the end, fail. - */ - if (!*s) { - return 0; - } - /* Check if the next character of the string is acceptable. */ - if (*pattern != '?' && *pattern != *s) { - return 0; - } + /* Check if the next character of the string is acceptable. */ + if (*pattern != '?' && *pattern != *s) { + return 0; + } - /* Move to the next character, both in string and in pattern. */ - s++; - pattern++; - } + /* Move to the next character, both in string and in pattern. */ + s++; + pattern++; + } - /* NOTREACHED */ - return 0; + /* NOTREACHED */ + return 0; } /* @@ -128,11 +131,11 @@ static int match_pattern(const char *s, const char *pattern, size_t limit) * no match at all. */ int match_pattern_list(const char *string, const char *pattern, - unsigned int len, int dolower) { + size_t len, int dolower) { char sub[1024]; int negated; int got_positive; - unsigned int i, subi; + size_t i, subi; got_positive = 0; for (i = 0; i < len;) { diff --git a/src/mbedcrypto-compat.h b/src/mbedcrypto-compat.h new file mode 100644 index 00000000..705294a7 --- /dev/null +++ b/src/mbedcrypto-compat.h @@ -0,0 +1,39 @@ +#ifndef MBEDCRYPTO_COMPAT_H +#define MBEDCRYPTO_COMPAT_H + +/* mbedtls/version.h should be available for both v2 and v3 + * v3 defines the version inside build_info.h so if it isn't defined + * in version.h we should have v3 + */ +#include <mbedtls/version.h> +#include <mbedtls/cipher.h> +#ifdef MBEDTLS_VERSION_MAJOR +#if MBEDTLS_VERSION_MAJOR < 3 + +static inline size_t mbedtls_cipher_info_get_key_bitlen( + const mbedtls_cipher_info_t *info) +{ + if (info == NULL) { + return 0; + } + return info->key_bitlen; +} + +static inline size_t mbedtls_cipher_info_get_iv_size( + const mbedtls_cipher_info_t *info) +{ + if (info == NULL) { + return 0; + } + return (size_t)info->iv_size; +} + +#define MBEDTLS_PRIVATE(X) X +#endif /* MBEDTLS_VERSION_MAJOR < 3 */ +#else /* MBEDTLS_VERSION_MAJOR */ +#include <mbedtls/build_info.h> +#if MBEDTLS_VERSION_MAJOR < 3 +#define MBEDTLS_PRIVATE(X) X +#endif /* MBEDTLS_VERSION_MAJOR < 3 */ +#endif /* MBEDTLS_VERSION_MAJOR */ +#endif /* MBEDCRYPTO_COMPAT_H */ diff --git a/src/mbedcrypto_missing.c b/src/mbedcrypto_missing.c index ac0d688c..2c1a8d7a 100644 --- a/src/mbedcrypto_missing.c +++ b/src/mbedcrypto_missing.c @@ -45,7 +45,7 @@ void ssh_mbedcry_bn_free(bignum bn) SAFE_FREE(bn); } -unsigned char *ssh_mbedcry_bn2num(const_bignum num, int radix) +char *ssh_mbedcry_bn2num(const_bignum num, int radix) { char *buf = NULL; size_t olen; @@ -56,7 +56,7 @@ unsigned char *ssh_mbedcry_bn2num(const_bignum num, int radix) return NULL; } - buf = malloc(olen); + buf = mbedtls_calloc(1, olen); if (buf == NULL) { return NULL; } @@ -67,7 +67,7 @@ unsigned char *ssh_mbedcry_bn2num(const_bignum num, int radix) return NULL; } - return (unsigned char *) buf; + return buf; } int ssh_mbedcry_rand(bignum rnd, int bits, int top, int bottom) diff --git a/src/md_crypto.c b/src/md_crypto.c new file mode 100644 index 00000000..f7cda8dd --- /dev/null +++ b/src/md_crypto.c @@ -0,0 +1,324 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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" + +#include "libcrypto-compat.h" +#include "libssh/crypto.h" +#include "libssh/wrapper.h" + +#include <openssl/crypto.h> +#include <openssl/evp.h> +#include <openssl/md5.h> +#include <openssl/sha.h> + +SHACTX +sha1_init(void) +{ + int rc; + SHACTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha1(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha1_ctx_free(SHACTX c) +{ + EVP_MD_CTX_free(c); +} + +int +sha1_update(SHACTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha1_final(unsigned char *md, SHACTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha1(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHACTX c = sha1_init(); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha1_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha1_final(hash, c); +} + +SHA256CTX +sha256_init(void) +{ + int rc; + SHA256CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha256(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha256_ctx_free(SHA256CTX c) +{ + EVP_MD_CTX_free(c); +} + +int +sha256_update(SHA256CTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha256_final(unsigned char *md, SHA256CTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha256(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHA256CTX c = sha256_init(); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha256_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha256_final(hash, c); +} + +SHA384CTX +sha384_init(void) +{ + int rc; + SHA384CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha384(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha384_ctx_free(SHA384CTX c) +{ + EVP_MD_CTX_free(c); +} + +int +sha384_update(SHA384CTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha384_final(unsigned char *md, SHA384CTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha384(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHA384CTX c = sha384_init(); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha384_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha384_final(hash, c); +} + +SHA512CTX +sha512_init(void) +{ + int rc = 0; + SHA512CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha512(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha512_ctx_free(SHA512CTX c) +{ + EVP_MD_CTX_free(c); +} + +int +sha512_update(SHA512CTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha512_final(unsigned char *md, SHA512CTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha512(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHA512CTX c = sha512_init(); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha512_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha512_final(hash, c); +} + +MD5CTX +md5_init(void) +{ + int rc; + MD5CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_md5(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +md5_ctx_free(MD5CTX c) +{ + EVP_MD_CTX_free(c); +} + +int +md5_update(MD5CTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +md5_final(unsigned char *md, MD5CTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} diff --git a/src/md_gcrypt.c b/src/md_gcrypt.c new file mode 100644 index 00000000..93c7b0d9 --- /dev/null +++ b/src/md_gcrypt.c @@ -0,0 +1,246 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * Copyright (C) 2016 g10 Code GmbH + * + * 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" + +#include "libssh/crypto.h" +#include "libssh/wrapper.h" + +#include <gcrypt.h> + +SHACTX +sha1_init(void) +{ + SHACTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA1, 0); + + return ctx; +} + +int +sha1_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +void +sha1_ctx_free(SHACTX c) +{ + gcry_md_close(c); +} + +int +sha1_final(unsigned char *md, SHACTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int +sha1(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA1, hash, digest, len); + return SSH_OK; +} + +SHA256CTX +sha256_init(void) +{ + SHA256CTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA256, 0); + + return ctx; +} + +void +sha256_ctx_free(SHA256CTX c) +{ + gcry_md_close(c); +} + +int +sha256_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +int +sha256_final(unsigned char *md, SHACTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA256_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int +sha256(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA256, hash, digest, len); + return SSH_OK; +} + +SHA384CTX +sha384_init(void) +{ + SHA384CTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA384, 0); + + return ctx; +} + +void +sha384_ctx_free(SHA384CTX c) +{ + gcry_md_close(c); +} + +int +sha384_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +int +sha384_final(unsigned char *md, SHACTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA384_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int +sha384(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA384, hash, digest, len); + return SSH_OK; +} + +SHA512CTX +sha512_init(void) +{ + SHA512CTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA512, 0); + + return ctx; +} + +void +sha512_ctx_free(SHA512CTX c) +{ + gcry_md_close(c); +} + +int +sha512_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +int +sha512_final(unsigned char *md, SHACTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA512_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int +sha512(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA512, hash, digest, len); + return SSH_OK; +} + +MD5CTX +md5_init(void) +{ + MD5CTX c = NULL; + gcry_md_open(&c, GCRY_MD_MD5, 0); + + return c; +} + +void +md5_ctx_free(MD5CTX c) +{ + gcry_md_close(c); +} + +int +md5_update(MD5CTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +int +md5_final(unsigned char *md, MD5CTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, MD5_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} diff --git a/src/md_mbedcrypto.c b/src/md_mbedcrypto.c new file mode 100644 index 00000000..b3529b4b --- /dev/null +++ b/src/md_mbedcrypto.c @@ -0,0 +1,407 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk <juraj.vijtiuk@sartura.hr> + * + * 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" + +#include "libssh/crypto.h" +#include "libssh/wrapper.h" +#include "mbedcrypto-compat.h" + +#include <mbedtls/md.h> + +SHACTX +sha1_init(void) +{ + SHACTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha1_ctx_free(SHACTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +sha1_update(SHACTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha1_final(unsigned char *md, SHACTX c) +{ + int rc = mbedtls_md_finish(c, md); + sha1_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha1(const unsigned char *digest, size_t len, unsigned char *hash) +{ + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + int rc; + + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +SHA256CTX +sha256_init(void) +{ + SHA256CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha256_ctx_free(SHA256CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +sha256_update(SHA256CTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha256_final(unsigned char *md, SHA256CTX c) +{ + int rc = mbedtls_md_finish(c, md); + mbedtls_md_free(c); + SAFE_FREE(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha256(const unsigned char *digest, size_t len, unsigned char *hash) +{ + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +SHA384CTX +sha384_init(void) +{ + SHA384CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); + + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha384_ctx_free(SHA384CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +sha384_update(SHA384CTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha384_final(unsigned char *md, SHA384CTX c) +{ + int rc = mbedtls_md_finish(c, md); + sha384_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha384(const unsigned char *digest, size_t len, unsigned char *hash) +{ + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); + int rc; + + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +SHA512CTX +sha512_init(void) +{ + SHA512CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha512_ctx_free(SHA512CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +sha512_update(SHA512CTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha512_final(unsigned char *md, SHA512CTX c) +{ + int rc = mbedtls_md_finish(c, md); + sha512_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha512(const unsigned char *digest, size_t len, unsigned char *hash) +{ + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + int rc; + + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +MD5CTX +md5_init(void) +{ + MD5CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_MD5); + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +md5_ctx_free(MD5CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +md5_update(MD5CTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +md5_final(unsigned char *md, MD5CTX c) +{ + int rc = mbedtls_md_finish(c, md); + mbedtls_md_free(c); + SAFE_FREE(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} diff --git a/src/messages.c b/src/messages.c index 25683b23..be9462ac 100644 --- a/src/messages.c +++ b/src/messages.c @@ -54,7 +54,7 @@ * This file contains the message parsing utilities for client and server * programs using libssh. * - * On the server the the main loop of the program will call + * On the server the main loop of the program will call * ssh_message_get(session) to get messages as they come. They are not 1-1 with * the protocol messages. Then, the user will know what kind of a message it is * and use the appropriate functions to handle it (or use the default handlers @@ -79,7 +79,7 @@ static ssh_message ssh_message_new(ssh_session session) #ifndef WITH_SERVER -/* Reduced version of the reply default that only reply with +/* Reduced version of the reply default that only replies with * SSH_MSG_UNIMPLEMENTED */ static int ssh_message_reply_default(ssh_message msg) { @@ -160,7 +160,7 @@ static int ssh_execute_server_request(ssh_session session, ssh_message msg) if (channel != NULL) { rc = ssh_message_channel_request_open_reply_accept_channel(msg, channel); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_TRACE, "Failed to send reply for accepting a channel " "open"); } @@ -237,7 +237,7 @@ static int ssh_execute_server_request(ssh_session session, ssh_message msg) msg->channel_request.pxwidth, msg->channel_request.pxheight); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_TRACE, "Failed to iterate callbacks for window change"); } return SSH_OK; @@ -317,6 +317,17 @@ static int ssh_execute_server_request(ssh_session session, ssh_message msg) return SSH_AGAIN; } +static int ssh_reply_channel_open_request(ssh_message msg, ssh_channel channel) +{ + if (channel != NULL) { + return ssh_message_channel_request_open_reply_accept_channel(msg, channel); + } + + ssh_message_reply_default(msg); + + return SSH_OK; +} + static int ssh_execute_client_request(ssh_session session, ssh_message msg) { ssh_channel channel = NULL; @@ -329,30 +340,26 @@ static int ssh_execute_client_request(ssh_session session, ssh_message msg) msg->channel_request_open.originator, msg->channel_request_open.originator_port, session->common.callbacks->userdata); - if (channel != NULL) { - rc = ssh_message_channel_request_open_reply_accept_channel(msg, channel); - return rc; - } else { - ssh_message_reply_default(msg); - } - - return SSH_OK; + return ssh_reply_channel_open_request(msg, channel); } else if (msg->type == SSH_REQUEST_CHANNEL_OPEN && msg->channel_request_open.type == SSH_CHANNEL_AUTH_AGENT && ssh_callbacks_exists(session->common.callbacks, channel_open_request_auth_agent_function)) { channel = session->common.callbacks->channel_open_request_auth_agent_function (session, session->common.callbacks->userdata); - if (channel != NULL) { - rc = ssh_message_channel_request_open_reply_accept_channel(msg, channel); - - return rc; - } else { - ssh_message_reply_default(msg); - } + return ssh_reply_channel_open_request(msg, channel); + } else if (msg->type == SSH_REQUEST_CHANNEL_OPEN + && msg->channel_request_open.type == SSH_CHANNEL_FORWARDED_TCPIP + && ssh_callbacks_exists(session->common.callbacks, channel_open_request_forwarded_tcpip_function)) { + channel = session->common.callbacks->channel_open_request_forwarded_tcpip_function(session, + msg->channel_request_open.destination, + msg->channel_request_open.destination_port, + msg->channel_request_open.originator, + msg->channel_request_open.originator_port, + session->common.callbacks->userdata); - return SSH_OK; + return ssh_reply_channel_open_request(msg, channel); } return rc; @@ -513,24 +520,30 @@ static int ssh_message_termination(void *s){ * @warning This function blocks until a message has been received. Betterset up * a callback if this behavior is unwanted. */ -ssh_message ssh_message_get(ssh_session session) { - ssh_message msg = NULL; - int rc; +ssh_message ssh_message_get(ssh_session session) +{ + ssh_message msg = NULL; + int rc; - msg=ssh_message_pop_head(session); - if(msg) { - return msg; - } - if(session->ssh_message_list == NULL) { - session->ssh_message_list = ssh_list_new(); - } - rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, - ssh_message_termination, session); - if(rc || session->session_state == SSH_SESSION_STATE_ERROR) - return NULL; - msg=ssh_list_pop_head(ssh_message, session->ssh_message_list); + msg = ssh_message_pop_head(session); + if (msg != NULL) { + return msg; + } + if (session->ssh_message_list == NULL) { + session->ssh_message_list = ssh_list_new(); + if (session->ssh_message_list == NULL) { + ssh_set_error_oom(session); + return NULL; + } + } + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, + ssh_message_termination, session); + if (rc || session->session_state == SSH_SESSION_STATE_ERROR) { + return NULL; + } + msg = ssh_list_pop_head(ssh_message, session->ssh_message_list); - return msg; + return msg; } /** @@ -587,6 +600,7 @@ void ssh_message_free(ssh_message msg){ switch(msg->type) { case SSH_REQUEST_AUTH: SAFE_FREE(msg->auth_request.username); + SAFE_FREE(msg->auth_request.sigtype); if (msg->auth_request.password) { explicit_bzero(msg->auth_request.password, strlen(msg->auth_request.password)); @@ -708,8 +722,8 @@ static ssh_buffer ssh_msg_userauth_build_digest(ssh_session session, rc = ssh_buffer_pack(buffer, "dPbsssbsS", - crypto->digest_len, /* session ID string */ - (size_t)crypto->digest_len, crypto->session_id, + crypto->session_id_len, /* session ID string */ + crypto->session_id_len, crypto->session_id, SSH2_MSG_USERAUTH_REQUEST, /* type */ msg->auth_request.username, service, @@ -768,7 +782,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_request){ cmp = strcmp(service, "ssh-connection"); if (cmp != 0) { - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_TRACE, "Invalid service request: %s", service); goto end; @@ -846,6 +860,14 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_request){ goto error; } msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_NONE; + msg->auth_request.sigtype = strdup(ssh_string_get_char(algo)); + if (msg->auth_request.sigtype == NULL) { + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_ERROR; + SSH_STRING_FREE(algo); + algo = NULL; + goto error; + } + // has a valid signature ? if(has_sign) { ssh_string sig_blob = NULL; @@ -1040,7 +1062,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ } if (session->kbdint == NULL) { - SSH_LOG(SSH_LOG_PROTOCOL, "Warning: Got a keyboard-interactive " + SSH_LOG(SSH_LOG_DEBUG, "Warning: Got a keyboard-interactive " "response but it seems we didn't send the request."); session->kbdint = ssh_kbdint_new(); @@ -1061,10 +1083,10 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ session->kbdint->nanswers = 0; } - SSH_LOG(SSH_LOG_PACKET,"kbdint: %d answers",nanswers); + SSH_LOG(SSH_LOG_PACKET,"kbdint: %" PRIu32 " answers", nanswers); if (nanswers > KBDINT_MAX_PROMPT) { ssh_set_error(session, SSH_FATAL, - "Too much answers received from client: %u (0x%.4x)", + "Too much answers received from client: %" PRIu32 " (0x%.4" PRIx32 ")", nanswers, nanswers); ssh_kbdint_free(session->kbdint); session->kbdint = NULL; @@ -1074,8 +1096,8 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ if(nanswers != session->kbdint->nprompts) { /* warn but let the application handle this case */ - SSH_LOG(SSH_LOG_PROTOCOL, "Warning: Number of prompts and answers" - " mismatch: p=%u a=%u", session->kbdint->nprompts, nanswers); + SSH_LOG(SSH_LOG_DEBUG, "Warning: Number of prompts and answers" + " mismatch: p=%" PRIu32 " a=%" PRIu32, session->kbdint->nprompts, nanswers); } session->kbdint->nanswers = nanswers; @@ -1154,8 +1176,16 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open){ ssh_set_error(session,SSH_FATAL, "Invalid state when receiving channel open request (must be authenticated)"); goto error; } - - if (strcmp(type_c,"session") == 0) { + + if (strcmp(type_c, "session") == 0) { + if (session->flags & SSH_SESSION_FLAG_NO_MORE_SESSIONS) { + ssh_session_set_disconnect_message(session, "No more sessions allowed!"); + ssh_set_error(session, SSH_FATAL, "No more sessions allowed!"); + session->session_state = SSH_SESSION_STATE_ERROR; + ssh_disconnect(session); + goto error; + } + msg->channel_request_open.type = SSH_CHANNEL_SESSION; SAFE_FREE(type_c); goto end; @@ -1168,9 +1198,9 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open){ &destination_port, &msg->channel_request_open.originator, &originator_port); - if (rc != SSH_OK) { - goto error; - } + if (rc != SSH_OK) { + goto error; + } msg->channel_request_open.destination_port = (uint16_t) destination_port; msg->channel_request_open.originator_port = (uint16_t) originator_port; @@ -1233,7 +1263,7 @@ end: * * @param[in] chan The channel the request is made on. * - * @returns SSH_OK on success, SSH_ERROR if an error occured. + * @returns SSH_OK on success, SSH_ERROR if an error occurred. */ int ssh_message_channel_request_open_reply_accept_channel(ssh_message msg, ssh_channel chan) { ssh_session session; @@ -1267,7 +1297,7 @@ int ssh_message_channel_request_open_reply_accept_channel(ssh_message msg, ssh_c } SSH_LOG(SSH_LOG_PACKET, - "Accepting a channel request_open for chan %d", + "Accepting a channel request_open for chan %" PRIu32, chan->remote_channel); rc = ssh_packet_send(session); @@ -1324,7 +1354,7 @@ ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg) { * * @param[in] want_reply The want_reply field from the request. * - * @returns SSH_OK on success, SSH_ERROR if an error occured. + * @returns SSH_OK on success, SSH_ERROR if an error occurred. */ int ssh_message_handle_channel_request(ssh_session session, ssh_channel channel, ssh_buffer packet, const char *request, uint8_t want_reply) { @@ -1338,7 +1368,7 @@ int ssh_message_handle_channel_request(ssh_session session, ssh_channel channel, } SSH_LOG(SSH_LOG_PACKET, - "Received a %s channel_request for channel (%d:%d) (want_reply=%hhd)", + "Received a %s channel_request for channel (%" PRIu32 ":%" PRIu32 ") (want_reply=%hhd)", request, channel->local_channel, channel->remote_channel, want_reply); msg->type = SSH_REQUEST_CHANNEL; @@ -1438,6 +1468,14 @@ error: return SSH_ERROR; } +/** @internal + * + * @brief Sends a successful channel request reply + * + * @param msg A message to reply to + * + * @returns SSH_OK on success, SSH_ERROR if an error occurred. + */ int ssh_message_channel_request_reply_success(ssh_message msg) { uint32_t channel; int rc; @@ -1450,7 +1488,7 @@ int ssh_message_channel_request_reply_success(ssh_message msg) { channel = msg->channel_request.channel->remote_channel; SSH_LOG(SSH_LOG_PACKET, - "Sending a channel_request success to channel %d", channel); + "Sending a channel_request success to channel %" PRIu32, channel); rc = ssh_buffer_pack(msg->session->out_buffer, "bd", @@ -1481,7 +1519,7 @@ SSH_PACKET_CALLBACK(ssh_packet_global_request){ (void)type; (void)packet; - SSH_LOG(SSH_LOG_PROTOCOL,"Received SSH_MSG_GLOBAL_REQUEST packet"); + SSH_LOG(SSH_LOG_DEBUG,"Received SSH_MSG_GLOBAL_REQUEST packet"); r = ssh_buffer_unpack(packet, "sb", &request, &want_reply); @@ -1513,12 +1551,12 @@ SSH_PACKET_CALLBACK(ssh_packet_global_request){ msg->global_request.type = SSH_GLOBAL_REQUEST_TCPIP_FORWARD; msg->global_request.want_reply = want_reply; - SSH_LOG(SSH_LOG_PROTOCOL, "Received SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, want_reply, + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, want_reply, msg->global_request.bind_address, msg->global_request.bind_port); if(ssh_callbacks_exists(session->common.callbacks, global_request_function)) { - SSH_LOG(SSH_LOG_PROTOCOL, "Calling callback for SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, + SSH_LOG(SSH_LOG_DEBUG, "Calling callback for SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, want_reply, msg->global_request.bind_address, msg->global_request.bind_port); session->common.callbacks->global_request_function(session, msg, session->common.callbacks->userdata); @@ -1543,7 +1581,7 @@ SSH_PACKET_CALLBACK(ssh_packet_global_request){ msg->global_request.type = SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD; msg->global_request.want_reply = want_reply; - SSH_LOG(SSH_LOG_PROTOCOL, "Received SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, want_reply, + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, want_reply, msg->global_request.bind_address, msg->global_request.bind_port); @@ -1557,14 +1595,23 @@ SSH_PACKET_CALLBACK(ssh_packet_global_request){ } else if(strcmp(request, "keepalive@openssh.com") == 0) { msg->global_request.type = SSH_GLOBAL_REQUEST_KEEPALIVE; msg->global_request.want_reply = want_reply; - SSH_LOG(SSH_LOG_PROTOCOL, "Received keepalive@openssh.com %d", want_reply); + SSH_LOG(SSH_LOG_DEBUG, "Received keepalive@openssh.com %d", want_reply); if(ssh_callbacks_exists(session->common.callbacks, global_request_function)) { session->common.callbacks->global_request_function(session, msg, session->common.callbacks->userdata); } else { ssh_message_global_request_reply_success(msg, 0); } + } else if (strcmp(request, "no-more-sessions@openssh.com") == 0) { + msg->global_request.type = SSH_GLOBAL_REQUEST_NO_MORE_SESSIONS; + msg->global_request.want_reply = want_reply; + + SSH_LOG(SSH_LOG_PROTOCOL, "Received no-more-sessions@openssh.com %d", want_reply); + + ssh_message_global_request_reply_success(msg, 0); + + session->flags |= SSH_SESSION_FLAG_NO_MORE_SESSIONS; } else { - SSH_LOG(SSH_LOG_PROTOCOL, "UNKNOWN SSH_MSG_GLOBAL_REQUEST %s, " + SSH_LOG(SSH_LOG_DEBUG, "UNKNOWN SSH_MSG_GLOBAL_REQUEST %s, " "want_reply = %d", request, want_reply); goto reply_with_failure; } @@ -1597,7 +1644,7 @@ reply_with_failure: error: SAFE_FREE(msg); SAFE_FREE(request); - SSH_LOG(SSH_LOG_WARNING, "Invalid SSH_MSG_GLOBAL_REQUEST packet"); + SSH_LOG(SSH_LOG_TRACE, "Invalid SSH_MSG_GLOBAL_REQUEST packet"); return rc; } @@ -32,6 +32,7 @@ #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> +#include <net/if.h> #endif /* _WIN32 */ @@ -59,6 +60,7 @@ #include <ws2tcpip.h> #include <shlobj.h> #include <direct.h> +#include <netioapi.h> #ifdef HAVE_IO_H #include <io.h> @@ -94,8 +96,10 @@ #define ZLIB_STRING "" #endif +#define ARPA_DOMAIN_MAX_LEN 63 + /** - * @defgroup libssh_misc The SSH helper functions. + * @defgroup libssh_misc The SSH helper functions * @ingroup libssh * * Different helper functions used in the SSH Library. @@ -104,8 +108,9 @@ */ #ifdef _WIN32 -char *ssh_get_user_home_dir(void) { - char tmp[MAX_PATH] = {0}; +char *ssh_get_user_home_dir(void) +{ + char tmp[PATH_MAX] = {0}; char *szPath = NULL; if (SHGetSpecialFolderPathA(NULL, tmp, CSIDL_PROFILE, TRUE)) { @@ -122,12 +127,13 @@ char *ssh_get_user_home_dir(void) { } /* we have read access on file */ -int ssh_file_readaccess_ok(const char *file) { - if (_access(file, 4) < 0) { - return 0; - } +int ssh_file_readaccess_ok(const char *file) +{ + if (_access(file, 4) < 0) { + return 0; + } - return 1; + return 1; } /** @@ -158,7 +164,8 @@ int ssh_dir_writeable(const char *path) #define SSH_USEC_IN_SEC 1000000LL #define SSH_SECONDS_SINCE_1601 11644473600LL -int gettimeofday(struct timeval *__p, void *__t) { +int ssh_gettimeofday(struct timeval *__p, void *__t) +{ union { unsigned long long ns100; /* time since 1 Jan 1601 in 100ns units */ FILETIME ft; @@ -171,26 +178,34 @@ int gettimeofday(struct timeval *__p, void *__t) { return (0); } -char *ssh_get_local_username(void) { +char *ssh_get_local_username(void) +{ DWORD size = 0; - char *user; + char *user = NULL; + int rc; /* get the size */ GetUserName(NULL, &size); - user = (char *) malloc(size); + user = (char *)malloc(size); if (user == NULL) { return NULL; } if (GetUserName(user, &size)) { - return user; + rc = ssh_check_username_syntax(user); + if (rc == SSH_OK) { + return user; + } } + free(user); + return NULL; } -int ssh_is_ipaddr_v4(const char *str) { +int ssh_is_ipaddr_v4(const char *str) +{ struct sockaddr_storage ss; int sslen = sizeof(ss); int rc = SOCKET_ERROR; @@ -212,24 +227,40 @@ int ssh_is_ipaddr_v4(const char *str) { return 0; } -int ssh_is_ipaddr(const char *str) { +int ssh_is_ipaddr(const char *str) +{ int rc = SOCKET_ERROR; + char *s = strdup(str); - if (strchr(str, ':')) { + if (s == NULL) { + return -1; + } + if (strchr(s, ':')) { struct sockaddr_storage ss; int sslen = sizeof(ss); - - /* TODO link-local (IP:v6:addr%ifname). */ - rc = WSAStringToAddressA((LPSTR) str, + char *network_interface = strchr(s, '%'); + + /* link-local (IP:v6:addr%ifname). */ + if (network_interface != NULL) { + rc = if_nametoindex(network_interface + 1); + if (rc == 0) { + free(s); + return 0; + } + *network_interface = '\0'; + } + rc = WSAStringToAddressA((LPSTR) s, AF_INET6, NULL, (struct sockaddr*)&ss, &sslen); if (rc == 0) { + free(s); return 1; } } + free(s); return ssh_is_ipaddr_v4(str); } #else /* _WIN32 */ @@ -302,7 +333,7 @@ char *ssh_get_local_username(void) struct passwd pwd; struct passwd *pwdbuf = NULL; char buf[NSS_BUFLEN_PASSWD]; - char *name; + char *name = NULL; int rc; rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf); @@ -311,15 +342,18 @@ char *ssh_get_local_username(void) } name = strdup(pwd.pw_name); + rc = ssh_check_username_syntax(name); - if (name == NULL) { + if (rc != SSH_OK) { + free(name); return NULL; } return name; } -int ssh_is_ipaddr_v4(const char *str) { +int ssh_is_ipaddr_v4(const char *str) +{ int rc = -1; struct in_addr dest; @@ -331,25 +365,42 @@ int ssh_is_ipaddr_v4(const char *str) { return 0; } -int ssh_is_ipaddr(const char *str) { +int ssh_is_ipaddr(const char *str) +{ int rc = -1; + char *s = strdup(str); - if (strchr(str, ':')) { + if (s == NULL) { + return -1; + } + if (strchr(s, ':')) { struct in6_addr dest6; - - /* TODO link-local (IP:v6:addr%ifname). */ - rc = inet_pton(AF_INET6, str, &dest6); + char *network_interface = strchr(s, '%'); + + /* link-local (IP:v6:addr%ifname). */ + if (network_interface != NULL) { + rc = if_nametoindex(network_interface + 1); + if (rc == 0) { + free(s); + return 0; + } + *network_interface = '\0'; + } + rc = inet_pton(AF_INET6, s, &dest6); if (rc > 0) { + free(s); return 1; } } + free(s); return ssh_is_ipaddr_v4(str); } #endif /* _WIN32 */ -char *ssh_lowercase(const char* str) { +char *ssh_lowercase(const char* str) +{ char *new, *p; if (str == NULL) { @@ -392,15 +443,17 @@ char *ssh_hostport(const char *host, int port) * @brief Convert a buffer into a colon separated hex string. * The caller has to free the memory. * - * @param what What should be converted to a hex string. + * @param[in] what What should be converted to a hex string. * - * @param len Length of the buffer to convert. + * @param[in] len Length of the buffer to convert. * - * @return The hex string or NULL on error. + * @return The hex string or NULL on error. The memory needs + * to be freed using ssh_string_free_char(). * * @see ssh_string_free_char() */ -char *ssh_get_hexa(const unsigned char *what, size_t len) { +char *ssh_get_hexa(const unsigned char *what, size_t len) +{ const char h[] = "0123456789abcdef"; char *hexa; size_t i; @@ -428,7 +481,8 @@ char *ssh_get_hexa(const unsigned char *what, size_t len) { /** * @deprecated Please use ssh_print_hash() instead */ -void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len) { +void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len) +{ char *hexa = ssh_get_hexa(what, len); if (hexa == NULL) { @@ -455,7 +509,7 @@ void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len) { * " 00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ................" * * The value for each byte as corresponding ASCII character is printed at the - * end if the value is printable. Otherwise it is replace with '.'. + * end if the value is printable. Otherwise, it is replaced with '.'. * * @param[in] descr A description for the content to be logged * @param[in] what The buffer to be logged @@ -622,7 +676,7 @@ void ssh_log_hexdump(const char *descr, const unsigned char *what, size_t len) return; error: - SSH_LOG(SSH_LOG_WARN, "Could not print to buffer"); + SSH_LOG(SSH_LOG_DEBUG, "Could not print to buffer"); return; } @@ -649,48 +703,55 @@ error: * } * @endcode */ -const char *ssh_version(int req_version) { - if (req_version <= LIBSSH_VERSION_INT) { - return SSH_STRINGIFY(LIBSSH_VERSION) GCRYPT_STRING CRYPTO_STRING MBED_STRING - ZLIB_STRING; - } +const char *ssh_version(int req_version) +{ + if (req_version <= LIBSSH_VERSION_INT) { + return SSH_STRINGIFY(LIBSSH_VERSION) GCRYPT_STRING CRYPTO_STRING + MBED_STRING ZLIB_STRING; + } - return NULL; + return NULL; } -struct ssh_list *ssh_list_new(void) { - struct ssh_list *ret=malloc(sizeof(struct ssh_list)); - if(!ret) - return NULL; - ret->root=ret->end=NULL; - return ret; +struct ssh_list *ssh_list_new(void) +{ + struct ssh_list *ret = malloc(sizeof(struct ssh_list)); + if (ret == NULL) { + return NULL; + } + ret->root = ret->end = NULL; + return ret; } -void ssh_list_free(struct ssh_list *list){ - struct ssh_iterator *ptr,*next; - if(!list) - return; - ptr=list->root; - while(ptr){ - next=ptr->next; - SAFE_FREE(ptr); - ptr=next; - } - SAFE_FREE(list); +void ssh_list_free(struct ssh_list *list) +{ + struct ssh_iterator *ptr, *next; + if (!list) + return; + ptr = list->root; + while (ptr) { + next = ptr->next; + SAFE_FREE(ptr); + ptr = next; + } + SAFE_FREE(list); } -struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list){ - if(!list) - return NULL; - return list->root; +struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list) +{ + if (!list) + return NULL; + return list->root; } -struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value){ - struct ssh_iterator *it; - for(it = ssh_list_get_iterator(list); it != NULL ;it=it->next) - if(it->data==value) - return it; - return NULL; +struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value) +{ + struct ssh_iterator *it; + + for (it = ssh_list_get_iterator(list); it != NULL ; it = it->next) + if (it->data == value) + return it; + return NULL; } /** @@ -703,7 +764,7 @@ struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value){ size_t ssh_list_count(const struct ssh_list *list) { struct ssh_iterator *it = NULL; - int count = 0; + size_t count = 0; for (it = ssh_list_get_iterator(list); it != NULL ; it = it->next) { count++; @@ -712,16 +773,20 @@ size_t ssh_list_count(const struct ssh_list *list) return count; } -static struct ssh_iterator *ssh_iterator_new(const void *data){ - struct ssh_iterator *iterator=malloc(sizeof(struct ssh_iterator)); - if(!iterator) - return NULL; - iterator->next=NULL; - iterator->data=data; - return iterator; +static struct ssh_iterator *ssh_iterator_new(const void *data) +{ + struct ssh_iterator *iterator = malloc(sizeof(struct ssh_iterator)); + + if (iterator == NULL) { + return NULL; + } + iterator->next = NULL; + iterator->data = data; + return iterator; } -int ssh_list_append(struct ssh_list *list,const void *data){ +int ssh_list_append(struct ssh_list *list,const void *data) +{ struct ssh_iterator *iterator = NULL; if (list == NULL) { @@ -744,7 +809,8 @@ int ssh_list_append(struct ssh_list *list,const void *data){ return SSH_OK; } -int ssh_list_prepend(struct ssh_list *list, const void *data){ +int ssh_list_prepend(struct ssh_list *list, const void *data) +{ struct ssh_iterator *it = NULL; if (list == NULL) { @@ -768,8 +834,9 @@ int ssh_list_prepend(struct ssh_list *list, const void *data){ return SSH_OK; } -void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator){ - struct ssh_iterator *ptr,*prev; +void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator) +{ + struct ssh_iterator *ptr, *prev; if (list == NULL) { return; @@ -803,12 +870,13 @@ void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator){ * @brief Removes the top element of the list and returns the data value * attached to it. * - * @param[in[ list The ssh_list to remove the element. + * @param[in] list The ssh_list to remove the element. * * @returns A pointer to the element being stored in head, or NULL * if the list is empty. */ -const void *_ssh_list_pop_head(struct ssh_list *list){ +const void *_ssh_list_pop_head(struct ssh_list *list) +{ struct ssh_iterator *iterator = NULL; const void *data = NULL; @@ -834,17 +902,21 @@ const void *_ssh_list_pop_head(struct ssh_list *list){ * dirname breaks a null-terminated pathname string into a directory component. * In the usual case, ssh_dirname() returns the string up to, but not including, * the final '/'. Trailing '/' characters are not counted as part of the - * pathname. The caller must free the memory. + * pathname. The caller must free the memory using ssh_string_free_char(). * * @param[in] path The path to parse. * * @return The dirname of path or NULL if we can't allocate memory. * If path does not contain a slash, c_dirname() returns - * the string ".". If path is the string "/", it returns + * the string ".". If path is a string "/", it returns * the string "/". If path is NULL or an empty string, - * "." is returned. + * "." is returned. The memory needs to be freed using + * ssh_string_free_char(). + * + * @see ssh_string_free_char() */ -char *ssh_dirname (const char *path) { +char *ssh_dirname (const char *path) +{ char *new = NULL; size_t len; @@ -895,11 +967,15 @@ char *ssh_dirname (const char *path) { * @param[in] path The path to parse. * * @return The filename of path or NULL if we can't allocate - * memory. If path is a the string "/", basename returns + * memory. If path is the string "/", basename returns * the string "/". If path is NULL or an empty string, - * "." is returned. + * "." is returned. The caller needs to free this memory + * ssh_string_free_char(). + * + * @see ssh_string_free_char() */ -char *ssh_basename (const char *path) { +char *ssh_basename (const char *path) +{ char *new = NULL; const char *s; size_t len; @@ -1032,9 +1108,13 @@ int ssh_mkdirs(const char *pathname, mode_t mode) * * @param[in] d The directory to expand. * - * @return The expanded directory, NULL on error. + * @return The expanded directory, NULL on error. The caller + * needs to free the memory using ssh_string_free_char(). + * + * @see ssh_string_free_char() */ -char *ssh_path_expand_tilde(const char *d) { +char *ssh_path_expand_tilde(const char *d) +{ char *h = NULL, *r; const char *p; size_t ld; @@ -1101,12 +1181,17 @@ char *ssh_path_expand_tilde(const char *d) { * %l local hostname * %r remote username * %p remote port - * @returns Expanded string. + * @returns Expanded string. The caller needs to free the memory using + * ssh_string_free_char(). + * + * @see ssh_string_free_char() */ -char *ssh_path_expand_escape(ssh_session session, const char *s) { - char host[NI_MAXHOST]; - char buf[MAX_BUF_SIZE]; - char *r, *x = NULL; +char *ssh_path_expand_escape(ssh_session session, const char *s) +{ + char host[NI_MAXHOST] = {0}; + char *buf = NULL; + char *r = NULL; + char *x = NULL; const char *p; size_t i, l; @@ -1122,6 +1207,13 @@ char *ssh_path_expand_escape(ssh_session session, const char *s) { return NULL; } + buf = malloc(MAX_BUF_SIZE); + if (buf == NULL) { + ssh_set_error_oom(session); + free(r); + return NULL; + } + p = r; buf[0] = '\0'; @@ -1131,6 +1223,7 @@ char *ssh_path_expand_escape(ssh_session session, const char *s) { buf[i] = *p; i++; if (i >= MAX_BUF_SIZE) { + free(buf); free(r); return NULL; } @@ -1147,7 +1240,15 @@ char *ssh_path_expand_escape(ssh_session session, const char *s) { case '%': goto escape; case 'd': - x = strdup(session->opts.sshdir); + if (session->opts.sshdir) { + x = strdup(session->opts.sshdir); + } else { + ssh_set_error(session, SSH_FATAL, + "Cannot expand sshdir"); + free(buf); + free(r); + return NULL; + } break; case 'u': x = ssh_get_local_username(); @@ -1158,31 +1259,48 @@ char *ssh_path_expand_escape(ssh_session session, const char *s) { } break; case 'h': - x = strdup(session->opts.host); + if (session->opts.host) { + x = strdup(session->opts.host); + } else { + ssh_set_error(session, SSH_FATAL, + "Cannot expand host"); + free(buf); + free(r); + return NULL; + } break; case 'r': - x = strdup(session->opts.username); + if (session->opts.username) { + x = strdup(session->opts.username); + } else { + ssh_set_error(session, SSH_FATAL, + "Cannot expand username"); + free(buf); + free(r); + return NULL; + } break; case 'p': - if (session->opts.port < 65536) { - char tmp[6]; - - snprintf(tmp, - sizeof(tmp), - "%u", - session->opts.port > 0 ? session->opts.port : 22); - x = strdup(tmp); + { + char tmp[6]; + + snprintf(tmp, sizeof(tmp), "%hu", + (uint16_t)(session->opts.port > 0 ? session->opts.port + : 22)); + x = strdup(tmp); } break; default: ssh_set_error(session, SSH_FATAL, "Wrong escape sequence detected"); + free(buf); free(r); return NULL; } if (x == NULL) { ssh_set_error_oom(session); + free(buf); free(r); return NULL; } @@ -1191,19 +1309,26 @@ char *ssh_path_expand_escape(ssh_session session, const char *s) { if (i >= MAX_BUF_SIZE) { ssh_set_error(session, SSH_FATAL, "String too long"); + free(buf); free(x); free(r); return NULL; } l = strlen(buf); - strncpy(buf + l, x, sizeof(buf) - l - 1); + strncpy(buf + l, x, MAX_BUF_SIZE - l - 1); buf[i] = '\0'; SAFE_FREE(x); } free(r); - return strdup(buf); -#undef MAX_BUF_SIZE + + /* strip the unused space by realloc */ + x = realloc(buf, strlen(buf) + 1); + if (x == NULL) { + ssh_set_error_oom(session); + free(buf); + } + return x; } /** @@ -1249,7 +1374,7 @@ int ssh_analyze_banner(ssh_session session, int server) return -1; } - SSH_LOG(SSH_LOG_PROTOCOL, "Analyzing banner: %s", banner); + SSH_LOG(SSH_LOG_DEBUG, "Analyzing banner: %s", banner); switch (banner[4]) { case '2': @@ -1279,27 +1404,31 @@ int ssh_analyze_banner(ssh_session session, int server) * 012345678901234567890 */ if (strlen(openssh) > 9) { + errno = 0; major = strtoul(openssh + 8, &tmp, 10); if ((tmp == (openssh + 8)) || ((errno == ERANGE) && (major == ULONG_MAX)) || ((errno != 0) && (major == 0)) || ((major < 1) || (major > 100))) { /* invalid major */ + errno = 0; goto done; } + errno = 0; minor = strtoul(openssh + 10, &tmp, 10); if ((tmp == (openssh + 10)) || ((errno == ERANGE) && (major == ULONG_MAX)) || ((errno != 0) && (major == 0)) || (minor > 100)) { /* invalid minor */ + errno = 0; goto done; } session->openssh = SSH_VERSION_INT(((int) major), ((int) minor), 0); - SSH_LOG(SSH_LOG_PROTOCOL, + SSH_LOG(SSH_LOG_DEBUG, "We are talking to an OpenSSH %s version: %lu.%lu (%x)", server ? "client" : "server", major, minor, session->openssh); @@ -1322,7 +1451,8 @@ done: * @brief initializes a timestamp to the current time * @param[out] ts pointer to an allocated ssh_timestamp structure */ -void ssh_timestamp_init(struct ssh_timestamp *ts){ +void ssh_timestamp_init(struct ssh_timestamp *ts) +{ #ifdef HAVE_CLOCK_GETTIME struct timespec tp; clock_gettime(CLOCK, &tp); @@ -1345,17 +1475,18 @@ void ssh_timestamp_init(struct ssh_timestamp *ts){ * @returns difference in milliseconds */ -static int ssh_timestamp_difference(struct ssh_timestamp *old, - struct ssh_timestamp *new){ - long seconds, usecs, msecs; - seconds = new->seconds - old->seconds; - usecs = new->useconds - old->useconds; - if (usecs < 0){ - seconds--; - usecs += 1000000; - } - msecs = seconds * 1000 + usecs/1000; - return msecs; +static int +ssh_timestamp_difference(struct ssh_timestamp *old, struct ssh_timestamp *new) +{ + long seconds, usecs, msecs; + seconds = new->seconds - old->seconds; + usecs = new->useconds - old->useconds; + if (usecs < 0){ + seconds--; + usecs += 1000000; + } + msecs = seconds * 1000 + usecs/1000; + return msecs; } /** @@ -1366,14 +1497,20 @@ static int ssh_timestamp_difference(struct ssh_timestamp *old, * @param[in] usec number of microseconds * @returns milliseconds, or 10000 if user supplied values are equal to zero */ -int ssh_make_milliseconds(long sec, long usec) { - int res = usec ? (usec / 1000) : 0; +int ssh_make_milliseconds(unsigned long sec, unsigned long usec) +{ + unsigned long res = usec ? (usec / 1000) : 0; res += (sec * 1000); if (res == 0) { res = 10 * 1000; /* use a reasonable default value in case * SSH_OPTIONS_TIMEOUT is not set in options. */ } - return res; + + if (res > INT_MAX) { + return SSH_TIMEOUT_INFINITE; + } else { + return (int)res; + } } /** @@ -1386,7 +1523,8 @@ int ssh_make_milliseconds(long sec, long usec) { * @returns 1 if timeout is elapsed * 0 otherwise */ -int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout) { +int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout) +{ struct ssh_timestamp now; switch(timeout) { @@ -1394,7 +1532,7 @@ int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout) { * -2 means user-defined timeout as available in * session->timeout, session->timeout_usec. */ - SSH_LOG(SSH_LOG_WARN, "ssh_timeout_elapsed called with -2. this needs to " + SSH_LOG(SSH_LOG_DEBUG, "ssh_timeout_elapsed called with -2. this needs to " "be fixed. please set a breakpoint on misc.c:%d and " "fix the caller\n", __LINE__); return 0; @@ -1418,7 +1556,8 @@ int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout) { * timeout * @returns remaining time in milliseconds, 0 if elapsed, -1 if never. */ -int ssh_timeout_update(struct ssh_timestamp *ts, int timeout){ +int ssh_timeout_update(struct ssh_timestamp *ts, int timeout) +{ struct ssh_timestamp now; int ms, ret; if (timeout <= 0) { @@ -1547,20 +1686,20 @@ int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len) enum ssh_quote_state_e state = NO_QUOTE; if (file_name == NULL || buf == NULL || buf_len == 0) { - SSH_LOG(SSH_LOG_WARNING, "Invalid parameter"); + SSH_LOG(SSH_LOG_TRACE, "Invalid parameter"); return SSH_ERROR; } /* Only allow file names smaller than 32kb. */ if (strlen(file_name) > 32 * 1024) { - SSH_LOG(SSH_LOG_WARNING, "File name too long"); + SSH_LOG(SSH_LOG_TRACE, "File name too long"); return SSH_ERROR; } /* Paranoia check */ required_buf_len = (size_t)3 * strlen(file_name) + 1; if (required_buf_len > buf_len) { - SSH_LOG(SSH_LOG_WARNING, "Buffer too small"); + SSH_LOG(SSH_LOG_TRACE, "Buffer too small"); return SSH_ERROR; } @@ -1611,13 +1750,13 @@ int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len) *dst++ = '\\'; break; case SINGLE_QUOTE: - /* Close the current quoted string and replace '!' for unquoted + /* Close the currently quoted string and replace '!' for unquoted * "\!" */ *dst++ = '\''; *dst++ = '\\'; break; case DOUBLE_QUOTE: - /* Close current quoted string and replace "!" for unquoted + /* Close currently quoted string and replace "!" for unquoted * "\!" */ *dst++ = '"'; *dst++ = '\\'; @@ -1718,7 +1857,7 @@ int ssh_newline_vis(const char *string, char *buf, size_t buf_len) } if ((2 * strlen(string) + 1) > buf_len) { - SSH_LOG(SSH_LOG_WARNING, "Buffer too small"); + SSH_LOG(SSH_LOG_TRACE, "Buffer too small"); return SSH_ERROR; } @@ -1741,21 +1880,23 @@ int ssh_newline_vis(const char *string, char *buf, size_t buf_len) * * @brief Replaces the last 6 characters of a string from 'X' to 6 random hexdigits. * - * @param[in] template Any input string with last 6 characters as 'X'. + * @param[in,out] name Any input string with last 6 characters as 'X'. * @returns -1 as error when the last 6 characters of the input to be replaced are not 'X' * 0 otherwise. */ -int ssh_tmpname(char *template) +int ssh_tmpname(char *name) { char *tmp = NULL; size_t i = 0; + int rc = 0; + uint8_t random[6]; - if (template == NULL) { + if (name == NULL) { goto err; } - tmp = template + strlen(template) - 6; - if (tmp < template) { + tmp = name + strlen(name) - 6; + if (tmp < name) { goto err; } @@ -1767,17 +1908,18 @@ int ssh_tmpname(char *template) } } - srand(time(NULL)); + rc = ssh_get_random(random, 6, 0); + if (!rc) { + SSH_LOG(SSH_LOG_WARNING, + "Could not generate random data\n"); + goto err; + } - for (i = 0; i < 6; ++i) { -#ifdef _WIN32 - /* in win32 MAX_RAND is 32767, thus we can not shift that far, - * otherwise the last three chars are 0 */ - int hexdigit = (rand() >> (i * 2)) & 0x1f; -#else - int hexdigit = (rand() >> (i * 5)) & 0x1f; -#endif - tmp[i] = hexdigit > 9 ? hexdigit + 'a' - 10 : hexdigit + '0'; + for (i = 0; i < 6; i++) { + /* Limit the random[i] < 32 */ + random[i] &= 0x1f; + /* For values from 0 to 9 use numbers, otherwise use letters */ + tmp[i] = random[i] > 9 ? random[i] + 'a' - 10 : random[i] + '0'; } return 0; @@ -1790,15 +1932,19 @@ err: /** * @internal * - * @brief Finds the first occurence of a patterm in a string and replaces it. + * @brief Finds the first occurrence of a pattern in a string and replaces it. * - * @param[in] src Source string containing the patern to be replaced. + * @param[in] src Source string containing the pattern to be replaced. * @param[in] pattern Pattern to be replaced in the source string. - * Note: this function replaces the first occurence of pattern only. + * Note: this function replaces the first occurrence of + * pattern only. * @param[in] replace String to be replaced is stored in replace. * * @returns src_replaced a pointer that points to the replaced string. - * NULL if allocation fails or if src is NULL. + * NULL if allocation fails or if src is NULL. The returned memory needs to be + * freed using ssh_string_free_char(). + * + * @see ssh_string_free_char() */ char *ssh_strreplace(const char *src, const char *pattern, const char *replace) { @@ -1838,4 +1984,247 @@ char *ssh_strreplace(const char *src, const char *pattern, const char *replace) } } +/** + * @internal + * + * @brief Processes errno into error string + * + * @param[in] err_num The errno value + * @param[out] buf Pointer to a place where the string could be saved + * @param[in] buflen The allocated size of buf + * + * @return error string + */ +char *ssh_strerror(int err_num, char *buf, size_t buflen) +{ +#if defined(__linux__) && defined(__GLIBC__) && defined(_GNU_SOURCE) + /* GNU extension on Linux */ + return strerror_r(err_num, buf, buflen); +#else + int rv; + +#if defined(_WIN32) + rv = strerror_s(buf, buflen, err_num); +#else + /* POSIX version available for example on FreeBSD or in musl libc */ + rv = strerror_r(err_num, buf, buflen); +#endif /* _WIN32 */ + + /* make sure the buffer is initialized and terminated with NULL */ + if (-rv == ERANGE) { + buf[0] = '\0'; + } + return buf; +#endif /* defined(__linux__) && defined(__GLIBC__) && defined(_GNU_SOURCE) */ +} + +/** + * @brief Read the requested number of bytes from a local file. + * + * A call to read() may perform a short read even when sufficient data is + * present in the file. This function can be used to avoid such short reads. + * + * This function tries to read the requested number of bytes from the file + * until one of the following occurs : + * - Requested number of bytes are read. + * - EOF is encountered before reading the requested number of bytes. + * - An error occurs. + * + * On encountering an error due to an interrupt, this function ignores that + * error and continues trying to read the data. + * + * @param[in] fd The file descriptor of the local file to read from. + * + * @param[out] buf Pointer to a buffer in which read data will be + * stored. + * + * @param[in] nbytes Number of bytes to read. + * + * @returns Number of bytes read on success, + * SSH_ERROR on error with errno set to indicate the + * error. + */ +ssize_t ssh_readn(int fd, void *buf, size_t nbytes) +{ + size_t total_bytes_read = 0; + ssize_t bytes_read; + + if (fd < 0 || buf == NULL || nbytes == 0) { + errno = EINVAL; + return SSH_ERROR; + } + + do { + bytes_read = read(fd, + ((char *)buf) + total_bytes_read, + nbytes - total_bytes_read); + if (bytes_read == -1) { + if (errno == EINTR) { + /* Ignoring errors due to signal interrupts */ + continue; + } + + return SSH_ERROR; + } + + if (bytes_read == 0) { + /* EOF encountered on the local file before reading nbytes */ + break; + } + + total_bytes_read += (size_t)bytes_read; + } while (total_bytes_read < nbytes); + + return total_bytes_read; +} + +/** + * @brief Write the requested number of bytes to a local file. + * + * A call to write() may perform a short write on a local file. This function + * can be used to avoid short writes. + * + * This function tries to write the requested number of bytes until those many + * bytes are written or some error occurs. + * + * On encountering an error due to an interrupt, this function ignores that + * error and continues trying to write the data. + * + * @param[in] fd The file descriptor of the local file to write to. + * + * @param[in] buf Pointer to a buffer in which data to write is stored. + * + * @param[in] nbytes Number of bytes to write. + * + * @returns Number of bytes written on success, + * SSH_ERROR on error with errno set to indicate the + * error. + */ +ssize_t ssh_writen(int fd, const void *buf, size_t nbytes) +{ + size_t total_bytes_written = 0; + ssize_t bytes_written; + + if (fd < 0 || buf == NULL || nbytes == 0) { + errno = EINVAL; + return SSH_ERROR; + } + + do { + bytes_written = write(fd, + ((const char *)buf) + total_bytes_written, + nbytes - total_bytes_written); + if (bytes_written == -1) { + if(errno == EINTR) { + /* Ignoring errors due to signal interrupts */ + continue; + } + + return SSH_ERROR; + } + + total_bytes_written += (size_t)bytes_written; + } while (total_bytes_written < nbytes); + + return total_bytes_written; +} + +/** + * @brief Checks syntax of a domain name + * + * The check is made based on the RFC1035 section 2.3.1 + * Allowed characters are: hyphen, period, digits (0-9) and letters (a-zA-Z) + * + * The label should be no longer than 63 characters + * The label should start with a letter and end with a letter or number + * The label in this implementation can start with a number to allow virtual + * URLs to pass. Note that this will make IPv4 addresses to pass + * this check too. + * + * @param hostname The domain name to be checked, has to be null terminated + * + * @return SSH_OK if the hostname passes syntax check + * SSH_ERROR otherwise or if hostname is NULL or empty string + */ +int ssh_check_hostname_syntax(const char *hostname) +{ + char *it = NULL, *s = NULL, *buf = NULL; + size_t it_len; + char c; + + if (hostname == NULL || strlen(hostname) == 0) { + return SSH_ERROR; + } + + /* strtok_r writes into the string, keep the input clean */ + s = strdup(hostname); + if (s == NULL) { + return SSH_ERROR; + } + + it = strtok_r(s, ".", &buf); + /* if the token has 0 length */ + if (it == NULL) { + free(s); + return SSH_ERROR; + } + do { + it_len = strlen(it); + if (it_len > ARPA_DOMAIN_MAX_LEN || + /* the first char must be a letter, but some virtual urls start + * with a number */ + isalnum(it[0]) == 0 || + isalnum(it[it_len - 1]) == 0) { + free(s); + return SSH_ERROR; + } + while (*it != '\0') { + c = *it; + /* the "." is allowed too, but tokenization removes it from the + * string */ + if (isalnum(c) == 0 && c != '-') { + free(s); + return SSH_ERROR; + } + it++; + } + } while ((it = strtok_r(NULL, ".", &buf)) != NULL); + + free(s); + + return SSH_OK; +} + +/** + * @brief Checks syntax of a username + * + * This check disallows metacharacters in the username + * + * @param username The username to be checked, has to be null terminated + * + * @return SSH_OK if the username passes syntax check + * SSH_ERROR otherwise or if username is NULL or empty string + */ +int ssh_check_username_syntax(const char *username) +{ + size_t username_len; + + if (username == NULL || *username == '-') { + return SSH_ERROR; + } + + username_len = strlen(username); + if (username_len == 0 || username[username_len - 1] == '\\' || + strpbrk(username, "'`\";&<>|(){}") != NULL) { + return SSH_ERROR; + } + for (size_t i = 0; i < username_len; i++) { + if (isspace(username[i]) != 0 && username[i + 1] == '-') { + return SSH_ERROR; + } + } + + return SSH_OK; +} + /** @} */ diff --git a/src/options.c b/src/options.c index 692fc837..61a7e4a7 100644 --- a/src/options.c +++ b/src/options.c @@ -32,10 +32,12 @@ #include <winsock2.h> #endif #include <sys/types.h> +#include "libssh/pki_priv.h" #include "libssh/priv.h" #include "libssh/session.h" #include "libssh/misc.h" #include "libssh/options.h" +#include "libssh/config_parser.h" #ifdef WITH_SERVER #include "libssh/server.h" #include "libssh/bind.h" @@ -51,21 +53,23 @@ * @brief Duplicate the options of a session structure. * * If you make several sessions with the same options this is useful. You - * cannot use twice the same option structure in ssh_session_connect. + * cannot use twice the same option structure in ssh_connect. * * @param src The session to use to copy the options. * * @param dest A pointer to store the allocated session with duplicated - * options. You have to free the memory. + * options. You have to free the memory using ssh_free() * - * @returns 0 on sucess, -1 on error with errno set. + * @returns 0 on success, -1 on error with errno set. * - * @see ssh_session_connect() + * @see ssh_connect() + * @see ssh_free() */ int ssh_options_copy(ssh_session src, ssh_session *dest) { ssh_session new; struct ssh_iterator *it = NULL; + struct ssh_list *list = NULL; char *id = NULL; int i; @@ -103,24 +107,51 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) } /* Remove the default identities */ - for (id = ssh_list_pop_head(char *, new->opts.identity); + for (id = ssh_list_pop_head(char *, new->opts.identity_non_exp); id != NULL; - id = ssh_list_pop_head(char *, new->opts.identity)) { + id = ssh_list_pop_head(char *, new->opts.identity_non_exp)) { SAFE_FREE(id); } /* Copy the new identities from the source list */ - if (src->opts.identity != NULL) { + list = new->opts.identity_non_exp; + it = ssh_list_get_iterator(src->opts.identity_non_exp); + for (i = 0; i < 2; i++) { + while (it) { + int rc; + + id = strdup((char *)it->data); + if (id == NULL) { + ssh_free(new); + return -1; + } + + rc = ssh_list_append(list, id); + if (rc < 0) { + free(id); + ssh_free(new); + return -1; + } + it = it->next; + } + + /* copy the identity list if there is any already */ + list = new->opts.identity; it = ssh_list_get_iterator(src->opts.identity); + } + + list = new->opts.certificate_non_exp; + it = ssh_list_get_iterator(src->opts.certificate_non_exp); + for (i = 0; i < 2; i++) { while (it) { int rc; - id = strdup((char *) it->data); + id = strdup((char *)it->data); if (id == NULL) { ssh_free(new); return -1; } - rc = ssh_list_append(new->opts.identity, id); + rc = ssh_list_append(list, id); if (rc < 0) { free(id); ssh_free(new); @@ -128,6 +159,10 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) } it = it->next; } + + /* copy the certificate list if there is any already */ + list = new->opts.certificate; + it = ssh_list_get_iterator(src->opts.certificate); } if (src->opts.sshdir != NULL) { @@ -196,6 +231,14 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) } } + if (src->opts.control_path != NULL) { + new->opts.control_path = strdup(src->opts.control_path); + if (new->opts.control_path == NULL) { + ssh_free(new); + return -1; + } + } + memcpy(new->opts.options_seen, src->opts.options_seen, sizeof(new->opts.options_seen)); @@ -209,6 +252,7 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) new->opts.flags = src->opts.flags; new->opts.nodelay = src->opts.nodelay; new->opts.config_processed = src->opts.config_processed; + new->opts.control_master = src->opts.control_master; new->common.log_verbosity = src->common.log_verbosity; new->common.callbacks = src->common.callbacks; @@ -219,14 +263,30 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) int ssh_options_set_algo(ssh_session session, enum ssh_kex_types_e algo, - const char *list) + const char *list, + char **place) { - char *p = NULL; + /* When the list start with +,-,^ the filtration of unknown algorithms + * gets handled inside the helper functions, otherwise the list is taken + * as it is. */ + char *p = (char *)list; + + if (algo < SSH_COMP_C_S) { + if (list[0] == '+') { + p = ssh_add_to_default_algos(algo, list+1); + } else if (list[0] == '-') { + p = ssh_remove_from_default_algos(algo, list+1); + } else if (list[0] == '^') { + p = ssh_prefix_default_algos(algo, list+1); + } + } - if (ssh_fips_mode()) { - p = ssh_keep_fips_algos(algo, list); - } else { - p = ssh_keep_known_algos(algo, list); + if (p == list) { + if (ssh_fips_mode()) { + p = ssh_keep_fips_algos(algo, list); + } else { + p = ssh_keep_known_algos(algo, list); + } } if (p == NULL) { @@ -236,8 +296,8 @@ int ssh_options_set_algo(ssh_session session, return -1; } - SAFE_FREE(session->opts.wanted_methods[algo]); - session->opts.wanted_methods[algo] = p; + SAFE_FREE(*place); + *place = p; return 0; } @@ -263,9 +323,10 @@ int ssh_options_set_algo(ssh_session session, * The file descriptor to use (socket_t).\n * \n * If you wish to open the socket yourself for a reason - * or another, set the file descriptor. Don't forget to - * set the hostname as the hostname is used as a key in - * the known_host mechanism. + * or another, set the file descriptor and take care of closing + * it (this is new behavior in libssh 0.10). + * Don't forget to set the hostname as the hostname is used + * as a key in the known_host mechanism. * * - SSH_OPTIONS_BINDADDR: * The address to bind the client to (const char *). @@ -312,13 +373,28 @@ int ssh_options_set_algo(ssh_session session, * Add a new identity file (const char *, format string) to * the identity list.\n * \n - * By default identity, id_dsa and id_rsa are checked.\n + * By default id_rsa, id_ecdsa and id_ed25519 files are used.\n * \n * The identity used to authenticate with public key will be * prepended to the list. * It may include "%s" which will be replaced by the * user home directory. * + * - SSH_OPTIONS_CERTIFICATE: + * Add a new certificate file (const char *, format string) to + * the certificate list.\n + * \n + * By default id_rsa-cert.pub, id_ecdsa-cert.pub and + * id_ed25519-cert.pub files are used, when the underlying + * private key is present.\n + * \n + * The certificate itself can not be used to authenticate to + * remote server so it needs to be paired with private key + * (aka identity file) provided with separate option, from agent + * or from PKCS#11 token. + * It may include "%s" which will be replaced by the + * user home directory. + * * - SSH_OPTIONS_TIMEOUT: * Set a timeout for the connection in seconds (long). * @@ -340,8 +416,9 @@ int ssh_options_set_algo(ssh_session session, * - SSH_LOG_NOLOG: No logging * - SSH_LOG_WARNING: Only warnings * - SSH_LOG_PROTOCOL: High level protocol information - * - SSH_LOG_PACKET: Lower level protocol infomations, packet level + * - SSH_LOG_PACKET: Lower level protocol information, packet level * - SSH_LOG_FUNCTIONS: Every function path + * The default is SSH_LOG_NOLOG. * * - SSH_OPTIONS_LOG_VERBOSITY_STR: * Set the session logging verbosity via a @@ -353,34 +430,60 @@ int ssh_options_set_algo(ssh_session session, * * - SSH_OPTIONS_CIPHERS_C_S: * Set the symmetric cipher client to server (const char *, - * comma-separated list). + * comma-separated list). The list can be prepended by +,-,^ + * which can append, remove or move to the beginning + * (prioritizing) of the default list respectively. Giving an + * empty list after + and ^ will cause error. * * - SSH_OPTIONS_CIPHERS_S_C: * Set the symmetric cipher server to client (const char *, - * comma-separated list). + * comma-separated list). The list can be prepended by +,-,^ + * which can append, remove or move to the beginning + * (prioritizing) of the default list respectively. Giving an + * empty list after + and ^ will cause error. * * - SSH_OPTIONS_KEY_EXCHANGE: * Set the key exchange method to be used (const char *, * comma-separated list). ex: * "ecdh-sha2-nistp256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" + * The list can be prepended by +,-,^ which will append, + * remove or move to the beginning (prioritizing) of the + * default list respectively. Giving an empty list + * after + and ^ will cause error. * * - SSH_OPTIONS_HMAC_C_S: * Set the Message Authentication Code algorithm client to server - * (const char *, comma-separated list). + * (const char *, comma-separated list). The list can be + * prepended by +,-,^ which will append, remove or move to + * the beginning (prioritizing) of the default list + * respectively. Giving an empty list after + and ^ will + * cause error. * * - SSH_OPTIONS_HMAC_S_C: * Set the Message Authentication Code algorithm server to client - * (const char *, comma-separated list). + * (const char *, comma-separated list). The list can be + * prepended by +,-,^ which will append, remove or move to + * the beginning (prioritizing) of the default list + * respectively. Giving an empty list after + and ^ will + * cause error. * * - SSH_OPTIONS_HOSTKEYS: * Set the preferred server host key types (const char *, * comma-separated list). ex: - * "ssh-rsa,ssh-dss,ecdh-sha2-nistp256" + * "ssh-rsa,ecdh-sha2-nistp256". The list can be + * prepended by +,-,^ which will append, remove or move to + * the beginning (prioritizing) of the default list + * respectively. Giving an empty list after + and ^ will + * cause error. * * - SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: * Set the preferred public key algorithms to be used for * authentication (const char *, comma-separated list). ex: - * "ssh-rsa,rsa-sha2-256,ssh-dss,ecdh-sha2-nistp256" + * "ssh-rsa,rsa-sha2-256,ecdh-sha2-nistp256" + * The list can be prepended by +,-,^ which will append, + * remove or move to the beginning (prioritizing) of the + * default list respectively. Giving an empty list + * after + and ^ will cause error. * * - SSH_OPTIONS_COMPRESSION_C_S: * Set the compression to use for client to server @@ -461,10 +564,49 @@ int ssh_options_set_algo(ssh_session session, * (uint64_t, 0=default) * * - SSH_OPTIONS_REKEY_TIME - * Set the time limit for a session before intializing a rekey + * Set the time limit for a session before initializing a rekey * in seconds. RFC 4253 Section 9 recommends one hour. * (uint32_t, 0=off) * + * - SSH_OPTIONS_RSA_MIN_SIZE + * Set the minimum RSA key size in bits to be accepted by the + * client for both authentication and hostkey verification. + * The values under 768 bits are not accepted even with this + * configuration option as they are considered completely broken. + * Setting 0 will revert the value to defaults. + * Default is 1024 bits or 2048 bits in FIPS mode. + * (int *) + + * - SSH_OPTIONS_IDENTITY_AGENT + * Set the path to the SSH agent socket. If unset, the + * SSH_AUTH_SOCK environment is consulted. + * (const char *) + + * - SSH_OPTIONS_IDENTITIES_ONLY + * Use only keys specified in the SSH config, even if agent + * offers more. + * (bool) + * + * - SSH_OPTIONS_CONTROL_MASTER + * Set the option to enable the sharing of multiple sessions over a + * single network connection using connection multiplexing. + * + * The possible options are among the following: + * - SSH_CONTROL_MASTER_AUTO: enable connection sharing if possible + * - SSH_CONTROL_MASTER_YES: enable connection sharing unconditionally + * - SSH_CONTROL_MASTER_ASK: ask for confirmation if connection sharing is to be enabled + * - SSH_CONTROL_MASTER_AUTOASK: enable connection sharing if possible, + * but ask for confirmation + * - SSH_CONTROL_MASTER_NO: disable connection sharing unconditionally + * + * The default is SSH_CONTROL_MASTER_NO. + * + * - SSH_OPTIONS_CONTROL_PATH + * Set the path to the control socket used for connection sharing. + * Set to "none" to disable connection sharing. + * (const char *) + * + * * @param value The value to set. This is a generic pointer and the * datatype which is used should be set according to the * type set. @@ -472,12 +614,14 @@ int ssh_options_set_algo(ssh_session session, * @return 0 on success, < 0 on error. */ int ssh_options_set(ssh_session session, enum ssh_options_e type, - const void *value) { + const void *value) +{ const char *v; char *p, *q; long int i; unsigned int u; int rc; + char **wanted_methods = session->opts.wanted_methods; if (session == NULL) { return -1; @@ -490,33 +634,18 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - q = strdup(value); - if (q == NULL) { - ssh_set_error_oom(session); + char *username = NULL, *hostname = NULL; + rc = ssh_config_parse_uri(value, &username, &hostname, NULL, true); + if (rc != SSH_OK) { return -1; } - p = strchr(q, '@'); - - SAFE_FREE(session->opts.host); - - if (p) { - *p = '\0'; - session->opts.host = strdup(p + 1); - if (session->opts.host == NULL) { - SAFE_FREE(q); - ssh_set_error_oom(session); - return -1; - } - + if (username != NULL) { SAFE_FREE(session->opts.username); - session->opts.username = strdup(q); - SAFE_FREE(q); - if (session->opts.username == NULL) { - ssh_set_error_oom(session); - return -1; - } - } else { - session->opts.host = q; + session->opts.username = username; + } + if (hostname != NULL) { + SAFE_FREE(session->opts.host); + session->opts.host = hostname; } } break; @@ -547,7 +676,9 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, } i = strtol(q, &p, 10); if (q == p) { + SSH_LOG(SSH_LOG_DEBUG, "No port number was parsed"); SAFE_FREE(q); + return -1; } SAFE_FREE(q); if (i <= 0) { @@ -607,6 +738,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_oom(session); return -1; } + rc = ssh_check_username_syntax(session->opts.username); + if (rc != SSH_OK) { + ssh_set_error_invalid(session); + return -1; + } } break; case SSH_OPTIONS_SSH_DIR: @@ -639,7 +775,27 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, if (q == NULL) { return -1; } - rc = ssh_list_prepend(session->opts.identity, q); + if (session->opts.exp_flags & SSH_OPT_EXP_FLAG_IDENTITY) { + rc = ssh_list_append(session->opts.identity_non_exp, q); + } else { + rc = ssh_list_prepend(session->opts.identity_non_exp, q); + } + if (rc < 0) { + free(q); + return -1; + } + break; + case SSH_OPTIONS_CERTIFICATE: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } + q = strdup(v); + if (q == NULL) { + return -1; + } + rc = ssh_list_append(session->opts.certificate_non_exp, q); if (rc < 0) { free(q); return -1; @@ -659,6 +815,7 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_oom(session); return -1; } + session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; } break; case SSH_OPTIONS_GLOBAL_KNOWNHOSTS: @@ -680,6 +837,7 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_oom(session); return -1; } + session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS; } break; case SSH_OPTIONS_TIMEOUT: @@ -743,7 +901,9 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, } i = strtol(q, &p, 10); if (q == p) { + SSH_LOG(SSH_LOG_DEBUG, "No log verbositiy was parsed"); SAFE_FREE(q); + return -1; } SAFE_FREE(q); if (i < 0) { @@ -761,7 +921,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_CRYPT_C_S, v) < 0) + rc = ssh_options_set_algo(session, + SSH_CRYPT_C_S, + v, + &wanted_methods[SSH_CRYPT_C_S]); + if (rc < 0) return -1; } break; @@ -771,7 +935,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_CRYPT_S_C, v) < 0) + rc = ssh_options_set_algo(session, + SSH_CRYPT_S_C, + v, + &wanted_methods[SSH_CRYPT_S_C]); + if (rc < 0) return -1; } break; @@ -781,7 +949,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_KEX, v) < 0) + rc = ssh_options_set_algo(session, + SSH_KEX, + v, + &wanted_methods[SSH_KEX]); + if (rc < 0) return -1; } break; @@ -791,7 +963,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_HOSTKEYS, v) < 0) + rc = ssh_options_set_algo(session, + SSH_HOSTKEYS, + v, + &wanted_methods[SSH_HOSTKEYS]); + if (rc < 0) return -1; } break; @@ -801,20 +977,12 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_fips_mode()) { - p = ssh_keep_fips_algos(SSH_HOSTKEYS, v); - } else { - p = ssh_keep_known_algos(SSH_HOSTKEYS, v); - } - if (p == NULL) { - ssh_set_error(session, SSH_REQUEST_DENIED, - "Setting method: no known public key algorithm (%s)", - v); + rc = ssh_options_set_algo(session, + SSH_HOSTKEYS, + v, + &session->opts.pubkey_accepted_types); + if (rc < 0) return -1; - } - - SAFE_FREE(session->opts.pubkey_accepted_types); - session->opts.pubkey_accepted_types = p; } break; case SSH_OPTIONS_HMAC_C_S: @@ -823,7 +991,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_MAC_C_S, v) < 0) + rc = ssh_options_set_algo(session, + SSH_MAC_C_S, + v, + &wanted_methods[SSH_MAC_C_S]); + if (rc < 0) return -1; } break; @@ -833,7 +1005,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_MAC_S_C, v) < 0) + rc = ssh_options_set_algo(session, + SSH_MAC_S_C, + v, + &wanted_methods[SSH_MAC_S_C]); + if (rc < 0) return -1; } break; @@ -843,16 +1019,18 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (strcasecmp(value,"yes")==0){ - if(ssh_options_set_algo(session,SSH_COMP_C_S,"zlib@openssh.com,zlib") < 0) - return -1; - } else if (strcasecmp(value,"no")==0){ - if(ssh_options_set_algo(session,SSH_COMP_C_S,"none") < 0) - return -1; - } else { - if (ssh_options_set_algo(session, SSH_COMP_C_S, v) < 0) - return -1; + const char *tmp = v; + if (strcasecmp(value, "yes") == 0){ + tmp = "zlib@openssh.com,none"; + } else if (strcasecmp(value, "no") == 0){ + tmp = "none,zlib@openssh.com"; } + rc = ssh_options_set_algo(session, + SSH_COMP_C_S, + tmp, + &wanted_methods[SSH_COMP_C_S]); + if (rc < 0) + return -1; } break; case SSH_OPTIONS_COMPRESSION_S_C: @@ -861,16 +1039,19 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (strcasecmp(value,"yes")==0){ - if(ssh_options_set_algo(session,SSH_COMP_S_C,"zlib@openssh.com,zlib") < 0) - return -1; - } else if (strcasecmp(value,"no")==0){ - if(ssh_options_set_algo(session,SSH_COMP_S_C,"none") < 0) - return -1; - } else { - if (ssh_options_set_algo(session, SSH_COMP_S_C, v) < 0) - return -1; + const char *tmp = v; + if (strcasecmp(value, "yes") == 0){ + tmp = "zlib@openssh.com,none"; + } else if (strcasecmp(value, "no") == 0){ + tmp = "none,zlib@openssh.com"; } + + rc = ssh_options_set_algo(session, + SSH_COMP_S_C, + tmp, + &wanted_methods[SSH_COMP_S_C]); + if (rc < 0) + return -1; } break; case SSH_OPTIONS_COMPRESSION: @@ -906,7 +1087,6 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, session->opts.StrictHostKeyChecking = (*x & 0xff) > 0 ? 1 : 0; } - session->opts.StrictHostKeyChecking = *(int*)value; break; case SSH_OPTIONS_PROXYCOMMAND: v = value; @@ -923,6 +1103,7 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, return -1; } session->opts.ProxyCommand = q; + session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_PROXYCOMMAND; } } break; @@ -1029,6 +1210,77 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, session->opts.rekey_time = (*x) * 1000; } break; + case SSH_OPTIONS_RSA_MIN_SIZE: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *)value; + if (*x > 0 && *x < 768) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The provided value (%u) for minimal RSA key " + "size is too small. Use at least 768 bits.", *x); + return -1; + } + session->opts.rsa_min_size = *x; + } + break; + case SSH_OPTIONS_IDENTITY_AGENT: + v = value; + SAFE_FREE(session->opts.agent_socket); + if (v == NULL) { + /* The default value will be set by the ssh_options_apply() */ + } else if (v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + session->opts.agent_socket = ssh_path_expand_tilde(v); + if (session->opts.agent_socket == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_IDENTITIES_ONLY: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + bool *x = (bool *)value; + session->opts.identities_only = *x; + } + break; + case SSH_OPTIONS_CONTROL_MASTER: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + if (*x < SSH_CONTROL_MASTER_NO || *x > SSH_CONTROL_MASTER_AUTOASK) { + ssh_set_error_invalid(session); + return -1; + } + session->opts.control_master = *x; + } + break; + case SSH_OPTIONS_CONTROL_PATH: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + SAFE_FREE(session->opts.control_path); + rc = strcasecmp(v, "none"); + if (rc != 0) { + session->opts.control_path = ssh_path_expand_tilde(v); + if (session->opts.control_path == NULL) { + ssh_set_error_oom(session); + return -1; + } + session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_CONTROL_PATH; + } + } + break; default: ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); return -1; @@ -1039,6 +1291,46 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, } /** + * @brief This function returns the current algorithms used for algorithm + * negotiation. It is either libssh default, option manually set or option + * read from configuration file. + * + * This function will return NULL on error + * + * @param session An allocated SSH session structure. + * @param algo One of the ssh_kex_types_e values. + */ +char *ssh_options_get_algo(ssh_session session, + enum ssh_kex_types_e algo) +{ + char *value = NULL; + + /* Check session and algo values are valid */ + + if (session == NULL) { + return NULL; + } + + if (algo >= SSH_LANG_C_S) { + ssh_set_error_invalid(session); + return NULL; + } + + /* Get the option the user has set, if there is one */ + value = session->opts.wanted_methods[algo]; + if (value == NULL) { + /* The user has not set a value, return the appropriate default */ + if (ssh_fips_mode()) + value = (char *)ssh_kex_get_fips_methods(algo); + else + value = (char *)ssh_kex_get_default_methods(algo); + } + + return value; +} + + +/** * @brief This function can get ssh the ssh port. It must only be used on * a valid ssh session. This function is useful when the session * options have been automatically inferred from the environment @@ -1070,7 +1362,7 @@ int ssh_options_get_port(ssh_session session, unsigned int* port_target) { /** * @brief This function can get ssh options, it does not support all options provided for * ssh options set, but mostly those which a user-space program may care about having - * trusted the ssh driver to infer these values from underlaying configuration files. + * trusted the ssh driver to infer these values from underlying configuration files. * It operates only on those SSH_OPTIONS_* which return char*. If you wish to receive * the port then please use ssh_options_get_port() which returns an unsigned int. * @@ -1090,7 +1382,7 @@ int ssh_options_get_port(ssh_session session, unsigned int* port_target) { * - SSH_OPTIONS_IDENTITY: * Get the first identity file name (const char *).\n * \n - * By default identity, id_dsa and id_rsa are checked. + * By default id_rsa, id_ecdsa and id_ed25519 files are used. * * - SSH_OPTIONS_PROXYCOMMAND: * Get the proxycommand necessary to log into the @@ -1103,6 +1395,46 @@ int ssh_options_get_port(ssh_session session, unsigned int* port_target) { * - SSH_OPTIONS_KNOWNHOSTS: * Get the path to the known_hosts file being used. * + * - SSH_OPTIONS_CONTROL_PATH: + * Get the path to the control socket being used for connection + * multiplexing. + * + * - SSH_OPTIONS_KEY_EXCHANGE: + * Get the key exchange methods to be used. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_HOSTKEYS: + * Get the preferred server host key types. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: + * Get the preferred public key algorithms to be used for + * authentication. + * + * - SSH_OPTIONS_CIPHERS_C_S: + * Get the symmetric cipher client to server. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_CIPHERS_S_C: + * Get the symmetric cipher server to client. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_HMAC_C_S: + * Get the Message Authentication Code algorithm client to server + * If the option has not been set, returns the defaults. + * + * - SSH_OPTIONS_HMAC_S_C: + * Get the Message Authentication Code algorithm server to client + * If the option has not been set, returns the defaults. + * + * - SSH_OPTIONS_COMPRESSION_C_S: + * Get the compression to use for client to server communication + * If the option has not been set, returns the defaults. + * + * - SSH_OPTIONS_COMPRESSION_S_C: + * Get the compression to use for server to client communication + * If the option has not been set, returns the defaults. + * * @param value The value to get into. As a char**, space will be * allocated by the function for the value, it is * your responsibility to free the memory using @@ -1125,34 +1457,78 @@ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value) switch(type) { - case SSH_OPTIONS_HOST: { + case SSH_OPTIONS_HOST: src = session->opts.host; break; - } - case SSH_OPTIONS_USER: { + + case SSH_OPTIONS_USER: src = session->opts.username; break; - } + case SSH_OPTIONS_IDENTITY: { - struct ssh_iterator *it = ssh_list_get_iterator(session->opts.identity); + struct ssh_iterator *it; + it = ssh_list_get_iterator(session->opts.identity); + if (it == NULL) { + it = ssh_list_get_iterator(session->opts.identity_non_exp); + } if (it == NULL) { return SSH_ERROR; } src = ssh_iterator_value(char *, it); break; } - case SSH_OPTIONS_PROXYCOMMAND: { + + case SSH_OPTIONS_PROXYCOMMAND: src = session->opts.ProxyCommand; break; - } - case SSH_OPTIONS_KNOWNHOSTS: { + + case SSH_OPTIONS_KNOWNHOSTS: src = session->opts.knownhosts; break; - } - case SSH_OPTIONS_GLOBAL_KNOWNHOSTS: { + + case SSH_OPTIONS_GLOBAL_KNOWNHOSTS: src = session->opts.global_knownhosts; break; - } + case SSH_OPTIONS_CONTROL_PATH: + src = session->opts.control_path; + break; + + case SSH_OPTIONS_CIPHERS_C_S: + src = ssh_options_get_algo(session, SSH_CRYPT_C_S); + break; + + case SSH_OPTIONS_CIPHERS_S_C: + src = ssh_options_get_algo(session, SSH_CRYPT_S_C); + break; + + case SSH_OPTIONS_KEY_EXCHANGE: + src = ssh_options_get_algo(session, SSH_KEX); + break; + + case SSH_OPTIONS_HOSTKEYS: + src = ssh_options_get_algo(session, SSH_HOSTKEYS); + break; + + case SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: + src = session->opts.pubkey_accepted_types; + break; + + case SSH_OPTIONS_HMAC_C_S: + src = ssh_options_get_algo(session, SSH_MAC_C_S); + break; + + case SSH_OPTIONS_HMAC_S_C: + src = ssh_options_get_algo(session, SSH_MAC_S_C); + break; + + case SSH_OPTIONS_COMPRESSION_C_S: + src = ssh_options_get_algo(session, SSH_COMP_C_S); + break; + + case SSH_OPTIONS_COMPRESSION_S_C: + src = ssh_options_get_algo(session, SSH_COMP_S_C); + break; + default: ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); return SSH_ERROR; @@ -1175,10 +1551,10 @@ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value) * This is a helper for your application to generate the appropriate * options from the command line arguments.\n * The argv array and argc value are changed so that the parsed - * arguments wont appear anymore in them.\n + * arguments won't appear anymore in them.\n * The single arguments (without switches) are not parsed. thus, * myssh -l user localhost\n - * The command wont set the hostname value of options to localhost. + * The command won't set the hostname value of options to localhost. * * @param session The session to configure. * @@ -1208,8 +1584,6 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) size_t i = 0; int argc = *argcptr; int debuglevel = 0; - int usersa = 0; - int usedss = 0; int compress = 0; int cont = 1; size_t current = 0; @@ -1223,7 +1597,7 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) } opterr = 0; /* shut up getopt */ - while((opt = getopt(argc, argv, "c:i:Cl:p:vb:rd12")) != -1) { + while((opt = getopt(argc, argv, "c:i:Cl:p:vb:r12")) != -1) { switch(opt) { case 'l': user = optarg; @@ -1235,10 +1609,6 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) debuglevel++; break; case 'r': - usersa++; - break; - case 'd': - usedss++; break; case 'c': cipher = optarg; @@ -1302,11 +1672,6 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) optind++; } - if (usersa && usedss) { - ssh_set_error(session, SSH_FATAL, "Either RSA or DSS must be chosen"); - cont = 0; - } - ssh_set_log_level(debuglevel); optind = saveoptind; @@ -1375,13 +1740,14 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) * @param session SSH session handle * * @param filename The options file to use, if NULL the default - * ~/.ssh/config will be used. + * ~/.ssh/config and /etc/ssh/ssh_config will be used. * * @return 0 on success, < 0 on error. * * @see ssh_options_set() */ -int ssh_options_parse_config(ssh_session session, const char *filename) { +int ssh_options_parse_config(ssh_session session, const char *filename) +{ char *expanded_filename; int r; @@ -1426,8 +1792,8 @@ out: return r; } -int ssh_options_apply(ssh_session session) { - struct ssh_iterator *it; +int ssh_options_apply(ssh_session session) +{ char *tmp; int rc; @@ -1445,54 +1811,124 @@ int ssh_options_apply(ssh_session session) { } } - if (session->opts.knownhosts == NULL) { - tmp = ssh_path_expand_escape(session, "%d/known_hosts"); - } else { - tmp = ssh_path_expand_escape(session, session->opts.knownhosts); - } - if (tmp == NULL) { - return -1; + if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_KNOWNHOSTS) == 0) { + if (session->opts.knownhosts == NULL) { + tmp = ssh_path_expand_escape(session, "%d/known_hosts"); + } else { + tmp = ssh_path_expand_escape(session, session->opts.knownhosts); + } + if (tmp == NULL) { + return -1; + } + free(session->opts.knownhosts); + session->opts.knownhosts = tmp; + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_KNOWNHOSTS; } - free(session->opts.knownhosts); - session->opts.knownhosts = tmp; - if (session->opts.global_knownhosts == NULL) { - tmp = strdup("/etc/ssh/ssh_known_hosts"); - } else { - tmp = ssh_path_expand_escape(session, session->opts.global_knownhosts); + if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS) == 0) { + if (session->opts.global_knownhosts == NULL) { + tmp = strdup("/etc/ssh/ssh_known_hosts"); + } else { + tmp = ssh_path_expand_escape(session, + session->opts.global_knownhosts); + } + if (tmp == NULL) { + return -1; + } + free(session->opts.global_knownhosts); + session->opts.global_knownhosts = tmp; + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS; } - if (tmp == NULL) { - return -1; + + + if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_PROXYCOMMAND) == 0) { + if (session->opts.ProxyCommand != NULL) { + char *p = NULL; + size_t plen = strlen(session->opts.ProxyCommand) + + 5 /* strlen("exec ") */; + + if (strncmp(session->opts.ProxyCommand, "exec ", 5) != 0) { + p = malloc(plen + 1 /* \0 */); + if (p == NULL) { + return -1; + } + + rc = snprintf(p, plen + 1, "exec %s", session->opts.ProxyCommand); + if ((size_t)rc != plen) { + free(p); + return -1; + } + tmp = ssh_path_expand_escape(session, p); + free(p); + } else { + tmp = ssh_path_expand_escape(session, + session->opts.ProxyCommand); + } + + if (tmp == NULL) { + return -1; + } + free(session->opts.ProxyCommand); + session->opts.ProxyCommand = tmp; + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_PROXYCOMMAND; + } } - free(session->opts.global_knownhosts); - session->opts.global_knownhosts = tmp; - if (session->opts.ProxyCommand != NULL) { - tmp = ssh_path_expand_escape(session, session->opts.ProxyCommand); - if (tmp == NULL) { - return -1; + if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_CONTROL_PATH) == 0) { + if (session->opts.control_path != NULL) { + tmp = ssh_path_expand_escape(session, session->opts.control_path); + if (tmp == NULL) { + return -1; + } + free(session->opts.control_path); + session->opts.control_path = tmp; + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_CONTROL_PATH; } - free(session->opts.ProxyCommand); - session->opts.ProxyCommand = tmp; } - for (it = ssh_list_get_iterator(session->opts.identity); - it != NULL; - it = it->next) { - char *id = (char *) it->data; - if (strncmp(id, "pkcs11:", 6) == 0) { + for (tmp = ssh_list_pop_head(char *, session->opts.identity_non_exp); + tmp != NULL; + tmp = ssh_list_pop_head(char *, session->opts.identity_non_exp)) { + char *id = tmp; + if (strncmp(id, "pkcs11:", 6) != 0) { /* PKCS#11 URIs are using percent-encoding so we can not mix * it with ssh expansion of ssh escape characters. - * Skip these identities now, before we will have PKCS#11 support */ - continue; + tmp = ssh_path_expand_escape(session, id); + if (tmp == NULL) { + return -1; + } + free(id); + } + + /* use append to keep the order at first call and use prepend + * to put anything that comes on the nth calls to the beginning */ + if (session->opts.exp_flags & SSH_OPT_EXP_FLAG_IDENTITY) { + rc = ssh_list_prepend(session->opts.identity, tmp); + } else { + rc = ssh_list_append(session->opts.identity, tmp); } + if (rc != SSH_OK) { + return -1; + } + } + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_IDENTITY; + + for (tmp = ssh_list_pop_head(char *, session->opts.certificate_non_exp); + tmp != NULL; + tmp = ssh_list_pop_head(char *, session->opts.certificate_non_exp)) { + char *id = tmp; + tmp = ssh_path_expand_escape(session, id); if (tmp == NULL) { return -1; } free(id); - it->data = tmp; + + rc = ssh_list_append(session->opts.certificate, tmp); + if (rc != SSH_OK) { + return -1; + } } return 0; @@ -1501,12 +1937,27 @@ int ssh_options_apply(ssh_session session) { /** @} */ #ifdef WITH_SERVER +static bool ssh_bind_key_size_allowed(ssh_bind sshbind, ssh_key key) +{ + int min_size = 0; + + switch (ssh_key_type(key)) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + min_size = sshbind->rsa_min_size; + return ssh_key_size_allowed_rsa(min_size, key); + default: + return true; + } +} + /** * @addtogroup libssh_server * @{ */ -static int ssh_bind_set_key(ssh_bind sshbind, char **key_loc, - const void *value) { +static int +ssh_bind_set_key(ssh_bind sshbind, char **key_loc, const void *value) +{ if (value == NULL) { ssh_set_error_invalid(sshbind); return -1; @@ -1523,26 +1974,12 @@ static int ssh_bind_set_key(ssh_bind sshbind, char **key_loc, static int ssh_bind_set_algo(ssh_bind sshbind, enum ssh_kex_types_e algo, - const char *list) + const char *list, + char **place) { - char *p = NULL; - - if (ssh_fips_mode()) { - p = ssh_keep_fips_algos(algo, list); - } else { - p = ssh_keep_known_algos(algo, list); - } - if (p == NULL) { - ssh_set_error(sshbind, SSH_REQUEST_DENIED, - "Setting method: no algorithm for method \"%s\" (%s)", - ssh_kex_get_description(algo), list); - return -1; - } - - SAFE_FREE(sshbind->wanted_methods[algo]); - sshbind->wanted_methods[algo] = p; - - return 0; + /* sshbind is needed only for ssh_set_error which takes void* + * the typecast is only to satisfy function parameter type */ + return ssh_options_set_algo((ssh_session)sshbind, algo, list, place); } /** @@ -1556,7 +1993,7 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * - SSH_BIND_OPTIONS_HOSTKEY: * Set the path to an ssh host key, regardless * of type. Only one key from per key type - * (RSA, DSA, ECDSA) is allowed in an ssh_bind + * (RSA, ED25519 and ECDSA) is allowed in an ssh_bind * at a time, and later calls to this function * with this option for the same key type will * override prior calls (const char *). @@ -1580,46 +2017,52 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * - SSH_LOG_NOLOG: No logging * - SSH_LOG_WARNING: Only warnings * - SSH_LOG_PROTOCOL: High level protocol information - * - SSH_LOG_PACKET: Lower level protocol infomations, packet level + * - SSH_LOG_PACKET: Lower level protocol information, + * packet level * - SSH_LOG_FUNCTIONS: Every function path + * The default is SSH_LOG_NOLOG. * * - SSH_BIND_OPTIONS_LOG_VERBOSITY_STR: * Set the session logging verbosity via a * string that will be converted to a numerical * value (e.g. "3") and interpreted according * to the values of - * SSH_BIND_OPTIONS_LOG_VERBOSITY above (const - * char *). - * - * - SSH_BIND_OPTIONS_DSAKEY: - * Set the path to the ssh host dsa key, SSHv2 - * only (const char *). + * SSH_BIND_OPTIONS_LOG_VERBOSITY above + * (const char *). * * - SSH_BIND_OPTIONS_RSAKEY: - * Set the path to the ssh host rsa key, SSHv2 - * only (const char *). + * Deprecated alias to SSH_BIND_OPTIONS_HOSTKEY + * (const char *). * * - SSH_BIND_OPTIONS_ECDSAKEY: - * Set the path to the ssh host ecdsa key, - * SSHv2 only (const char *). + * Deprecated alias to SSH_BIND_OPTIONS_HOSTKEY + * (const char *). * * - SSH_BIND_OPTIONS_BANNER: * Set the server banner sent to clients (const char *). * + * - SSH_BIND_OPTIONS_DSAKEY: + * This is DEPRECATED, please do not use. + * * - SSH_BIND_OPTIONS_IMPORT_KEY: - * Set the Private Key for the server directly (ssh_key) + * Set the Private Key for the server directly + * (ssh_key). It will be free'd by ssh_bind_free(). + * + * - SSH_BIND_OPTIONS_IMPORT_KEY_STR: + * Set the Private key for the server from a + * base64 encoded buffer (const char *). * * - SSH_BIND_OPTIONS_CIPHERS_C_S: - * Set the symmetric cipher client to server (const char *, - * comma-separated list). + * Set the symmetric cipher client to server + * (const char *, comma-separated list). * * - SSH_BIND_OPTIONS_CIPHERS_S_C: - * Set the symmetric cipher server to client (const char *, - * comma-separated list). + * Set the symmetric cipher server to client + * (const char *, comma-separated list). * * - SSH_BIND_OPTIONS_KEY_EXCHANGE: - * Set the key exchange method to be used (const char *, - * comma-separated list). ex: + * Set the key exchange method to be used + * (const char *, comma-separated list). ex: * "ecdh-sha2-nistp256,diffie-hellman-group14-sha1" * * - SSH_BIND_OPTIONS_HMAC_C_S: @@ -1656,250 +2099,237 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * set and then filtered against this list. * (const char *, comma-separated list). * + * - SSH_BIND_OPTIONS_MODULI + * Set the path to the moduli file. Defaults to + * /etc/ssh/moduli if not specified (const char *). + * + * - SSH_BIND_OPTIONS_RSA_MIN_SIZE + * Set the minimum RSA key size in bits to be accepted by + * the server for both authentication and hostkey + * operations. The values under 768 bits are not accepted + * even with this configuration option as they are + * considered completely broken. Setting 0 will revert + * the value to defaults. + * Default is 1024 bits or 2048 bits in FIPS mode. + * (int) + * + * * @param value The value to set. This is a generic pointer and the * datatype which should be used is described at the * corresponding value of type above. * - * @return 0 on success, < 0 on error, invalid option, or parameter. + * @return 0 on success, < 0 on error, invalid option, or + * parameter. */ -int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, - const void *value) +int +ssh_bind_options_set(ssh_bind sshbind, + enum ssh_bind_options_e type, + const void *value) { - char *p, *q; - const char *v; - int i, rc; + bool allowed; + char *p, *q; + const char *v; + int i, rc; + char **wanted_methods = sshbind->wanted_methods; - if (sshbind == NULL) { - return -1; - } + if (sshbind == NULL) { + return -1; + } - switch (type) { + switch (type) { + case SSH_BIND_OPTIONS_RSAKEY: + case SSH_BIND_OPTIONS_ECDSAKEY: + /* deprecated */ case SSH_BIND_OPTIONS_HOSTKEY: - if (value == NULL) { - ssh_set_error_invalid(sshbind); - return -1; - } else { - int key_type; - ssh_key key; - ssh_key *bind_key_loc = NULL; - char **bind_key_path_loc; - - rc = ssh_pki_import_privkey_file(value, NULL, NULL, NULL, &key); - if (rc != SSH_OK) { - return -1; - } - key_type = ssh_key_type(key); - switch (key_type) { - case SSH_KEYTYPE_DSS: -#ifdef HAVE_DSA - bind_key_loc = &sshbind->dsa; - bind_key_path_loc = &sshbind->dsakey; -#else - ssh_set_error(sshbind, - SSH_FATAL, - "DSS key used and libssh compiled " - "without DSA support"); -#endif - break; - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: -#ifdef HAVE_ECC - bind_key_loc = &sshbind->ecdsa; - bind_key_path_loc = &sshbind->ecdsakey; -#else - ssh_set_error(sshbind, - SSH_FATAL, - "ECDSA key used and libssh compiled " - "without ECDSA support"); -#endif - break; - case SSH_KEYTYPE_RSA: - bind_key_loc = &sshbind->rsa; - bind_key_path_loc = &sshbind->rsakey; - break; - case SSH_KEYTYPE_ED25519: - bind_key_loc = &sshbind->ed25519; - bind_key_path_loc = &sshbind->ed25519key; - break; - default: - ssh_set_error(sshbind, - SSH_FATAL, - "Unsupported key type %d", key_type); - } - - if (bind_key_loc == NULL) { - ssh_key_free(key); - return -1; - } - - /* Set the location of the key on disk even though we don't - need it in case some other function wants it */ - rc = ssh_bind_set_key(sshbind, bind_key_path_loc, value); - if (rc < 0) { - ssh_key_free(key); - return -1; - } - ssh_key_free(*bind_key_loc); - *bind_key_loc = key; - } - break; case SSH_BIND_OPTIONS_IMPORT_KEY: + case SSH_BIND_OPTIONS_IMPORT_KEY_STR: if (value == NULL) { ssh_set_error_invalid(sshbind); return -1; } else { int key_type; ssh_key *bind_key_loc = NULL; - ssh_key key = (ssh_key)value; - - key_type = ssh_key_type(key); - switch (key_type) { - case SSH_KEYTYPE_DSS: -#ifdef HAVE_DSA - bind_key_loc = &sshbind->dsa; -#else + ssh_key key = NULL; + char **bind_key_path_loc = NULL; + + if (type == SSH_BIND_OPTIONS_IMPORT_KEY_STR) { + const char *key_str = (const char *)value; + rc = ssh_pki_import_privkey_base64(key_str, + NULL, + NULL, + NULL, + &key); + if (rc == SSH_ERROR) { ssh_set_error(sshbind, SSH_FATAL, - "DSA key used and libssh compiled " - "without DSA support"); -#endif - break; - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: + "Failed to import key from buffer"); + return -1; + } + } else if (type == SSH_BIND_OPTIONS_IMPORT_KEY) { + key = (ssh_key)value; + } else { + rc = ssh_pki_import_privkey_file(value, NULL, NULL, NULL, &key); + if (rc != SSH_OK) { + return -1; + } + } + allowed = ssh_bind_key_size_allowed(sshbind, key); + if (!allowed) { + ssh_set_error(sshbind, + SSH_FATAL, + "The host key size %d is too small.", + ssh_key_size(key)); + return -1; + } + key_type = ssh_key_type(key); + switch (key_type) { + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: #ifdef HAVE_ECC - bind_key_loc = &sshbind->ecdsa; + bind_key_loc = &sshbind->ecdsa; + bind_key_path_loc = &sshbind->ecdsakey; #else - ssh_set_error(sshbind, - SSH_FATAL, - "ECDSA key used and libssh compiled " - "without ECDSA support"); + ssh_set_error(sshbind, + SSH_FATAL, + "ECDSA key used and libssh compiled " + "without ECDSA support"); #endif - break; - case SSH_KEYTYPE_RSA: - bind_key_loc = &sshbind->rsa; - break; - case SSH_KEYTYPE_ED25519: - bind_key_loc = &sshbind->ed25519; - break; - default: - ssh_set_error(sshbind, - SSH_FATAL, - "Unsupported key type %d", key_type); + break; + case SSH_KEYTYPE_RSA: + bind_key_loc = &sshbind->rsa; + bind_key_path_loc = &sshbind->rsakey; + break; + case SSH_KEYTYPE_ED25519: + bind_key_loc = &sshbind->ed25519; + bind_key_path_loc = &sshbind->ed25519key; + break; + default: + ssh_set_error(sshbind, + SSH_FATAL, + "Unsupported key type %d", + key_type); + } + if (type == SSH_BIND_OPTIONS_RSAKEY || + type == SSH_BIND_OPTIONS_ECDSAKEY || + type == SSH_BIND_OPTIONS_HOSTKEY) { + if (bind_key_loc == NULL) { + ssh_key_free(key); + return -1; + } + /* Set the location of the key on disk even though we don't + need it in case some other function wants it */ + rc = ssh_bind_set_key(sshbind, bind_key_path_loc, value); + if (rc < 0) { + ssh_key_free(key); + return -1; + } + } else { + if (bind_key_loc == NULL) { + return -1; + } } - if (bind_key_loc == NULL) - return -1; ssh_key_free(*bind_key_loc); *bind_key_loc = key; } break; case SSH_BIND_OPTIONS_BINDADDR: - if (value == NULL) { - ssh_set_error_invalid(sshbind); - return -1; - } else { - SAFE_FREE(sshbind->bindaddr); - sshbind->bindaddr = strdup(value); - if (sshbind->bindaddr == NULL) { - ssh_set_error_oom(sshbind); - return -1; + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->bindaddr); + sshbind->bindaddr = strdup(value); + if (sshbind->bindaddr == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } } - } - break; + break; case SSH_BIND_OPTIONS_BINDPORT: - if (value == NULL) { - ssh_set_error_invalid(sshbind); - return -1; - } else { - int *x = (int *) value; - sshbind->bindport = *x & 0xffffU; - } - break; - case SSH_BIND_OPTIONS_BINDPORT_STR: - if (value == NULL) { - sshbind->bindport = 22 & 0xffffU; - } else { - q = strdup(value); - if (q == NULL) { - ssh_set_error_oom(sshbind); - return -1; - } - i = strtol(q, &p, 10); - if (q == p) { - SAFE_FREE(q); - } - SAFE_FREE(q); - - sshbind->bindport = i & 0xffffU; - } - break; - case SSH_BIND_OPTIONS_LOG_VERBOSITY: - if (value == NULL) { - ssh_set_error_invalid(sshbind); - return -1; - } else { - int *x = (int *) value; - ssh_set_log_level(*x & 0xffffU); - } - break; - case SSH_BIND_OPTIONS_LOG_VERBOSITY_STR: - if (value == NULL) { - ssh_set_log_level(0); - } else { - q = strdup(value); - if (q == NULL) { - ssh_set_error_oom(sshbind); - return -1; - } - i = strtol(q, &p, 10); - if (q == p) { - SAFE_FREE(q); + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + int *x = (int *)value; + sshbind->bindport = *x & 0xffffU; } - SAFE_FREE(q); + break; + case SSH_BIND_OPTIONS_BINDPORT_STR: + if (value == NULL) { + sshbind->bindport = 22 & 0xffffU; + } else { + q = strdup(value); + if (q == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SSH_LOG(SSH_LOG_DEBUG, "No bind port was parsed"); + SAFE_FREE(q); + return -1; + } + SAFE_FREE(q); - ssh_set_log_level(i & 0xffffU); - } - break; - case SSH_BIND_OPTIONS_DSAKEY: - rc = ssh_bind_set_key(sshbind, &sshbind->dsakey, value); - if (rc < 0) { - return -1; + sshbind->bindport = i & 0xffffU; } break; - case SSH_BIND_OPTIONS_RSAKEY: - rc = ssh_bind_set_key(sshbind, &sshbind->rsakey, value); - if (rc < 0) { + case SSH_BIND_OPTIONS_LOG_VERBOSITY: + if (value == NULL) { + ssh_set_error_invalid(sshbind); return -1; + } else { + int *x = (int *)value; + ssh_set_log_level(*x & 0xffffU); } break; - case SSH_BIND_OPTIONS_ECDSAKEY: - rc = ssh_bind_set_key(sshbind, &sshbind->ecdsakey, value); - if (rc < 0) { - return -1; + case SSH_BIND_OPTIONS_LOG_VERBOSITY_STR: + if (value == NULL) { + ssh_set_log_level(0); + } else { + q = strdup(value); + if (q == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SSH_LOG(SSH_LOG_DEBUG, "No log verbositiy was parsed"); + SAFE_FREE(q); + return -1; + } + SAFE_FREE(q); + + ssh_set_log_level(i & 0xffffU); } break; case SSH_BIND_OPTIONS_BANNER: - if (value == NULL) { - ssh_set_error_invalid(sshbind); - return -1; - } else { - SAFE_FREE(sshbind->banner); - sshbind->banner = strdup(value); - if (sshbind->banner == NULL) { - ssh_set_error_oom(sshbind); - return -1; + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->banner); + sshbind->banner = strdup(value); + if (sshbind->banner == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } } - } - break; + break; case SSH_BIND_OPTIONS_CIPHERS_C_S: v = value; if (v == NULL || v[0] == '\0') { ssh_set_error_invalid(sshbind); return -1; } else { - if (ssh_bind_set_algo(sshbind, SSH_CRYPT_C_S, v) < 0) + rc = ssh_bind_set_algo(sshbind, + SSH_CRYPT_C_S, + v, + &wanted_methods[SSH_CRYPT_C_S]); + if (rc < 0) { return -1; + } } break; case SSH_BIND_OPTIONS_CIPHERS_S_C: @@ -1908,8 +2338,13 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - if (ssh_bind_set_algo(sshbind, SSH_CRYPT_S_C, v) < 0) + rc = ssh_bind_set_algo(sshbind, + SSH_CRYPT_S_C, + v, + &wanted_methods[SSH_CRYPT_S_C]); + if (rc < 0) { return -1; + } } break; case SSH_BIND_OPTIONS_KEY_EXCHANGE: @@ -1918,7 +2353,10 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - rc = ssh_bind_set_algo(sshbind, SSH_KEX, v); + rc = ssh_bind_set_algo(sshbind, + SSH_KEX, + v, + &wanted_methods[SSH_KEX]); if (rc < 0) { return -1; } @@ -1930,18 +2368,28 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - if (ssh_bind_set_algo(sshbind, SSH_MAC_C_S, v) < 0) + rc = ssh_bind_set_algo(sshbind, + SSH_MAC_C_S, + v, + &wanted_methods[SSH_MAC_C_S]); + if (rc < 0) { return -1; + } } break; - case SSH_BIND_OPTIONS_HMAC_S_C: + case SSH_BIND_OPTIONS_HMAC_S_C: v = value; if (v == NULL || v[0] == '\0') { ssh_set_error_invalid(sshbind); return -1; } else { - if (ssh_bind_set_algo(sshbind, SSH_MAC_S_C, v) < 0) + rc = ssh_bind_set_algo(sshbind, + SSH_MAC_S_C, + v, + &wanted_methods[SSH_MAC_S_C]); + if (rc < 0) { return -1; + } } break; case SSH_BIND_OPTIONS_CONFIG_DIR: @@ -1966,20 +2414,13 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - if (ssh_fips_mode()) { - p = ssh_keep_fips_algos(SSH_HOSTKEYS, v); - } else { - p = ssh_keep_known_algos(SSH_HOSTKEYS, v); - } - if (p == NULL) { - ssh_set_error(sshbind, SSH_REQUEST_DENIED, - "Setting method: no known public key algorithm (%s)", - v); + rc = ssh_bind_set_algo(sshbind, + SSH_HOSTKEYS, + v, + &sshbind->pubkey_accepted_key_types); + if (rc < 0) { return -1; } - - SAFE_FREE(sshbind->pubkey_accepted_key_types); - sshbind->pubkey_accepted_key_types = p; } break; case SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS: @@ -1988,7 +2429,10 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - rc = ssh_bind_set_algo(sshbind, SSH_HOSTKEYS, v); + rc = ssh_bind_set_algo(sshbind, + SSH_HOSTKEYS, + v, + &wanted_methods[SSH_HOSTKEYS]); if (rc < 0) { return -1; } @@ -2003,19 +2447,53 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, sshbind->config_processed = !(*x); } break; + case SSH_BIND_OPTIONS_MODULI: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->moduli_file); + sshbind->moduli_file = strdup(value); + if (sshbind->moduli_file == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + break; + case SSH_BIND_OPTIONS_RSA_MIN_SIZE: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + int *x = (int *)value; + if (*x > 0 && *x < 768) { + ssh_set_error(sshbind, + SSH_REQUEST_DENIED, + "The provided value (%u) for minimal RSA key " + "size is too small. Use at least 768 bits.", + *x); + return -1; + } + sshbind->rsa_min_size = *x; + } + break; default: - ssh_set_error(sshbind, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); - return -1; - break; - } + ssh_set_error(sshbind, + SSH_REQUEST_DENIED, + "Unknown ssh option %d", + type); + return -1; + break; + } - return 0; + return 0; } static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) { - char buf[MAX_BUF_SIZE]; - char *r, *x = NULL; + char *buf = NULL; + char *r = NULL; + char *x = NULL; const char *p; size_t i, l; @@ -2031,6 +2509,13 @@ static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) return NULL; } + buf = malloc(MAX_BUF_SIZE); + if (buf == NULL) { + ssh_set_error_oom(sshbind); + free(r); + return NULL; + } + p = r; buf[0] = '\0'; @@ -2039,6 +2524,7 @@ static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) buf[i] = *p; i++; if (i >= MAX_BUF_SIZE) { + free(buf); free(r); return NULL; } @@ -2058,12 +2544,14 @@ static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) default: ssh_set_error(sshbind, SSH_FATAL, "Wrong escape sequence detected"); + free(buf); free(r); return NULL; } if (x == NULL) { ssh_set_error_oom(sshbind); + free(buf); free(r); return NULL; } @@ -2072,18 +2560,26 @@ static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) if (i >= MAX_BUF_SIZE) { ssh_set_error(sshbind, SSH_FATAL, "String too long"); + free(buf); free(x); free(r); return NULL; } l = strlen(buf); - strncpy(buf + l, x, sizeof(buf) - l - 1); + strncpy(buf + l, x, MAX_BUF_SIZE - l - 1); buf[i] = '\0'; SAFE_FREE(x); } free(r); - return strdup(buf); + + /* strip the unused space by realloc */ + x = realloc(buf, strlen(buf) + 1); + if (x == NULL) { + ssh_set_error_oom(sshbind); + free(buf); + } + return x; } /** @@ -2096,7 +2592,7 @@ static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) * @param sshbind SSH bind handle * * @param filename The options file to use; if NULL only the global - * configuration is parsed and applied (if it haven't been + * configuration is parsed and applied (if it hasn't been * processed before). * * @return 0 on success, < 0 on error. diff --git a/src/packet.c b/src/packet.c index f14731c9..8508c731 100644 --- a/src/packet.c +++ b/src/packet.c @@ -113,7 +113,7 @@ static ssh_packet_callback default_packet_handlers[]= { ssh_packet_global_request, // SSH2_MSG_GLOBAL_REQUEST 80 #else /* WITH_SERVER */ NULL, -#endif /* WITH_SERVER */ +#endif /* WITH_SERVER */ ssh_request_success, // SSH2_MSG_REQUEST_SUCCESS 81 ssh_request_denied, // SSH2_MSG_REQUEST_FAILURE 82 NULL, NULL, NULL, NULL, NULL, NULL, NULL,// 83-89 @@ -363,9 +363,14 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se * Transitions: * - session->dh_handshake_state = DH_STATE_INIT_SENT * then calls dh_handshake_server which triggers: - * - session->dh_handhsake_state = DH_STATE_NEWKEYS_SENT + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT * */ + if (!session->server) { + rc = SSH_PACKET_DENIED; + break; + } + if (session->session_state != SSH_SESSION_STATE_DH) { rc = SSH_PACKET_DENIED; break; @@ -391,7 +396,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se * or dh_handshake_state == DH_STATE_REQUEST_SENT (dh-gex) * * Transitions: - * - session->dh_handhsake_state = DH_STATE_NEWKEYS_SENT + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT * */ if (session->session_state != SSH_SESSION_STATE_DH) { @@ -425,7 +430,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se /* * States required: * - session_state == SSH_SESSION_STATE_AUTHENTICATING - * - dh_hanshake_state == DH_STATE_FINISHED + * - dh_handshake_state == DH_STATE_FINISHED * * Transitions: * - if authentication was successful: @@ -454,7 +459,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se /* * States required: * - session_state == SSH_SESSION_STATE_AUTHENTICATING - * - dh_hanshake_state == DH_STATE_FINISHED + * - dh_handshake_state == DH_STATE_FINISHED * - session->auth.state == SSH_AUTH_STATE_KBDINT_SENT * or session->auth.state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT * or session->auth.state == SSH_AUTH_STATE_PUBKEY_AUTH_SENT @@ -492,7 +497,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se /* * States required: * - session_state == SSH_SESSION_STATE_AUTHENTICATING - * - dh_hanshake_state == DH_STATE_FINISHED + * - dh_handshake_state == DH_STATE_FINISHED * - session->auth.state == SSH_AUTH_STATE_KBDINT_SENT * or session->auth.state == SSH_AUTH_STATE_PUBKEY_AUTH_SENT * or session->auth.state == SSH_AUTH_STATE_PASSWORD_AUTH_SENT @@ -688,10 +693,12 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se /* * States required: * - session_state == SSH_SESSION_STATE_AUTHENTICATED - * - session->global_req_state == SSH_CHANNEL_REQ_STATE_PENDING * * Transitions: - * - session->global_req_state == SSH_CHANNEL_REQ_STATE_ACCEPTED + * - From channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING + * - To channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED + * + * If not in a pending state, message is ignored in the callback handler. * */ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { @@ -699,21 +706,18 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se break; } - if (session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING) { - rc = SSH_PACKET_DENIED; - break; - } - rc = SSH_PACKET_ALLOWED; break; case SSH2_MSG_REQUEST_FAILURE: // 82 /* * States required: * - session_state == SSH_SESSION_STATE_AUTHENTICATED - * - session->global_req_state == SSH_CHANNEL_REQ_STATE_PENDING * * Transitions: - * - session->global_req_state == SSH_CHANNEL_REQ_STATE_DENIED + * - From channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING + * - To channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED + * + * If not in a pending state, message is ignored in the callback handler. * */ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { @@ -721,11 +725,6 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se break; } - if (session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING) { - rc = SSH_PACKET_DENIED; - break; - } - rc = SSH_PACKET_ALLOWED; break; case SSH2_MSG_CHANNEL_OPEN: // 90 @@ -878,10 +877,12 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se /* * States required: * - session_state == SSH_SESSION_STATE_AUTHENTICATED - * - channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING * * Transitions: - * - channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED + * - From channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING + * - To channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED + * + * If not in a pending state, message is ignored in the callback handler. * */ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { @@ -895,10 +896,12 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se /* * States required: * - session_state == SSH_SESSION_STATE_AUTHENTICATED - * - channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING * * Transitions: - * - channel->request_state = SSH_CHANNEL_REQ_STATE_DENIED + * - From channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING + * - To channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED + * + * If not in a pending state, message is ignored in the callback handler. * */ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { @@ -1054,24 +1057,26 @@ static bool ssh_packet_need_rekey(ssh_session session, * @param user pointer to current ssh_session * @param data pointer to the data received * @len length of data received. It might not be enough for a complete packet - * @returns number of bytes read and processed. + * @returns number of bytes read and processed. Zero means only partial packet + * received and negative value means error. */ -int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) +size_t +ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) { ssh_session session = (ssh_session)user; uint32_t blocksize = 8; uint32_t lenfield_blocksize = 8; size_t current_macsize = 0; uint8_t *ptr = NULL; - int to_be_read; + long to_be_read; int rc; uint8_t *cleartext_packet = NULL; uint8_t *packet_second_block = NULL; uint8_t *mac = NULL; - size_t packet_remaining; + size_t packet_remaining, packet_offset; uint32_t packet_len, compsize, payloadsize; uint8_t padding; - size_t processed = 0; /* number of byte processed from the callback */ + size_t processed = 0; /* number of bytes processed from the callback */ enum ssh_packet_filter_result_e filter_result; struct ssh_crypto_struct *crypto = NULL; bool etm = false; @@ -1114,7 +1119,7 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) session->packet_state == PACKET_STATE_PROCESSING ? "PROCESSING" : "unknown"); #endif - switch(session->packet_state) { + switch (session->packet_state) { case PACKET_STATE_INIT: if (receivedlen < lenfield_blocksize + etm_packet_offset) { /* @@ -1147,11 +1152,13 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) } if (!etm) { - ptr = ssh_buffer_allocate(session->in_buffer, lenfield_blocksize); + ptr = ssh_buffer_allocate(session->in_buffer, + lenfield_blocksize); if (ptr == NULL) { goto error; } - packet_len = ssh_packet_decrypt_len(session, ptr, (uint8_t *)data); + packet_len = ssh_packet_decrypt_len(session, ptr, + (uint8_t *)data); to_be_read = packet_len - lenfield_blocksize + sizeof(uint32_t); } else { /* Length is unencrypted in case of Encrypt-then-MAC */ @@ -1163,7 +1170,7 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) if (packet_len > MAX_PACKET_LEN) { ssh_set_error(session, SSH_FATAL, - "read_packet(): Packet len too high(%u %.4x)", + "read_packet(): Packet len too high(%" PRIu32 " %.4" PRIx32 ")", packet_len, packet_len); goto error; } @@ -1171,7 +1178,7 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) /* remote sshd sends invalid sizes? */ ssh_set_error(session, SSH_FATAL, - "Given numbers of bytes left to be read < 0 (%d)!", + "Given numbers of bytes left to be read < 0 (%ld)!", to_be_read); goto error; } @@ -1181,28 +1188,27 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) FALL_THROUGH; case PACKET_STATE_SIZEREAD: packet_len = session->in_packet.len; - processed = lenfield_blocksize + etm_packet_offset; + packet_offset = processed = lenfield_blocksize + etm_packet_offset; to_be_read = packet_len + sizeof(uint32_t) + current_macsize; /* if to_be_read is zero, the whole packet was blocksize bytes. */ if (to_be_read != 0) { - if (receivedlen < (unsigned int)to_be_read) { + if (receivedlen < (unsigned long)to_be_read) { /* give up, not enough data in buffer */ SSH_LOG(SSH_LOG_PACKET, "packet: partial packet (read len) " - "[len=%d, receivedlen=%d, to_be_read=%d]", + "[len=%" PRIu32 ", receivedlen=%zu, to_be_read=%ld]", packet_len, - (int)receivedlen, + receivedlen, to_be_read); return 0; } - packet_second_block = (uint8_t*)data + lenfield_blocksize + etm_packet_offset; + packet_second_block = (uint8_t*)data + packet_offset; processed = to_be_read - current_macsize; } /* remaining encrypted bytes from the packet, MAC not included */ - packet_remaining = - packet_len - (lenfield_blocksize - sizeof(uint32_t) + etm_packet_offset); + packet_remaining = packet_len - (packet_offset - sizeof(uint32_t)); cleartext_packet = ssh_buffer_allocate(session->in_buffer, packet_remaining); if (cleartext_packet == NULL) { @@ -1225,16 +1231,16 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) } } /* - * Decrypt the packet. In case of EtM mode, the length is already - * known as it's unencrypted. In the other case, lenfield_blocksize bytes - * already have been decrypted. + * Decrypt the packet. In case of EtM mode, the length is + * already known as it's unencrypted. In the other case, + * lenfield_blocksize bytes already have been decrypted. */ if (packet_remaining > 0) { rc = ssh_packet_decrypt(session, cleartext_packet, (uint8_t *)data, - lenfield_blocksize + etm_packet_offset, - processed - (lenfield_blocksize + etm_packet_offset)); + packet_offset, + processed - packet_offset); if (rc < 0) { ssh_set_error(session, SSH_FATAL, @@ -1244,9 +1250,10 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) } if (crypto->in_hmac != SSH_HMAC_NONE && !etm) { + ssh_buffer in = session->in_buffer; rc = ssh_packet_hmac_verify(session, - ssh_buffer_get(session->in_buffer), - ssh_buffer_get_len(session->in_buffer), + ssh_buffer_get(in), + ssh_buffer_get_len(in), mac, crypto->in_hmac); if (rc < 0) { @@ -1288,7 +1295,7 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) if (padding > ssh_buffer_get_len(session->in_buffer)) { ssh_set_error(session, SSH_FATAL, - "Invalid padding: %d (%d left)", + "Invalid padding: %d (%" PRIu32 " left)", padding, ssh_buffer_get_len(session->in_buffer)); goto error; @@ -1297,15 +1304,29 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) compsize = ssh_buffer_get_len(session->in_buffer); #ifdef WITH_ZLIB - if (crypto && crypto->do_compress_in - && ssh_buffer_get_len(session->in_buffer) > 0) { - rc = decompress_buffer(session, session->in_buffer,MAX_PACKET_LEN); + if (crypto && crypto->do_compress_in && + ssh_buffer_get_len(session->in_buffer) > 0) { + rc = decompress_buffer(session, session->in_buffer, + MAX_PACKET_LEN); if (rc < 0) { goto error; } } #endif /* WITH_ZLIB */ payloadsize = ssh_buffer_get_len(session->in_buffer); + if (session->recv_seq == UINT32_MAX) { + /* Overflowing sequence numbers is always fishy */ + if (crypto == NULL) { + /* don't allow sequence number overflow when unencrypted */ + ssh_set_error(session, + SSH_FATAL, + "Incoming sequence number overflow"); + goto error; + } else { + SSH_LOG(SSH_LOG_WARNING, + "Incoming sequence number overflow"); + } + } session->recv_seq++; if (crypto != NULL) { struct ssh_cipher_struct *cipher = NULL; @@ -1326,13 +1347,27 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) session->packet_state = PACKET_STATE_PROCESSING; ssh_packet_parse_type(session); SSH_LOG(SSH_LOG_PACKET, - "packet: read type %hhd [len=%d,padding=%hhd,comp=%d,payload=%d]", - session->in_packet.type, packet_len, padding, compsize, payloadsize); + "packet: read type %hhd [len=%" PRIu32 ",padding=%hhd," + "comp=%" PRIu32 ",payload=%" PRIu32 "]", + session->in_packet.type, packet_len, padding, compsize, + payloadsize); + if (crypto == NULL) { + /* In strict kex, only a few packets are allowed. Taint the session + * if we received packets that are normally allowed but to be + * refused if we are in strict kex when KEX is over. + */ + uint8_t type = session->in_packet.type; + if (type != SSH2_MSG_KEXINIT && type != SSH2_MSG_NEWKEYS && + (type < SSH2_MSG_KEXDH_INIT || + type > SSH2_MSG_KEX_DH_GEX_REQUEST)) { + session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; + } + } /* Check if the packet is expected */ filter_result = ssh_packet_incoming_filter(session); - switch(filter_result) { + switch (filter_result) { case SSH_PACKET_ALLOWED: /* Execute callbacks */ ssh_packet_process(session, session->in_packet.type); @@ -1344,6 +1379,9 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) session->in_packet.type); goto error; case SSH_PACKET_UNKNOWN: + if (crypto == NULL) { + session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; + } ssh_packet_send_unimplemented(session, session->recv_seq - 1); break; } @@ -1357,7 +1395,8 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) ptr = ((uint8_t*)data) + processed; - rc = ssh_packet_socket_callback(ptr, receivedlen - processed,user); + rc = ssh_packet_socket_callback(ptr, receivedlen - processed, + user); processed += rc; } @@ -1383,8 +1422,8 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) session->packet_state); error: - session->session_state= SSH_SESSION_STATE_ERROR; - SSH_LOG(SSH_LOG_PACKET,"Packet: processed %zu bytes", processed); + session->session_state = SSH_SESSION_STATE_ERROR; + SSH_LOG(SSH_LOG_PACKET, "Packet: processed %zu bytes", processed); return processed; } @@ -1412,31 +1451,41 @@ static void ssh_packet_socket_controlflow_callback(int code, void *userdata) } } -void ssh_packet_register_socket_callback(ssh_session session, ssh_socket s){ - session->socket_callbacks.data=ssh_packet_socket_callback; - session->socket_callbacks.connected=NULL; - session->socket_callbacks.controlflow = ssh_packet_socket_controlflow_callback; - session->socket_callbacks.userdata=session; - ssh_socket_set_callbacks(s,&session->socket_callbacks); +void ssh_packet_register_socket_callback(ssh_session session, ssh_socket s) +{ + struct ssh_socket_callbacks_struct *callbacks = &session->socket_callbacks; + + callbacks->data = ssh_packet_socket_callback; + callbacks->connected = NULL; + callbacks->controlflow = ssh_packet_socket_controlflow_callback; + callbacks->userdata = session; + ssh_socket_set_callbacks(s, callbacks); } /** @internal * @brief sets the callbacks for the packet layer */ -void ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks){ - if(session->packet_callbacks == NULL){ - session->packet_callbacks = ssh_list_new(); - } - if (session->packet_callbacks != NULL) { +void +ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks) +{ + if (session->packet_callbacks == NULL) { + session->packet_callbacks = ssh_list_new(); + if (session->packet_callbacks == NULL) { + ssh_set_error_oom(session); + return; + } + } ssh_list_append(session->packet_callbacks, callbacks); - } } /** @internal * @brief remove the callbacks from the packet layer */ -void ssh_packet_remove_callbacks(ssh_session session, ssh_packet_callbacks callbacks){ +void +ssh_packet_remove_callbacks(ssh_session session, ssh_packet_callbacks callbacks) +{ struct ssh_iterator *it = NULL; + it = ssh_list_find(session->packet_callbacks, callbacks); if (it != NULL) { ssh_list_remove(session->packet_callbacks, it); @@ -1446,12 +1495,15 @@ void ssh_packet_remove_callbacks(ssh_session session, ssh_packet_callbacks callb /** @internal * @brief sets the default packet handlers */ -void ssh_packet_set_default_callbacks(ssh_session session){ - session->default_packet_callbacks.start=1; - session->default_packet_callbacks.n_callbacks=sizeof(default_packet_handlers)/sizeof(ssh_packet_callback); - session->default_packet_callbacks.user=session; - session->default_packet_callbacks.callbacks=default_packet_handlers; - ssh_packet_set_callbacks(session, &session->default_packet_callbacks); +void ssh_packet_set_default_callbacks(ssh_session session) +{ + struct ssh_packet_callbacks_struct *c = &session->default_packet_callbacks; + + c->start = 1; + c->n_callbacks = sizeof(default_packet_handlers) / sizeof(ssh_packet_callback); + c->user = session; + c->callbacks = default_packet_handlers; + ssh_packet_set_callbacks(session, c); } /** @internal @@ -1505,7 +1557,33 @@ void ssh_packet_process(ssh_session session, uint8_t type) SSH_LOG(SSH_LOG_RARE, "Failed to send unimplemented: %s", ssh_get_error(session)); } + if (session->current_crypto == NULL) { + session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; + } + } +} + +/** @internal + * @brief sends a SSH_MSG_NEWKEYS when enabling the new negotiated ciphers + * @param session the SSH session + * @return SSH_ERROR on error, else SSH_OK + */ +int ssh_packet_send_newkeys(ssh_session session) +{ + int rc; + + /* Send the MSG_NEWKEYS */ + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); + if (rc < 0) { + return rc; } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return rc; + } + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); + return rc; } /** @internal @@ -1543,12 +1621,12 @@ SSH_PACKET_CALLBACK(ssh_packet_unimplemented){ rc = ssh_buffer_unpack(packet, "d", &seq); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_TRACE, "Could not unpack SSH_MSG_UNIMPLEMENTED packet"); } SSH_LOG(SSH_LOG_RARE, - "Received SSH_MSG_UNIMPLEMENTED (sequence number %d)",seq); + "Received SSH_MSG_UNIMPLEMENTED (sequence number %" PRIu32 ")",seq); return SSH_PACKET_USED; } @@ -1714,8 +1792,8 @@ static int packet_send2(ssh_session session) } SSH_LOG(SSH_LOG_PACKET, - "packet: wrote [type=%u, len=%u, padding_size=%hhd, comp=%u, " - "payload=%u]", + "packet: wrote [type=%u, len=%" PRIu32 ", padding_size=%hhd, comp=%" PRIu32 ", " + "payload=%" PRIu32 "]", type, finallen, padding_size, @@ -1755,10 +1833,12 @@ static bool ssh_packet_in_rekey(ssh_session session) { /* We know we are rekeying if we are authenticated and the DH - * status is not finished + * status is not finished, but we only queue packets until we've + * sent our NEWKEYS. */ return (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) && - (session->dh_handshake_state != DH_STATE_FINISHED); + (session->dh_handshake_state != DH_STATE_FINISHED) && + (session->dh_handshake_state != DH_STATE_NEWKEYS_SENT); } int ssh_packet_send(ssh_session session) @@ -1799,7 +1879,7 @@ int ssh_packet_send(ssh_session session) if (need_rekey) { /* Send the KEXINIT packet instead. - * This recursivelly calls the packet_send(), but it should + * This recursively calls the packet_send(), but it should * not get into rekeying again. * After that we need to handle the key exchange responses * up to the point where we can send the rest of the queue. @@ -1816,6 +1896,10 @@ int ssh_packet_send(ssh_session session) if (rc == SSH_OK && type == SSH2_MSG_NEWKEYS) { struct ssh_iterator *it; + if (session->flags & SSH_SESSION_FLAG_KEX_STRICT) { + /* reset packet sequence number when running in strict kex mode */ + session->send_seq = 0; + } for (it = ssh_list_get_iterator(session->out_queue); it != NULL; it = ssh_list_get_iterator(session->out_queue)) { @@ -1867,7 +1951,7 @@ ssh_init_rekey_state(struct ssh_session_struct *session, session->opts.rekey_data / cipher->blocksize); } - SSH_LOG(SSH_LOG_PROTOCOL, + SSH_LOG(SSH_LOG_DEBUG, "Set rekey after %" PRIu64 " blocks", cipher->max_blocks); } @@ -1895,7 +1979,7 @@ ssh_packet_set_newkeys(ssh_session session, session->next_crypto->used |= direction; if (session->current_crypto != NULL) { if (session->current_crypto->used & direction) { - SSH_LOG(SSH_LOG_WARNING, "This direction isn't used anymore."); + SSH_LOG(SSH_LOG_TRACE, "This direction isn't used anymore."); } /* Mark the current requested direction unused */ session->current_crypto->used &= ~direction; @@ -1903,7 +1987,7 @@ ssh_packet_set_newkeys(ssh_session session, /* Both sides switched: do the actual switch now */ if (session->next_crypto->used == SSH_DIRECTION_BOTH) { - size_t digest_len; + size_t session_id_len; if (session->current_crypto != NULL) { crypto_free(session->current_crypto); @@ -1920,8 +2004,8 @@ ssh_packet_set_newkeys(ssh_session session, return SSH_ERROR; } - digest_len = session->current_crypto->digest_len; - session->next_crypto->session_id = malloc(digest_len); + session_id_len = session->current_crypto->session_id_len; + session->next_crypto->session_id = malloc(session_id_len); if (session->next_crypto->session_id == NULL) { ssh_set_error_oom(session); return SSH_ERROR; @@ -1929,7 +2013,8 @@ ssh_packet_set_newkeys(ssh_session session, memcpy(session->next_crypto->session_id, session->current_crypto->session_id, - digest_len); + session_id_len); + session->next_crypto->session_id_len = session_id_len; return SSH_OK; } @@ -1968,7 +2053,7 @@ ssh_packet_set_newkeys(ssh_session session, ssh_init_rekey_state(session, in_cipher); if (session->opts.rekey_time != 0) { ssh_timestamp_init(&session->last_rekey_time); - SSH_LOG(SSH_LOG_PROTOCOL, "Set rekey after %" PRIu32 " seconds", + SSH_LOG(SSH_LOG_DEBUG, "Set rekey after %" PRIu32 " seconds", session->opts.rekey_time/1000); } diff --git a/src/packet_cb.c b/src/packet_cb.c index 4e692915..7edb6791 100644 --- a/src/packet_cb.c +++ b/src/packet_cb.c @@ -45,36 +45,48 @@ * * @brief Handle a SSH_DISCONNECT packet. */ -SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback){ - int rc; - uint32_t code = 0; - char *error = NULL; - ssh_string error_s; - (void)user; - (void)type; - - rc = ssh_buffer_get_u32(packet, &code); - if (rc != 0) { - code = ntohl(code); - } - - error_s = ssh_buffer_get_ssh_string(packet); - if (error_s != NULL) { - error = ssh_string_to_char(error_s); - SSH_STRING_FREE(error_s); - } - SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_DISCONNECT %d:%s", - code, error != NULL ? error : "no error"); - ssh_set_error(session, SSH_FATAL, - "Received SSH_MSG_DISCONNECT: %d:%s", - code, error != NULL ? error : "no error"); - SAFE_FREE(error); - - ssh_socket_close(session->socket); - session->alive = 0; - session->session_state = SSH_SESSION_STATE_ERROR; - /* TODO: handle a graceful disconnect */ - return SSH_PACKET_USED; +SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback) +{ + int rc; + uint32_t code = 0; + char *error = NULL; + ssh_string error_s = NULL; + + (void)user; + (void)type; + + rc = ssh_buffer_get_u32(packet, &code); + if (rc != 0) { + code = ntohl(code); + } + + error_s = ssh_buffer_get_ssh_string(packet); + if (error_s != NULL) { + error = ssh_string_to_char(error_s); + SSH_STRING_FREE(error_s); + } + + if (error != NULL) { + session->peer_discon_msg = strdup(error); + } + + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_MSG_DISCONNECT %" PRIu32 ":%s", + code, + error != NULL ? error : "no error"); + ssh_set_error(session, + SSH_FATAL, + "Received SSH_MSG_DISCONNECT: %" PRIu32 ":%s", + code, + error != NULL ? error : "no error"); + SAFE_FREE(error); + + ssh_session_socket_close(session); + /* correctly handle disconnect during authorization */ + session->auth.state = SSH_AUTH_STATE_FAILED; + + /* TODO: handle a graceful disconnect */ + return SSH_PACKET_USED; } /** @@ -82,96 +94,125 @@ SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback){ * * @brief Handle a SSH_IGNORE and SSH_DEBUG packet. */ -SSH_PACKET_CALLBACK(ssh_packet_ignore_callback){ +SSH_PACKET_CALLBACK(ssh_packet_ignore_callback) +{ (void)session; /* unused */ - (void)user; - (void)type; - (void)packet; - SSH_LOG(SSH_LOG_PROTOCOL,"Received %s packet",type==SSH2_MSG_IGNORE ? "SSH_MSG_IGNORE" : "SSH_MSG_DEBUG"); - /* TODO: handle a graceful disconnect */ - return SSH_PACKET_USED; + (void)user; + (void)type; + (void)packet; + + SSH_LOG(SSH_LOG_DEBUG, + "Received %s packet", + type == SSH2_MSG_IGNORE ? "SSH_MSG_IGNORE" : "SSH_MSG_DEBUG"); + + /* TODO: handle a graceful disconnect */ + return SSH_PACKET_USED; } -SSH_PACKET_CALLBACK(ssh_packet_newkeys){ - ssh_string sig_blob = NULL; - ssh_signature sig = NULL; - int rc; - (void)packet; - (void)user; - (void)type; - SSH_LOG(SSH_LOG_PROTOCOL, "Received SSH_MSG_NEWKEYS"); - - if (session->session_state != SSH_SESSION_STATE_DH || - session->dh_handshake_state != DH_STATE_NEWKEYS_SENT) { - ssh_set_error(session, - SSH_FATAL, - "ssh_packet_newkeys called in wrong state : %d:%d", - session->session_state,session->dh_handshake_state); - goto error; - } - - if(session->server){ - /* server things are done in server.c */ - session->dh_handshake_state=DH_STATE_FINISHED; - } else { - ssh_key server_key; - - /* client */ - - /* Verify the host's signature. FIXME do it sooner */ - sig_blob = session->next_crypto->dh_server_signature; - session->next_crypto->dh_server_signature = NULL; - - /* get the server public key */ - server_key = ssh_dh_get_next_server_publickey(session); - if (server_key == NULL) { - goto error; - } +SSH_PACKET_CALLBACK(ssh_packet_newkeys) +{ + ssh_string sig_blob = NULL; + ssh_signature sig = NULL; + int rc; + + (void)packet; + (void)user; + (void)type; - rc = ssh_pki_import_signature_blob(sig_blob, server_key, &sig); - if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_NEWKEYS"); + + if (session->session_state != SSH_SESSION_STATE_DH || + session->dh_handshake_state != DH_STATE_NEWKEYS_SENT) { + ssh_set_error(session, + SSH_FATAL, + "ssh_packet_newkeys called in wrong state : %d:%d", + session->session_state, + session->dh_handshake_state); goto error; } - /* Check if signature from server matches user preferences */ - if (session->opts.wanted_methods[SSH_HOSTKEYS]) { - if (!ssh_match_group(session->opts.wanted_methods[SSH_HOSTKEYS], - sig->type_c)) { + if (session->flags & SSH_SESSION_FLAG_KEX_STRICT) { + /* reset packet sequence number when running in strict kex mode */ + session->recv_seq = 0; + /* Check that we aren't tainted */ + if (session->flags & SSH_SESSION_FLAG_KEX_TAINTED) { ssh_set_error(session, SSH_FATAL, - "Public key from server (%s) doesn't match user " - "preference (%s)", - sig->type_c, - session->opts.wanted_methods[SSH_HOSTKEYS]); + "Received unexpected packets in strict KEX mode."); goto error; } } - rc = ssh_pki_signature_verify(session, - sig, - server_key, - session->next_crypto->secret_hash, - session->next_crypto->digest_len); - ssh_string_burn(sig_blob); - SSH_STRING_FREE(sig_blob); - ssh_signature_free(sig); - if (rc == SSH_ERROR) { - goto error; - } - SSH_LOG(SSH_LOG_PROTOCOL,"Signature verified and valid"); + if (session->server) { + /* server things are done in server.c */ + session->dh_handshake_state=DH_STATE_FINISHED; + } else { + ssh_key server_key = NULL; - /* When receiving this packet, we switch on the incomming crypto. */ - rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); - if (rc != SSH_OK) { - goto error; + /* client */ + + /* Verify the host's signature. FIXME do it sooner */ + sig_blob = session->next_crypto->dh_server_signature; + session->next_crypto->dh_server_signature = NULL; + + /* get the server public key */ + server_key = ssh_dh_get_next_server_publickey(session); + if (server_key == NULL) { + goto error; + } + + rc = ssh_pki_import_signature_blob(sig_blob, server_key, &sig); + ssh_string_burn(sig_blob); + SSH_STRING_FREE(sig_blob); + if (rc != SSH_OK) { + goto error; + } + + /* Check if signature from server matches user preferences */ + if (session->opts.wanted_methods[SSH_HOSTKEYS]) { + rc = ssh_match_group(session->opts.wanted_methods[SSH_HOSTKEYS], + sig->type_c); + if (rc == 0) { + ssh_set_error(session, + SSH_FATAL, + "Public key from server (%s) doesn't match user " + "preference (%s)", + sig->type_c, + session->opts.wanted_methods[SSH_HOSTKEYS]); + goto error; + } + } + + rc = ssh_pki_signature_verify(session, + sig, + server_key, + session->next_crypto->secret_hash, + session->next_crypto->digest_len); + SSH_SIGNATURE_FREE(sig); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Failed to verify server hostkey signature"); + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Signature verified and valid"); + + /* When receiving this packet, we switch on the incoming crypto. */ + rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); + if (rc != SSH_OK) { + goto error; + } } - } - session->dh_handshake_state = DH_STATE_FINISHED; - session->ssh_connection_callback(session); - return SSH_PACKET_USED; + session->dh_handshake_state = DH_STATE_FINISHED; + session->ssh_connection_callback(session); + return SSH_PACKET_USED; + error: - session->session_state = SSH_SESSION_STATE_ERROR; - return SSH_PACKET_USED; + SSH_SIGNATURE_FREE(sig); + ssh_string_burn(sig_blob); + SSH_STRING_FREE(sig_blob); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; } /** @@ -179,16 +220,16 @@ error: * @brief handles a SSH_SERVICE_ACCEPT packet * */ -SSH_PACKET_CALLBACK(ssh_packet_service_accept){ - (void)packet; - (void)type; - (void)user; +SSH_PACKET_CALLBACK(ssh_packet_service_accept) +{ + (void)packet; + (void)type; + (void)user; session->auth.service_state = SSH_AUTH_SERVICE_ACCEPTED; - SSH_LOG(SSH_LOG_PACKET, - "Received SSH_MSG_SERVICE_ACCEPT"); + SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_SERVICE_ACCEPT"); - return SSH_PACKET_USED; + return SSH_PACKET_USED; } /** @@ -201,6 +242,7 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info) int rc; uint32_t nr_extensions = 0; uint32_t i; + (void)type; (void)user; @@ -218,7 +260,7 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info) return SSH_PACKET_USED; } - SSH_LOG(SSH_LOG_PACKET, "Follows %u extensions", nr_extensions); + SSH_LOG(SSH_LOG_PACKET, "Follows %" PRIu32 " extensions", nr_extensions); for (i = 0; i < nr_extensions; i++) { char *name = NULL; @@ -241,6 +283,8 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info) if (ssh_match_group(value, "rsa-sha2-256")) { session->extensions |= SSH_EXT_SIG_RSA_SHA256; } + } else { + SSH_LOG(SSH_LOG_PACKET, "Unknown extension: %s", name); } free(name); free(value); diff --git a/src/packet_crypt.c b/src/packet_crypt.c index c2f7ab02..fe3f489e 100644 --- a/src/packet_crypt.c +++ b/src/packet_crypt.c @@ -130,14 +130,15 @@ int ssh_packet_decrypt(ssh_session session, return 0; } -unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_t len) +unsigned char *ssh_packet_encrypt(ssh_session session, void *data, size_t len) { struct ssh_crypto_struct *crypto = NULL; struct ssh_cipher_struct *cipher = NULL; HMACCTX ctx = NULL; char *out = NULL; - int etm_packet_offset = 0; - unsigned int finallen, blocksize; + int etm_packet_offset = 0, rc; + unsigned int blocksize; + size_t finallen = DIGEST_MAX_LEN; uint32_t seq, lenfield_blocksize; enum ssh_hmac_e type; bool etm; @@ -161,7 +162,7 @@ unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_t len) if ((len - lenfield_blocksize - etm_packet_offset) % blocksize != 0) { ssh_set_error(session, SSH_FATAL, "Cryptographic functions must be set" - " on at least one blocksize (received %d)", len); + " on at least one blocksize (received %zu)", len); return NULL; } out = calloc(1, len); @@ -185,9 +186,21 @@ unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_t len) } if (!etm) { - hmac_update(ctx, (unsigned char *)&seq, sizeof(uint32_t)); - hmac_update(ctx, data, len); - hmac_final(ctx, crypto->hmacbuf, &finallen); + rc = hmac_update(ctx, (unsigned char *)&seq, sizeof(uint32_t)); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_update(ctx, data, len); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_final(ctx, crypto->hmacbuf, &finallen); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } } } @@ -197,14 +210,26 @@ unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_t len) if (type != SSH_HMAC_NONE) { if (etm) { PUSH_BE_U32(data, 0, len - etm_packet_offset); - hmac_update(ctx, (unsigned char *)&seq, sizeof(uint32_t)); - hmac_update(ctx, data, len); - hmac_final(ctx, crypto->hmacbuf, &finallen); + rc = hmac_update(ctx, (unsigned char *)&seq, sizeof(uint32_t)); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_update(ctx, data, len); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_final(ctx, crypto->hmacbuf, &finallen); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } } #ifdef DEBUG_CRYPTO ssh_log_hexdump("mac: ", data, len); if (finallen != hmac_digest_len(type)) { - printf("Final len is %d\n", finallen); + printf("Final len is %zu\n", finallen); } ssh_log_hexdump("Packet hmac", crypto->hmacbuf, hmac_digest_len(type)); #endif @@ -216,17 +241,6 @@ unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_t len) return crypto->hmacbuf; } -static int secure_memcmp(const void *s1, const void *s2, size_t n) -{ - int rc = 0; - const unsigned char *p1 = s1; - const unsigned char *p2 = s2; - for (; n > 0; --n) { - rc |= *p1++ ^ *p2++; - } - return (rc != 0); -} - /** * @internal * @@ -246,42 +260,71 @@ int ssh_packet_hmac_verify(ssh_session session, uint8_t *mac, enum ssh_hmac_e type) { - struct ssh_crypto_struct *crypto = NULL; - unsigned char hmacbuf[DIGEST_MAX_LEN] = {0}; - HMACCTX ctx; - unsigned int hmaclen; - uint32_t seq; - - /* AEAD types have no mac checking */ - if (type == SSH_HMAC_AEAD_POLY1305 || - type == SSH_HMAC_AEAD_GCM) { - return SSH_OK; - } + struct ssh_crypto_struct *crypto = NULL; + unsigned char hmacbuf[DIGEST_MAX_LEN] = {0}; + HMACCTX ctx; + size_t hmaclen = DIGEST_MAX_LEN; + uint32_t seq; + int cmp; + int rc; - crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); - if (crypto == NULL) { - return SSH_ERROR; - } + /* AEAD types have no mac checking */ + if (type == SSH_HMAC_AEAD_POLY1305 || + type == SSH_HMAC_AEAD_GCM) { + return SSH_OK; + } - ctx = hmac_init(crypto->decryptMAC, hmac_digest_len(type), type); - if (ctx == NULL) { - return -1; - } + crypto = ssh_packet_get_current_crypto(session, + SSH_DIRECTION_IN); + if (crypto == NULL) { + return SSH_ERROR; + } - seq = htonl(session->recv_seq); + ctx = hmac_init(crypto->decryptMAC, + hmac_digest_len(type), + type); + if (ctx == NULL) { + return SSH_ERROR; + } - hmac_update(ctx, (unsigned char *) &seq, sizeof(uint32_t)); - hmac_update(ctx, data, len); - hmac_final(ctx, hmacbuf, &hmaclen); + seq = htonl(session->recv_seq); + + rc = hmac_update(ctx, + (unsigned char *) &seq, + sizeof(uint32_t)); + if (rc != 1) { + return SSH_ERROR; + } + rc = hmac_update(ctx, + data, + len); + if (rc != 1) { + return SSH_ERROR; + } + rc = hmac_final(ctx, + hmacbuf, + &hmaclen); + if (rc != 1) { + return SSH_ERROR; + } #ifdef DEBUG_CRYPTO - ssh_log_hexdump("received mac",mac,hmaclen); - ssh_log_hexdump("Computed mac",hmacbuf,hmaclen); - ssh_log_hexdump("seq",(unsigned char *)&seq,sizeof(uint32_t)); + ssh_log_hexdump("received mac", + mac, + hmaclen); + ssh_log_hexdump("Computed mac", + hmacbuf, + hmaclen); + ssh_log_hexdump("seq", + (unsigned char *)&seq, + sizeof(uint32_t)); #endif - if (secure_memcmp(mac, hmacbuf, hmaclen) == 0) { - return 0; - } + cmp = secure_memcmp(mac, + hmacbuf, + hmaclen); + if (cmp == 0) { + return SSH_OK; + } - return -1; + return SSH_ERROR; } @@ -44,14 +44,11 @@ #include "libssh/socket.h" /** - * @internal - * * @defgroup libssh_pcap The libssh pcap functions * @ingroup libssh * * The pcap file generation * - * * @{ */ @@ -63,7 +60,7 @@ struct pcap_hdr_s { uint32_t magic_number; /* magic number */ uint16_t version_major; /* major version number */ uint16_t version_minor; /* minor version number */ - int32_t thiszone; /* GMT to local correction */ + int32_t thiszone; /* GMT to local correction */ uint32_t sigfigs; /* accuracy of timestamps */ uint32_t snaplen; /* max length of captured packets, in octets */ uint32_t network; /* data link type */ @@ -98,12 +95,11 @@ struct pcaprec_hdr_s { * in a SSH session only. Multiple pcap contexts may be used into * a single pcap file. */ - struct ssh_pcap_context_struct { ssh_session session; ssh_pcap_file file; int connected; - /* All of these information are useful to generate + /* All of this information is useful to generate * the dummy IP and TCP packets */ uint32_t ipsource; @@ -126,10 +122,11 @@ struct ssh_pcap_file_struct { /** * @brief create a new ssh_pcap_file object */ -ssh_pcap_file ssh_pcap_file_new(void) { - struct ssh_pcap_file_struct *pcap; +ssh_pcap_file ssh_pcap_file_new(void) +{ + struct ssh_pcap_file_struct *pcap = NULL; - pcap = (struct ssh_pcap_file_struct *) malloc(sizeof(struct ssh_pcap_file_struct)); + pcap = malloc(sizeof(struct ssh_pcap_file_struct)); if (pcap == NULL) { return NULL; } @@ -141,163 +138,182 @@ ssh_pcap_file ssh_pcap_file_new(void) { /** @internal * @brief writes a packet on file */ -static int ssh_pcap_file_write(ssh_pcap_file pcap, ssh_buffer packet){ - int err; - uint32_t len; - if(pcap == NULL || pcap->output==NULL) - return SSH_ERROR; - len=ssh_buffer_get_len(packet); - err=fwrite(ssh_buffer_get(packet),len,1,pcap->output); - if(err<0) - return SSH_ERROR; - else - return SSH_OK; +static int ssh_pcap_file_write(ssh_pcap_file pcap, ssh_buffer packet) +{ + int err; + uint32_t len; + if (pcap == NULL || pcap->output == NULL) { + return SSH_ERROR; + } + len = ssh_buffer_get_len(packet); + err = fwrite(ssh_buffer_get(packet), len, 1, pcap->output); + if (err < 0) { + return SSH_ERROR; + } else { + return SSH_OK; + } } /** @internal * @brief prepends a packet with the pcap header and writes packet * on file */ -int ssh_pcap_file_write_packet(ssh_pcap_file pcap, ssh_buffer packet, uint32_t original_len){ - ssh_buffer header=ssh_buffer_new(); - struct timeval now; - int err; - if(header == NULL) - return SSH_ERROR; - gettimeofday(&now,NULL); +int ssh_pcap_file_write_packet(ssh_pcap_file pcap, ssh_buffer packet, uint32_t original_len) +{ + ssh_buffer header = ssh_buffer_new(); + struct timeval now; + int err; + + if (header == NULL) { + return SSH_ERROR; + } + + gettimeofday(&now, NULL); err = ssh_buffer_allocate_size(header, sizeof(uint32_t) * 4 + ssh_buffer_get_len(packet)); if (err < 0) { goto error; } - err = ssh_buffer_add_u32(header,htonl(now.tv_sec)); + err = ssh_buffer_add_u32(header, htonl(now.tv_sec)); if (err < 0) { goto error; } - err = ssh_buffer_add_u32(header,htonl(now.tv_usec)); + err = ssh_buffer_add_u32(header, htonl(now.tv_usec)); if (err < 0) { goto error; } - err = ssh_buffer_add_u32(header,htonl(ssh_buffer_get_len(packet))); + err = ssh_buffer_add_u32(header, htonl(ssh_buffer_get_len(packet))); if (err < 0) { goto error; } - err = ssh_buffer_add_u32(header,htonl(original_len)); + err = ssh_buffer_add_u32(header, htonl(original_len)); if (err < 0) { goto error; } - err = ssh_buffer_add_buffer(header,packet); + err = ssh_buffer_add_buffer(header, packet); if (err < 0) { goto error; } - err=ssh_pcap_file_write(pcap,header); + err = ssh_pcap_file_write(pcap, header); error: - SSH_BUFFER_FREE(header); - return err; + SSH_BUFFER_FREE(header); + return err; } /** - * @brief opens a new pcap file and create header + * @brief opens a new pcap file and creates header */ -int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename){ - ssh_buffer header; - int err; - if(pcap == NULL) - return SSH_ERROR; - if(pcap->output){ - fclose(pcap->output); - pcap->output=NULL; - } - pcap->output=fopen(filename,"wb"); - if(pcap->output==NULL) - return SSH_ERROR; - header=ssh_buffer_new(); - if(header==NULL) - return SSH_ERROR; +int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename) +{ + ssh_buffer header = NULL; + int err; + + if (pcap == NULL) { + return SSH_ERROR; + } + if (pcap->output) { + fclose(pcap->output); + pcap->output = NULL; + } + pcap->output = fopen(filename, "wb"); + if (pcap->output == NULL) { + return SSH_ERROR; + } + header = ssh_buffer_new(); + if (header == NULL) { + return SSH_ERROR; + } err = ssh_buffer_allocate_size(header, sizeof(uint32_t) * 5 + sizeof(uint16_t) * 2); if (err < 0) { goto error; } - err = ssh_buffer_add_u32(header,htonl(PCAP_MAGIC)); + err = ssh_buffer_add_u32(header, htonl(PCAP_MAGIC)); if (err < 0) { goto error; } - err = ssh_buffer_add_u16(header,htons(PCAP_VERSION_MAJOR)); + err = ssh_buffer_add_u16(header, htons(PCAP_VERSION_MAJOR)); if (err < 0) { goto error; } - err = ssh_buffer_add_u16(header,htons(PCAP_VERSION_MINOR)); + err = ssh_buffer_add_u16(header, htons(PCAP_VERSION_MINOR)); if (err < 0) { goto error; } - /* currently hardcode GMT to 0 */ - err = ssh_buffer_add_u32(header,htonl(0)); + /* currently hardcode GMT to 0 */ + err = ssh_buffer_add_u32(header, htonl(0)); if (err < 0) { goto error; } - /* accuracy */ - err = ssh_buffer_add_u32(header,htonl(0)); + /* accuracy */ + err = ssh_buffer_add_u32(header, htonl(0)); if (err < 0) { goto error; } - /* size of the biggest packet */ - err = ssh_buffer_add_u32(header,htonl(MAX_PACKET_LEN)); + /* size of the biggest packet */ + err = ssh_buffer_add_u32(header, htonl(MAX_PACKET_LEN)); if (err < 0) { goto error; } - /* we will write sort-of IP */ - err = ssh_buffer_add_u32(header,htonl(DLT_RAW)); + /* we will write sort-of IP */ + err = ssh_buffer_add_u32(header, htonl(DLT_RAW)); if (err < 0) { goto error; } - err=ssh_pcap_file_write(pcap,header); + err = ssh_pcap_file_write(pcap,header); error: - SSH_BUFFER_FREE(header); - return err; + SSH_BUFFER_FREE(header); + return err; } -int ssh_pcap_file_close(ssh_pcap_file pcap){ - int err; - if(pcap ==NULL || pcap->output==NULL) - return SSH_ERROR; - err=fclose(pcap->output); - pcap->output=NULL; - if(err != 0) - return SSH_ERROR; - else - return SSH_OK; +int ssh_pcap_file_close(ssh_pcap_file pcap) +{ + int err; + + if (pcap == NULL || pcap->output == NULL) { + return SSH_ERROR; + } + err = fclose(pcap->output); + pcap->output = NULL; + if (err != 0) { + return SSH_ERROR; + } else { + return SSH_OK; + } } -void ssh_pcap_file_free(ssh_pcap_file pcap){ - ssh_pcap_file_close(pcap); - SAFE_FREE(pcap); +void ssh_pcap_file_free(ssh_pcap_file pcap) +{ + ssh_pcap_file_close(pcap); + SAFE_FREE(pcap); } /** @internal * @brief allocates a new ssh_pcap_context object */ - -ssh_pcap_context ssh_pcap_context_new(ssh_session session){ - ssh_pcap_context ctx = (struct ssh_pcap_context_struct *) malloc(sizeof(struct ssh_pcap_context_struct)); - if(ctx==NULL){ - ssh_set_error_oom(session); - return NULL; - } - ZERO_STRUCTP(ctx); - ctx->session=session; - return ctx; +ssh_pcap_context ssh_pcap_context_new(ssh_session session) +{ + ssh_pcap_context ctx = (struct ssh_pcap_context_struct *)malloc(sizeof(struct ssh_pcap_context_struct)); + if (ctx == NULL) { + ssh_set_error_oom(session); + return NULL; + } + ZERO_STRUCTP(ctx); + ctx->session = session; + return ctx; } -void ssh_pcap_context_free(ssh_pcap_context ctx){ - SAFE_FREE(ctx); +void ssh_pcap_context_free(ssh_pcap_context ctx) +{ + SAFE_FREE(ctx); } -void ssh_pcap_context_set_file(ssh_pcap_context ctx, ssh_pcap_file pcap){ - ctx->file=pcap; +void ssh_pcap_context_set_file(ssh_pcap_context ctx, ssh_pcap_file pcap) +{ + ctx->file = pcap; } /** @internal @@ -315,6 +331,7 @@ static int ssh_pcap_context_connect(ssh_pcap_context ctx) socket_t fd; socklen_t len; int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; if (session == NULL) { return SSH_ERROR; @@ -337,7 +354,7 @@ static int ssh_pcap_context_connect(ssh_pcap_context ctx) ssh_set_error(session, SSH_REQUEST_DENIED, "Getting local IP address: %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } @@ -347,7 +364,7 @@ static int ssh_pcap_context_connect(ssh_pcap_context ctx) ssh_set_error(session, SSH_REQUEST_DENIED, "Getting remote IP address: %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } @@ -383,7 +400,7 @@ static int ssh_pcap_context_connect(ssh_pcap_context ctx) */ int ssh_pcap_context_write(ssh_pcap_context ctx, enum ssh_pcap_direction direction, - void *data, + void *data, uint32_t len, uint32_t origlen) { @@ -417,7 +434,7 @@ int ssh_pcap_context_write(ssh_pcap_context ctx, 0); /* checksum */ ctx->file->ipsequence++; - if (rc != SSH_OK){ + if (rc != SSH_OK) { goto error; } if (direction == SSH_PCAP_DIR_OUT) { @@ -506,23 +523,25 @@ error: /** @brief sets the pcap file used to trace the session * @param current session - * @param pcap an handler to a pcap file. A pcap file may be used in several + * @param pcap a handler to a pcap file. A pcap file may be used in several * sessions. * @returns SSH_ERROR in case of error, SSH_OK otherwise. */ -int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcap){ - ssh_pcap_context ctx=ssh_pcap_context_new(session); - if(ctx==NULL){ - ssh_set_error_oom(session); - return SSH_ERROR; - } - ctx->file=pcap; - if(session->pcap_ctx) - ssh_pcap_context_free(session->pcap_ctx); - session->pcap_ctx=ctx; - return SSH_OK; +int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcap) +{ + ssh_pcap_context ctx = ssh_pcap_context_new(session); + if (ctx == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + ctx->file = pcap; + if (session->pcap_ctx) { + ssh_pcap_context_free(session->pcap_ctx); + } + session->pcap_ctx = ctx; + return SSH_OK; } - +/** @} */ #else /* WITH_PCAP */ @@ -531,30 +550,36 @@ int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcap){ #include "libssh/libssh.h" #include "libssh/priv.h" -int ssh_pcap_file_close(ssh_pcap_file pcap){ - (void) pcap; - return SSH_ERROR; +int ssh_pcap_file_close(ssh_pcap_file pcap) +{ + (void)pcap; + + return SSH_ERROR; } -void ssh_pcap_file_free(ssh_pcap_file pcap){ - (void) pcap; +void ssh_pcap_file_free(ssh_pcap_file pcap) +{ + (void)pcap; } -ssh_pcap_file ssh_pcap_file_new(void){ - return NULL; +ssh_pcap_file ssh_pcap_file_new(void) +{ + return NULL; } -int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename){ - (void) pcap; - (void) filename; - return SSH_ERROR; +int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename) +{ + (void)pcap; + (void)filename; + + return SSH_ERROR; } -int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcapfile){ - (void) pcapfile; - ssh_set_error(session,SSH_REQUEST_DENIED,"Pcap support not compiled in"); - return SSH_ERROR; +int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcapfile) +{ + (void)pcapfile; + + ssh_set_error(session, SSH_REQUEST_DENIED, "Pcap support not compiled in"); + return SSH_ERROR; } #endif - -/** @} */ @@ -43,20 +43,6 @@ #include <sys/stat.h> #include <sys/types.h> -#ifdef _WIN32 -# ifdef HAVE_IO_H -# include <io.h> -# undef open -# define open _open -# undef close -# define close _close -# undef read -# define read _read -# undef unlink -# define unlink _unlink -# endif /* HAVE_IO_H */ -#endif - #include "libssh/libssh.h" #include "libssh/session.h" #include "libssh/priv.h" @@ -67,17 +53,16 @@ #include "libssh/misc.h" #include "libssh/agent.h" +#ifndef MAX_LINE_SIZE +#define MAX_LINE_SIZE 4096 +#endif /* NOT MAX_LINE_SIZE */ + #define PKCS11_URI "pkcs11:" enum ssh_keytypes_e pki_privatekey_type_from_string(const char *privkey) { char *start = NULL; - start = strstr(privkey, DSA_HEADER_BEGIN); - if (start != NULL) { - return SSH_KEYTYPE_DSS; - } - start = strstr(privkey, RSA_HEADER_BEGIN); if (start != NULL) { return SSH_KEYTYPE_RSA; @@ -113,22 +98,31 @@ const char *ssh_pki_key_ecdsa_name(const ssh_key key) return pki_key_ecdsa_nid_to_name(key->ecdsa_nid); #else return NULL; -#endif +#endif /* HAVE_ECC */ } /** * @brief creates a new empty SSH key + * * @returns an empty ssh_key handle, or NULL on error. */ -ssh_key ssh_key_new (void) { - ssh_key ptr = malloc (sizeof (struct ssh_key_struct)); - if (ptr == NULL) { - return NULL; - } - ZERO_STRUCTP(ptr); - return ptr; +ssh_key ssh_key_new (void) +{ + ssh_key ptr = malloc (sizeof (struct ssh_key_struct)); + if (ptr == NULL) { + return NULL; + } + ZERO_STRUCTP(ptr); + return ptr; } +/** + * @brief duplicates the key + * + * @param key An ssh_key to duplicate + * + * @return A duplicated ssh_key key + */ ssh_key ssh_key_dup(const ssh_key key) { if (key == NULL) { @@ -142,39 +136,22 @@ ssh_key ssh_key_dup(const ssh_key key) * @brief clean up the key and deallocate all existing keys * @param[in] key ssh_key to clean */ -void ssh_key_clean (ssh_key key){ - if(key == NULL) +void ssh_key_clean (ssh_key key) +{ + if (key == NULL) return; -#ifdef HAVE_LIBGCRYPT - if(key->dsa) gcry_sexp_release(key->dsa); - if(key->rsa) gcry_sexp_release(key->rsa); - if(key->ecdsa) gcry_sexp_release(key->ecdsa); -#elif defined HAVE_LIBCRYPTO - if(key->dsa) DSA_free(key->dsa); - if(key->rsa) RSA_free(key->rsa); -#ifdef HAVE_OPENSSL_ECC - if(key->ecdsa) EC_KEY_free(key->ecdsa); -#endif /* HAVE_OPENSSL_ECC */ -#elif defined HAVE_LIBMBEDCRYPTO - if (key->rsa != NULL) { - mbedtls_pk_free(key->rsa); - SAFE_FREE(key->rsa); - } - if (key->ecdsa != NULL) { - mbedtls_ecdsa_free(key->ecdsa); - SAFE_FREE(key->ecdsa); - } -#endif + pki_key_clean(key); + if (key->ed25519_privkey != NULL){ -#ifdef HAVE_OPENSSL_ED25519 +#ifdef HAVE_LIBCRYPTO /* In OpenSSL implementation the private key is only the private * original seed. In the internal implementation the private key is the * concatenation of the original private seed with the public key.*/ explicit_bzero(key->ed25519_privkey, ED25519_KEY_LEN); #else explicit_bzero(key->ed25519_privkey, sizeof(ed25519_privkey)); -#endif +#endif /* HAVE_LIBCRYPTO*/ SAFE_FREE(key->ed25519_privkey); } SAFE_FREE(key->ed25519_pubkey); @@ -189,21 +166,19 @@ void ssh_key_clean (ssh_key key){ ssh_string_free(key->sk_application); } key->cert_type = SSH_KEYTYPE_UNKNOWN; - key->flags=SSH_KEY_FLAG_EMPTY; - key->type=SSH_KEYTYPE_UNKNOWN; + key->flags = SSH_KEY_FLAG_EMPTY; + key->type = SSH_KEYTYPE_UNKNOWN; key->ecdsa_nid = 0; - key->type_c=NULL; - key->dsa = NULL; - key->rsa = NULL; - key->ecdsa = NULL; + key->type_c = NULL; } /** * @brief deallocate a SSH key * @param[in] key ssh_key handle to free */ -void ssh_key_free (ssh_key key){ - if(key){ +void ssh_key_free (ssh_key key) +{ + if (key) { ssh_key_clean(key); SAFE_FREE(key); } @@ -212,15 +187,16 @@ void ssh_key_free (ssh_key key){ /** * @brief returns the type of a ssh key * @param[in] key the ssh_key handle - * @returns one of SSH_KEYTYPE_RSA, SSH_KEYTYPE_DSS, + * @returns one of SSH_KEYTYPE_RSA, * SSH_KEYTYPE_ECDSA_P256, SSH_KEYTYPE_ECDSA_P384, - * SSH_KEYTYPE_ECDSA_P521, SSH_KEYTYPE_ED25519, SSH_KEYTYPE_DSS_CERT01, + * SSH_KEYTYPE_ECDSA_P521, SSH_KEYTYPE_ED25519, * SSH_KEYTYPE_RSA_CERT01, SSH_KEYTYPE_ECDSA_P256_CERT01, * SSH_KEYTYPE_ECDSA_P384_CERT01, SSH_KEYTYPE_ECDSA_P521_CERT01, or * SSH_KEYTYPE_ED25519_CERT01. * @returns SSH_KEYTYPE_UNKNOWN if the type is unknown */ -enum ssh_keytypes_e ssh_key_type(const ssh_key key){ +enum ssh_keytypes_e ssh_key_type(const ssh_key key) +{ if (key == NULL) { return SSH_KEYTYPE_UNKNOWN; } @@ -232,6 +208,8 @@ enum ssh_keytypes_e ssh_key_type(const ssh_key key){ * * @param[in] type The algorithm type to convert. * + * @param[in] hash_type The hash type to convert + * * @return A string for the keytype or NULL if unknown. */ const char * @@ -282,8 +260,6 @@ ssh_key_signature_to_char(enum ssh_keytypes_e type, */ const char *ssh_key_type_to_char(enum ssh_keytypes_e type) { switch (type) { - case SSH_KEYTYPE_DSS: - return "ssh-dss"; case SSH_KEYTYPE_RSA: return "ssh-rsa"; case SSH_KEYTYPE_ECDSA: @@ -296,8 +272,6 @@ const char *ssh_key_type_to_char(enum ssh_keytypes_e type) { return "ecdsa-sha2-nistp521"; case SSH_KEYTYPE_ED25519: return "ssh-ed25519"; - case SSH_KEYTYPE_DSS_CERT01: - return "ssh-dss-cert-v01@openssh.com"; case SSH_KEYTYPE_RSA_CERT01: return "ssh-rsa-cert-v01@openssh.com"; case SSH_KEYTYPE_ECDSA_P256_CERT01: @@ -316,7 +290,9 @@ const char *ssh_key_type_to_char(enum ssh_keytypes_e type) { return "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com"; case SSH_KEYTYPE_SK_ED25519_CERT01: return "sk-ssh-ed25519-cert-v01@openssh.com"; + case SSH_KEYTYPE_DSS: /* deprecated */ case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ case SSH_KEYTYPE_UNKNOWN: return NULL; } @@ -334,8 +310,6 @@ enum ssh_digest_e ssh_key_hash_from_name(const char *name) if (strcmp(name, "ssh-rsa") == 0) { return SSH_DIGEST_SHA1; - } else if (strcmp(name, "ssh-dss") == 0) { - return SSH_DIGEST_SHA1; } else if (strcmp(name, "rsa-sha2-256") == 0) { return SSH_DIGEST_SHA256; } else if (strcmp(name, "rsa-sha2-512") == 0) { @@ -354,7 +328,7 @@ enum ssh_digest_e ssh_key_hash_from_name(const char *name) return SSH_DIGEST_AUTO; } - SSH_LOG(SSH_LOG_WARN, "Unknown signature name %s", name); + SSH_LOG(SSH_LOG_TRACE, "Unknown signature name %s", name); /* TODO we should rather fail */ return SSH_DIGEST_AUTO; @@ -386,13 +360,13 @@ int ssh_key_algorithm_allowed(ssh_session session, const char *type) else if (session->server) { allowed_list = session->opts.wanted_methods[SSH_HOSTKEYS]; if (allowed_list == NULL) { - SSH_LOG(SSH_LOG_WARN, "Session invalid: no host key available"); + SSH_LOG(SSH_LOG_TRACE, "Session invalid: no host key available"); return 0; } } -#endif +#endif /* WITH_SERVER */ else { - SSH_LOG(SSH_LOG_WARN, "Session invalid: not set as client nor server"); + SSH_LOG(SSH_LOG_TRACE, "Session invalid: not set as client nor server"); return 0; } @@ -400,6 +374,42 @@ int ssh_key_algorithm_allowed(ssh_session session, const char *type) return ssh_match_group(allowed_list, type); } +bool ssh_key_size_allowed_rsa(int min_size, ssh_key key) +{ + int key_size = ssh_key_size(key); + + if (min_size < 768) { + if (ssh_fips_mode()) { + min_size = 2048; + } else { + min_size = 1024; + } + } + return (key_size >= min_size); +} + +/** + * @brief Check the given key is acceptable in regards to the key size policy + * specified by the configuration + * + * @param[in] session The SSH session + * @param[in] key The SSH key + * @returns true if the key is allowed, false otherwise + */ +bool ssh_key_size_allowed(ssh_session session, ssh_key key) +{ + int min_size = 0; + + switch (ssh_key_type(key)) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + min_size = session->opts.rsa_min_size; + return ssh_key_size_allowed_rsa(min_size, key); + default: + return true; + } +} + /** * @brief Convert a key type to a hash type. This is usually unambiguous * for all the key types, unless the SHA2 extension (RFC 8332) is @@ -415,9 +425,6 @@ enum ssh_digest_e ssh_key_type_to_hash(ssh_session session, enum ssh_keytypes_e type) { switch (type) { - case SSH_KEYTYPE_DSS_CERT01: - case SSH_KEYTYPE_DSS: - return SSH_DIGEST_SHA1; case SSH_KEYTYPE_RSA_CERT01: /* If we are talking to an old OpenSSH version which does not support * SHA2 in certificates */ @@ -459,10 +466,12 @@ enum ssh_digest_e ssh_key_type_to_hash(ssh_session session, case SSH_KEYTYPE_ED25519: return SSH_DIGEST_AUTO; case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ case SSH_KEYTYPE_ECDSA: case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_WARN, "Digest algorithm to be used with key type %u " + SSH_LOG(SSH_LOG_TRACE, "Digest algorithm to be used with key type %u " "is not defined", type); } @@ -533,19 +542,16 @@ enum ssh_keytypes_e ssh_key_type_from_signature_name(const char *name) { * * @return The enum ssh key type. */ -enum ssh_keytypes_e ssh_key_type_from_name(const char *name) { +enum ssh_keytypes_e ssh_key_type_from_name(const char *name) +{ if (name == NULL) { return SSH_KEYTYPE_UNKNOWN; } if (strcmp(name, "rsa") == 0) { return SSH_KEYTYPE_RSA; - } else if (strcmp(name, "dsa") == 0) { - return SSH_KEYTYPE_DSS; } else if (strcmp(name, "ssh-rsa") == 0) { return SSH_KEYTYPE_RSA; - } else if (strcmp(name, "ssh-dss") == 0) { - return SSH_KEYTYPE_DSS; } else if (strcmp(name, "ssh-ecdsa") == 0 || strcmp(name, "ecdsa") == 0 || strcmp(name, "ecdsa-sha2-nistp256") == 0) { @@ -556,8 +562,6 @@ enum ssh_keytypes_e ssh_key_type_from_name(const char *name) { return SSH_KEYTYPE_ECDSA_P521; } else if (strcmp(name, "ssh-ed25519") == 0){ return SSH_KEYTYPE_ED25519; - } else if (strcmp(name, "ssh-dss-cert-v01@openssh.com") == 0) { - return SSH_KEYTYPE_DSS_CERT01; } else if (strcmp(name, "ssh-rsa-cert-v01@openssh.com") == 0) { return SSH_KEYTYPE_RSA_CERT01; } else if (strcmp(name, "ecdsa-sha2-nistp256-cert-v01@openssh.com") == 0) { @@ -582,16 +586,15 @@ enum ssh_keytypes_e ssh_key_type_from_name(const char *name) { } /** - * @brief Get the pubic key type corresponding to a certificate type. + * @brief Get the public key type corresponding to a certificate type. * * @param[in] type The certificate or public key type. * * @return The matching public key type. */ -enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type) { +enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type) +{ switch (type) { - case SSH_KEYTYPE_DSS_CERT01: - return SSH_KEYTYPE_DSS; case SSH_KEYTYPE_RSA_CERT01: return SSH_KEYTYPE_RSA; case SSH_KEYTYPE_ECDSA_P256_CERT01: @@ -618,7 +621,8 @@ enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type) { * * @return 1 if it is a public key, 0 if not. */ -int ssh_key_is_public(const ssh_key k) { +int ssh_key_is_public(const ssh_key k) +{ if (k == NULL) { return 0; } @@ -660,8 +664,8 @@ int ssh_key_cmp(const ssh_key k1, return 1; } - if (k1->type != k2->type) { - SSH_LOG(SSH_LOG_WARN, "key types don't match!"); + if (ssh_key_type_plain(k1->type) != ssh_key_type_plain(k2->type)) { + SSH_LOG(SSH_LOG_DEBUG, "key types don't match!"); return 1; } @@ -681,6 +685,22 @@ int ssh_key_cmp(const ssh_key k1, } } + if (what == SSH_KEY_CMP_CERTIFICATE) { + if (!is_cert_type(k1->type) || + !is_cert_type(k2->type)) { + return 1; + } + if (k1->cert == NULL || k2->cert == NULL) { + return 1; + } + if (ssh_buffer_get_len(k1->cert) != ssh_buffer_get_len(k2->cert)) { + return 1; + } + return memcmp(ssh_buffer_get(k1->cert), + ssh_buffer_get(k2->cert), + ssh_buffer_get_len(k1->cert)); + } + if (k1->type == SSH_KEYTYPE_ED25519 || k1->type == SSH_KEYTYPE_SK_ED25519) { return pki_ed25519_key_cmp(k1, k2, what); @@ -709,17 +729,12 @@ void ssh_signature_free(ssh_signature sig) } switch(sig->type) { - case SSH_KEYTYPE_DSS: -#ifdef HAVE_LIBGCRYPT - gcry_sexp_release(sig->dsa_sig); -#endif - break; case SSH_KEYTYPE_RSA: #ifdef HAVE_LIBGCRYPT gcry_sexp_release(sig->rsa_sig); #elif defined HAVE_LIBMBEDCRYPTO SAFE_FREE(sig->rsa_sig); -#endif +#endif /* HAVE_LIBGCRYPT */ break; case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: @@ -730,16 +745,17 @@ void ssh_signature_free(ssh_signature sig) #elif defined HAVE_LIBMBEDCRYPTO bignum_safe_free(sig->ecdsa_sig.r); bignum_safe_free(sig->ecdsa_sig.s); -#endif +#endif /* HAVE_GCRYPT_ECC */ break; case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: -#ifndef HAVE_OPENSSL_ED25519 +#ifndef HAVE_LIBCRYPTO /* When using OpenSSL, the signature is stored in sig->raw_sig */ SAFE_FREE(sig->ed25519_sig); -#endif +#endif /* HAVE_LIBCRYPTO */ break; - case SSH_KEYTYPE_DSS_CERT01: + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_ECDSA_P384_CERT01: @@ -760,7 +776,7 @@ void ssh_signature_free(ssh_signature sig) } /** - * @brief import a base64 formated key from a memory c-string + * @brief import a base64 formatted key from a memory c-string * * @param[in] b64_key The c-string holding the base64 encoded key * @@ -771,7 +787,7 @@ void ssh_signature_free(ssh_signature sig) * @param[in] auth_data Private data passed to the auth function. * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free() * * @return SSH_ERROR in case of error, SSH_OK otherwise. * @@ -794,7 +810,7 @@ int ssh_pki_import_privkey_base64(const char *b64_key, return SSH_ERROR; } - SSH_LOG(SSH_LOG_INFO, + SSH_LOG(SSH_LOG_DEBUG, "Trying to decode privkey passphrase=%s", passphrase ? "true" : "false"); @@ -820,9 +836,10 @@ int ssh_pki_import_privkey_base64(const char *b64_key, return SSH_OK; } + + /** - * @brief Convert a private key to a pem base64 encoded key, or OpenSSH format for - * keytype ssh-ed25519 + * @brief Convert a private key to a base64 encoded key in given format * * @param[in] privkey The private key to export. * @@ -834,15 +851,21 @@ int ssh_pki_import_privkey_base64(const char *b64_key, * @param[in] auth_data Private data passed to the auth function. * * @param[out] b64_key A pointer to store the allocated base64 encoded key. You - * need to free the buffer. + * need to free the buffer using ssh_string_from_char(). + * + * @param[in] format The file format (OpenSSH, PEM, or default) * * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_string_free_char() */ -int ssh_pki_export_privkey_base64(const ssh_key privkey, - const char *passphrase, - ssh_auth_callback auth_fn, - void *auth_data, - char **b64_key) +int +ssh_pki_export_privkey_base64_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key, + enum ssh_file_format_e format) { ssh_string blob = NULL; char *b64 = NULL; @@ -851,16 +874,32 @@ int ssh_pki_export_privkey_base64(const ssh_key privkey, return SSH_ERROR; } - if (privkey->type == SSH_KEYTYPE_ED25519){ - blob = ssh_pki_openssh_privkey_export(privkey, - passphrase, - auth_fn, - auth_data); - } else { + /* + * For historic reasons, the Ed25519 keys are exported in OpenSSH file + * format by default also when built with OpenSSL. + */ +#ifdef HAVE_LIBCRYPTO + if (format == SSH_FILE_FORMAT_DEFAULT && + privkey->type != SSH_KEYTYPE_ED25519) { + format = SSH_FILE_FORMAT_PEM; + } +#endif /* HAVE_LIBCRYPTO */ + + switch (format) { + case SSH_FILE_FORMAT_PEM: blob = pki_private_key_to_pem(privkey, passphrase, auth_fn, auth_data); + break; + case SSH_FILE_FORMAT_DEFAULT: + /* default except (OpenSSL && !ED25519) handled above */ + case SSH_FILE_FORMAT_OPENSSH: + blob = ssh_pki_openssh_privkey_export(privkey, + passphrase, + auth_fn, + auth_data); + break; } if (blob == NULL) { return SSH_ERROR; @@ -877,6 +916,42 @@ int ssh_pki_export_privkey_base64(const ssh_key privkey, return SSH_OK; } + /** + * @brief Convert a private key to a pem base64 encoded key, or OpenSSH format for + * keytype ssh-ed25519 + * + * @param[in] privkey The private key to export. + * + * @param[in] passphrase The passphrase to use to encrypt the key with or + * NULL. An empty string means no passphrase. + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[out] b64_key A pointer to store the allocated base64 encoded key. You + * need to free the buffer using ssh_string_from_char(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_string_free_char() + */ +int ssh_pki_export_privkey_base64(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key) +{ + return ssh_pki_export_privkey_base64_format(privkey, + passphrase, + auth_fn, + auth_data, + b64_key, + SSH_FILE_FORMAT_DEFAULT); +} + + + /** * @brief Import a private key from a file or a PKCS #11 device. * @@ -891,7 +966,7 @@ int ssh_pki_export_privkey_base64(const ssh_key privkey, * @param[in] auth_data Private data passed to the auth function. * * @param[out] pkey A pointer to store the allocated ssh_key. You need to - * free the key. + * free the key using ssh_key_free(). * * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission * denied, SSH_ERROR otherwise. @@ -908,6 +983,7 @@ int ssh_pki_import_privkey_file(const char *filename, FILE *file; off_t size; int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; if (pkey == NULL || filename == NULL || *filename == '\0') { return SSH_ERROR; @@ -918,24 +994,24 @@ int ssh_pki_import_privkey_file(const char *filename, rc = pki_uri_import(filename, pkey, SSH_KEY_PRIVATE); return rc; } -#endif +#endif /* WITH_PKCS11_URI */ file = fopen(filename, "rb"); if (file == NULL) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Error opening %s: %s", filename, - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_EOF; } rc = fstat(fileno(file), &sb); if (rc < 0) { fclose(file); - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Error getting stat of %s: %s", filename, - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); switch (errno) { case ENOENT: case EACCES: @@ -946,7 +1022,7 @@ int ssh_pki_import_privkey_file(const char *filename, } if (sb.st_size > MAX_PRIVKEY_SIZE) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Private key is bigger than 4M."); fclose(file); return SSH_ERROR; @@ -955,7 +1031,7 @@ int ssh_pki_import_privkey_file(const char *filename, key_buf = malloc(sb.st_size + 1); if (key_buf == NULL) { fclose(file); - SSH_LOG(SSH_LOG_WARN, "Out of memory!"); + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); return SSH_ERROR; } @@ -964,10 +1040,10 @@ int ssh_pki_import_privkey_file(const char *filename, if (size != sb.st_size) { SAFE_FREE(key_buf); - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Error reading %s: %s", filename, - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } key_buf[size] = 0; @@ -983,8 +1059,7 @@ int ssh_pki_import_privkey_file(const char *filename, } /** - * @brief Export a private key to a pem file on disk, or OpenSSH format for - * keytype ssh-ed25519 + * @brief Export a private key to a file in format specified in the argument * * @param[in] privkey The private key to export. * @@ -997,16 +1072,21 @@ int ssh_pki_import_privkey_file(const char *filename, * * @param[in] filename The path where to store the pem file. * + * @param[in] format The file format (OpenSSH, PEM, or default) + * * @return SSH_OK on success, SSH_ERROR on error. */ -int ssh_pki_export_privkey_file(const ssh_key privkey, - const char *passphrase, - ssh_auth_callback auth_fn, - void *auth_data, - const char *filename) + +int +ssh_pki_export_privkey_file_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename, + enum ssh_file_format_e format) { - ssh_string blob; - FILE *fp; + ssh_string blob = NULL; + FILE *fp = NULL; int rc; if (privkey == NULL || !ssh_key_is_private(privkey)) { @@ -1015,21 +1095,38 @@ int ssh_pki_export_privkey_file(const ssh_key privkey, fp = fopen(filename, "wb"); if (fp == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; SSH_LOG(SSH_LOG_FUNCTIONS, "Error opening %s: %s", - filename, strerror(errno)); + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_EOF; } - if (privkey->type == SSH_KEYTYPE_ED25519){ - blob = ssh_pki_openssh_privkey_export(privkey, - passphrase, - auth_fn, - auth_data); - } else { + /* + * For historic reasons, the Ed25519 keys are exported in OpenSSH file + * format by default also when built with OpenSSL. + */ +#ifdef HAVE_LIBCRYPTO + if (format == SSH_FILE_FORMAT_DEFAULT && + privkey->type != SSH_KEYTYPE_ED25519) { + format = SSH_FILE_FORMAT_PEM; + } +#endif /* HAVE_LIBCRYPTO */ + + switch (format) { + case SSH_FILE_FORMAT_PEM: blob = pki_private_key_to_pem(privkey, passphrase, auth_fn, auth_data); + break; + case SSH_FILE_FORMAT_DEFAULT: + /* default except (OpenSSL && !ED25519) handled above */ + case SSH_FILE_FORMAT_OPENSSH: + blob = ssh_pki_openssh_privkey_export(privkey, + passphrase, + auth_fn, + auth_data); + break; } if (blob == NULL) { fclose(fp); @@ -1048,12 +1145,45 @@ int ssh_pki_export_privkey_file(const ssh_key privkey, return SSH_OK; } -/* temporary function to migrate seemlessly to ssh_key */ -ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key) { +/** + * @brief Export a private key to a pem file on disk, or OpenSSH format for + * keytype ssh-ed25519 + * + * @param[in] privkey The private key to export. + * + * @param[in] passphrase The passphrase to use to encrypt the key with or + * NULL. An empty string means no passphrase. + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[in] filename The path where to store the pem file. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int +ssh_pki_export_privkey_file(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename) +{ + return ssh_pki_export_privkey_file_format(privkey, + passphrase, + auth_fn, + auth_data, + filename, + SSH_FILE_FORMAT_DEFAULT); +} + +/* temporary function to migrate seamlessly to ssh_key */ +ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key) +{ ssh_public_key pub; ssh_key tmp; - if(key == NULL) { + if (key == NULL) { return NULL; } @@ -1062,38 +1192,44 @@ ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key) { return NULL; } - pub = malloc(sizeof(struct ssh_public_key_struct)); + pub = calloc(1, sizeof(struct ssh_public_key_struct)); if (pub == NULL) { ssh_key_free(tmp); return NULL; } - ZERO_STRUCTP(pub); pub->type = tmp->type; pub->type_c = tmp->type_c; - pub->dsa_pub = tmp->dsa; - tmp->dsa = NULL; +#ifndef HAVE_LIBCRYPTO pub->rsa_pub = tmp->rsa; tmp->rsa = NULL; +#else + pub->key_pub = tmp->key; + tmp->key = NULL; +#endif /* HAVE_LIBCRYPTO */ ssh_key_free(tmp); return pub; } -ssh_private_key ssh_pki_convert_key_to_privatekey(const ssh_key key) { +ssh_private_key ssh_pki_convert_key_to_privatekey(const ssh_key key) +{ ssh_private_key privkey; - privkey = malloc(sizeof(struct ssh_private_key_struct)); + privkey = calloc(1, sizeof(struct ssh_private_key_struct)); if (privkey == NULL) { ssh_key_free(key); return NULL; } privkey->type = key->type; - privkey->dsa_priv = key->dsa; +#ifndef HAVE_LIBCRYPTO privkey->rsa_priv = key->rsa; +#else + privkey->key_priv = key->key; +#endif /* HAVE_LIBCRYPTO */ return privkey; } @@ -1115,46 +1251,6 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; switch (type) { - case SSH_KEYTYPE_DSS: - { - ssh_string p = NULL; - ssh_string q = NULL; - ssh_string g = NULL; - ssh_string pubkey = NULL; - ssh_string privkey = NULL; - - rc = ssh_buffer_unpack(buffer, "SSSSS", &p, &q, &g, - &pubkey, &privkey); - if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Unpack error"); - goto fail; - } - - rc = pki_privkey_build_dss(key, p, q, g, pubkey, privkey); -#ifdef DEBUG_CRYPTO - ssh_log_hexdump("p", ssh_string_data(p), ssh_string_len(p)); - ssh_log_hexdump("q", ssh_string_data(q), ssh_string_len(q)); - ssh_log_hexdump("g", ssh_string_data(g), ssh_string_len(g)); - ssh_log_hexdump("pubkey", ssh_string_data(pubkey), - ssh_string_len(pubkey)); - ssh_log_hexdump("privkey", ssh_string_data(privkey), - ssh_string_len(privkey)); -#endif - ssh_string_burn(p); - SSH_STRING_FREE(p); - ssh_string_burn(q); - SSH_STRING_FREE(q); - ssh_string_burn(g); - SSH_STRING_FREE(g); - ssh_string_burn(pubkey); - SSH_STRING_FREE(pubkey); - ssh_string_burn(privkey); - SSH_STRING_FREE(privkey); - if (rc == SSH_ERROR) { - goto fail; - } - } - break; case SSH_KEYTYPE_RSA: { ssh_string n = NULL; @@ -1167,7 +1263,7 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, rc = ssh_buffer_unpack(buffer, "SSSSSS", &n, &e, &d, &iqmp, &p, &q); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Unpack error"); + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); goto fail; } @@ -1176,11 +1272,12 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, ssh_log_hexdump("n", ssh_string_data(n), ssh_string_len(n)); ssh_log_hexdump("e", ssh_string_data(e), ssh_string_len(e)); ssh_log_hexdump("d", ssh_string_data(d), ssh_string_len(d)); - ssh_log_hexdump("iqmp", ssh_string_data(iqmp), - ssh_string_len(iqmp)); + ssh_log_hexdump("iqmp", + ssh_string_data(iqmp), + ssh_string_len(iqmp)); ssh_log_hexdump("p", ssh_string_data(p), ssh_string_len(p)); ssh_log_hexdump("q", ssh_string_data(q), ssh_string_len(q)); -#endif +#endif /* DEBUG_CRYPTO */ ssh_string_burn(n); SSH_STRING_FREE(n); ssh_string_burn(e); @@ -1194,7 +1291,7 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, ssh_string_burn(q); SSH_STRING_FREE(q); if (rc == SSH_ERROR) { - SSH_LOG(SSH_LOG_WARN, "Failed to build RSA private key"); + SSH_LOG(SSH_LOG_TRACE, "Failed to build RSA private key"); goto fail; } } @@ -1211,7 +1308,7 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, rc = ssh_buffer_unpack(buffer, "SSS", &i, &e, &exp); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Unpack error"); + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); goto fail; } @@ -1231,19 +1328,19 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, ssh_string_burn(exp); SSH_STRING_FREE(exp); if (rc < 0) { - SSH_LOG(SSH_LOG_WARN, "Failed to build ECDSA private key"); + SSH_LOG(SSH_LOG_TRACE, "Failed to build ECDSA private key"); goto fail; } } break; -#endif +#endif /* HAVE_ECC */ case SSH_KEYTYPE_ED25519: { ssh_string pubkey = NULL, privkey = NULL; rc = ssh_buffer_unpack(buffer, "SS", &pubkey, &privkey); if (rc != SSH_OK){ - SSH_LOG(SSH_LOG_WARN, "Unpack error"); + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); goto fail; } @@ -1252,12 +1349,11 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, SSH_STRING_FREE(privkey); SSH_STRING_FREE(pubkey); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Failed to build ed25519 key"); + SSH_LOG(SSH_LOG_TRACE, "Failed to build ed25519 key"); goto fail; } } break; - case SSH_KEYTYPE_DSS_CERT01: case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_ECDSA_P384_CERT01: @@ -1270,7 +1366,7 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_WARN, "Unknown private key type (%d)", type); + SSH_LOG(SSH_LOG_TRACE, "Unknown private key type (%d)", type); goto fail; } @@ -1284,7 +1380,8 @@ fail: static int pki_import_pubkey_buffer(ssh_buffer buffer, enum ssh_keytypes_e type, - ssh_key *pkey) { + ssh_key *pkey) +{ ssh_key key = NULL; int rc; @@ -1298,39 +1395,6 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, key->flags = SSH_KEY_FLAG_PUBLIC; switch (type) { - case SSH_KEYTYPE_DSS: - { - ssh_string p = NULL; - ssh_string q = NULL; - ssh_string g = NULL; - ssh_string pubkey = NULL; - - rc = ssh_buffer_unpack(buffer, "SSSS", &p, &q, &g, &pubkey); - if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Unpack error"); - goto fail; - } - - rc = pki_pubkey_build_dss(key, p, q, g, pubkey); -#ifdef DEBUG_CRYPTO - ssh_log_hexdump("p", ssh_string_data(p), ssh_string_len(p)); - ssh_log_hexdump("q", ssh_string_data(q), ssh_string_len(q)); - ssh_log_hexdump("g", ssh_string_data(g), ssh_string_len(g)); -#endif - ssh_string_burn(p); - SSH_STRING_FREE(p); - ssh_string_burn(q); - SSH_STRING_FREE(q); - ssh_string_burn(g); - SSH_STRING_FREE(g); - ssh_string_burn(pubkey); - SSH_STRING_FREE(pubkey); - if (rc == SSH_ERROR) { - SSH_LOG(SSH_LOG_WARN, "Failed to build DSA public key"); - goto fail; - } - } - break; case SSH_KEYTYPE_RSA: { ssh_string e = NULL; @@ -1338,7 +1402,7 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, rc = ssh_buffer_unpack(buffer, "SS", &e, &n); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Unpack error"); + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); goto fail; } @@ -1346,13 +1410,13 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, #ifdef DEBUG_CRYPTO ssh_log_hexdump("e", ssh_string_data(e), ssh_string_len(e)); ssh_log_hexdump("n", ssh_string_data(n), ssh_string_len(n)); -#endif +#endif /* DEBUG_CRYPTO */ ssh_string_burn(e); SSH_STRING_FREE(e); ssh_string_burn(n); SSH_STRING_FREE(n); if (rc == SSH_ERROR) { - SSH_LOG(SSH_LOG_WARN, "Failed to build RSA public key"); + SSH_LOG(SSH_LOG_TRACE, "Failed to build RSA public key"); goto fail; } } @@ -1370,7 +1434,7 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, rc = ssh_buffer_unpack(buffer, "SS", &i, &e); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Unpack error"); + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); goto fail; } @@ -1386,7 +1450,7 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, ssh_string_burn(e); SSH_STRING_FREE(e); if (rc < 0) { - SSH_LOG(SSH_LOG_WARN, "Failed to build ECDSA public key"); + SSH_LOG(SSH_LOG_TRACE, "Failed to build ECDSA public key"); goto fail; } @@ -1399,7 +1463,7 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, if (type == SSH_KEYTYPE_SK_ECDSA) { ssh_string application = ssh_buffer_get_ssh_string(buffer); if (application == NULL) { - SSH_LOG(SSH_LOG_WARN, "SK Unpack error"); + SSH_LOG(SSH_LOG_TRACE, "SK Unpack error"); goto fail; } key->sk_application = application; @@ -1407,14 +1471,14 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, } } break; -#endif +#endif /* HAVE_ECC */ case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: { ssh_string pubkey = ssh_buffer_get_ssh_string(buffer); if (ssh_string_len(pubkey) != ED25519_KEY_LEN) { - SSH_LOG(SSH_LOG_WARN, "Invalid public key length"); + SSH_LOG(SSH_LOG_TRACE, "Invalid public key length"); ssh_string_burn(pubkey); SSH_STRING_FREE(pubkey); goto fail; @@ -1434,14 +1498,13 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, if (type == SSH_KEYTYPE_SK_ED25519) { ssh_string application = ssh_buffer_get_ssh_string(buffer); if (application == NULL) { - SSH_LOG(SSH_LOG_WARN, "SK Unpack error"); + SSH_LOG(SSH_LOG_TRACE, "SK Unpack error"); goto fail; } key->sk_application = application; } } break; - case SSH_KEYTYPE_DSS_CERT01: case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_ECDSA_P384_CERT01: @@ -1452,7 +1515,7 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_WARN, "Unknown public key protocol %d", type); + SSH_LOG(SSH_LOG_TRACE, "Unknown public key protocol %d", type); goto fail; } @@ -1466,7 +1529,8 @@ fail: static int pki_import_cert_buffer(ssh_buffer buffer, enum ssh_keytypes_e type, - ssh_key *pkey) { + ssh_key *pkey) +{ ssh_buffer cert; ssh_string tmp_s; const char *type_c; @@ -1508,9 +1572,6 @@ static int pki_import_cert_buffer(ssh_buffer buffer, SSH_STRING_FREE(tmp_s); switch (type) { - case SSH_KEYTYPE_DSS_CERT01: - rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_DSS, &key); - break; case SSH_KEYTYPE_RSA_CERT01: rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_RSA, &key); break; @@ -1541,7 +1602,7 @@ static int pki_import_cert_buffer(ssh_buffer buffer, key->type = type; key->type_c = type_c; - key->cert = (void*) cert; + key->cert = cert; *pkey = key; return SSH_OK; @@ -1553,14 +1614,14 @@ fail: } /** - * @brief Import a base64 formated public key from a memory c-string. + * @brief Import a base64 formatted public key from a memory c-string. * * @param[in] b64_key The base64 key to format. * * @param[in] type The type of the key to format. * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * @@ -1568,7 +1629,8 @@ fail: */ int ssh_pki_import_pubkey_base64(const char *b64_key, enum ssh_keytypes_e type, - ssh_key *pkey) { + ssh_key *pkey) +{ ssh_buffer buffer = NULL; ssh_string type_s = NULL; int rc; @@ -1608,14 +1670,15 @@ int ssh_pki_import_pubkey_base64(const char *b64_key, * 6.6 "Public Key Algorithms". * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * * @see ssh_key_free() */ int ssh_pki_import_pubkey_blob(const ssh_string key_blob, - ssh_key *pkey) { + ssh_key *pkey) +{ ssh_buffer buffer = NULL; ssh_string type_s = NULL; enum ssh_keytypes_e type; @@ -1627,26 +1690,26 @@ int ssh_pki_import_pubkey_blob(const ssh_string key_blob, buffer = ssh_buffer_new(); if (buffer == NULL) { - SSH_LOG(SSH_LOG_WARN, "Out of memory!"); + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); return SSH_ERROR; } rc = ssh_buffer_add_data(buffer, ssh_string_data(key_blob), ssh_string_len(key_blob)); if (rc < 0) { - SSH_LOG(SSH_LOG_WARN, "Out of memory!"); + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); goto fail; } type_s = ssh_buffer_get_ssh_string(buffer); if (type_s == NULL) { - SSH_LOG(SSH_LOG_WARN, "Out of memory!"); + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); goto fail; } type = ssh_key_type_from_name(ssh_string_get_char(type_s)); if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "Unknown key type found!"); + SSH_LOG(SSH_LOG_TRACE, "Unknown key type found!"); goto fail; } SSH_STRING_FREE(type_s); @@ -1667,6 +1730,7 @@ fail: return SSH_ERROR; } +#ifdef WITH_PKCS11_URI /** *@brief Detect if the pathname in cmp is a PKCS #11 URI. * @@ -1695,7 +1759,10 @@ bool ssh_pki_is_uri(const char *cmp) * * @param[in] priv_uri Private PKCS #11 URI. * - * @returns pointer to the public PKCS #11 URI + * @returns pointer to the public PKCS #11 URI. You need to free + * the memory using ssh_string_free_char(). + * + * @see ssh_string_free_char(). */ char *ssh_pki_export_pub_uri_from_priv_uri(const char *priv_uri) { @@ -1707,6 +1774,7 @@ char *ssh_pki_export_pub_uri_from_priv_uri(const char *priv_uri) return pub_uri_temp; } +#endif /* WITH_PKCS11_URI */ /** * @brief Import a public key from a file or a PKCS #11 device. @@ -1715,7 +1783,7 @@ char *ssh_pki_export_pub_uri_from_priv_uri(const char *priv_uri) * PKCS #11 URI corresponding to the public key. * * @param[out] pkey A pointer to store the allocated public key. You need to - * free the memory. + * free the memory using ssh_key_free(). * * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission * denied, SSH_ERROR otherwise. @@ -1726,12 +1794,14 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) { enum ssh_keytypes_e type; struct stat sb; - char *key_buf, *p; + char *key_buf = NULL, *p = NULL; size_t buflen, i; - const char *q; - FILE *file; + const char *q = NULL; + FILE *file = NULL; off_t size; int rc, cmp; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_key priv_key = NULL; if (pkey == NULL || filename == NULL || *filename == '\0') { return SSH_ERROR; @@ -1742,20 +1812,20 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) rc = pki_uri_import(filename, pkey, SSH_KEY_PUBLIC); return rc; } -#endif +#endif /* WITH_PKCS11_URI */ file = fopen(filename, "rb"); if (file == NULL) { - SSH_LOG(SSH_LOG_WARN, "Error opening %s: %s", - filename, strerror(errno)); + SSH_LOG(SSH_LOG_TRACE, "Error opening %s: %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_EOF; } rc = fstat(fileno(file), &sb); if (rc < 0) { fclose(file); - SSH_LOG(SSH_LOG_WARN, "Error gettint stat of %s: %s", - filename, strerror(errno)); + SSH_LOG(SSH_LOG_TRACE, "Error gettint stat of %s: %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); switch (errno) { case ENOENT: case EACCES: @@ -1772,7 +1842,7 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) key_buf = malloc(sb.st_size + 1); if (key_buf == NULL) { fclose(file); - SSH_LOG(SSH_LOG_WARN, "Out of memory!"); + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); return SSH_ERROR; } @@ -1781,8 +1851,8 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) if (size != sb.st_size) { SAFE_FREE(key_buf); - SSH_LOG(SSH_LOG_WARN, "Error reading %s: %s", - filename, strerror(errno)); + SSH_LOG(SSH_LOG_TRACE, "Error reading %s: %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } key_buf[size] = '\0'; @@ -1794,7 +1864,24 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) *pkey = ssh_pki_openssh_pubkey_import(key_buf); SAFE_FREE(key_buf); if (*pkey == NULL) { - SSH_LOG(SSH_LOG_WARN, "Failed to import public key from OpenSSH" + SSH_LOG(SSH_LOG_TRACE, "Failed to import public key from OpenSSH" + " private key file"); + return SSH_ERROR; + } + return SSH_OK; + } + + /* + * Try to parse key as PEM. Set empty passphrase, so user won't be prompted + * for passphrase. Don't try to decrypt encrypted private key. + */ + priv_key = pki_private_key_from_base64(key_buf, "", NULL, NULL); + if (priv_key) { + rc = ssh_pki_export_privkey_to_pubkey(priv_key, pkey); + ssh_key_free(priv_key); + SAFE_FREE(key_buf); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to import public key from PEM" " private key file"); return SSH_ERROR; } @@ -1816,6 +1903,10 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) return SSH_ERROR; } + if (i >= buflen) { + SAFE_FREE(key_buf); + return SSH_ERROR; + } q = &p[i + 1]; for (; i < buflen; i++) { if (isspace((int)p[i])) { @@ -1831,14 +1922,14 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) } /** - * @brief Import a base64 formated certificate from a memory c-string. + * @brief Import a base64 formatted certificate from a memory c-string. * * @param[in] b64_cert The base64 cert to format. * * @param[in] type The type of the cert to format. * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * @@ -1846,7 +1937,8 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) */ int ssh_pki_import_cert_base64(const char *b64_cert, enum ssh_keytypes_e type, - ssh_key *pkey) { + ssh_key *pkey) +{ return ssh_pki_import_pubkey_base64(b64_cert, type, pkey); } @@ -1859,14 +1951,15 @@ int ssh_pki_import_cert_base64(const char *b64_cert, * 6.6 "Public Key Algorithms". * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * * @see ssh_key_free() */ int ssh_pki_import_cert_blob(const ssh_string cert_blob, - ssh_key *pkey) { + ssh_key *pkey) +{ return ssh_pki_import_pubkey_blob(cert_blob, pkey); } @@ -1876,7 +1969,7 @@ int ssh_pki_import_cert_blob(const ssh_string cert_blob, * @param[in] filename The path to the certificate. * * @param[out] pkey A pointer to store the allocated certificate. You need to - * free the memory. + * free the memory using ssh_key_free(). * * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission * denied, SSH_ERROR otherwise. @@ -1885,26 +1978,39 @@ int ssh_pki_import_cert_blob(const ssh_string cert_blob, */ int ssh_pki_import_cert_file(const char *filename, ssh_key *pkey) { - return ssh_pki_import_pubkey_file(filename, pkey); + int rc; + + rc = ssh_pki_import_pubkey_file(filename, pkey); + if (rc == SSH_OK) { + /* check the key is a cert type. */ + if (!is_cert_type((*pkey)->type)) { + SSH_KEY_FREE(*pkey); + return SSH_ERROR; + } + } + + return rc; } /** - * @brief Generates a keypair. + * @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) - * dsa : length of the key in bits (e.g. 1024, 2048, 3072) * @param[out] pkey A pointer to store the allocated private key. You need - * to free the memory. + * 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){ + ssh_key *pkey) +{ int rc; ssh_key key = ssh_key_new(); @@ -1922,11 +2028,6 @@ int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, if(rc == SSH_ERROR) goto error; break; - case SSH_KEYTYPE_DSS: - rc = pki_key_generate_dss(key, parameter); - if(rc == SSH_ERROR) - goto error; - break; #ifdef HAVE_ECC case SSH_KEYTYPE_ECDSA: /* deprecated */ rc = pki_key_generate_ecdsa(key, parameter); @@ -1955,14 +2056,13 @@ int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, goto error; } break; -#endif +#endif /* HAVE_ECC */ case SSH_KEYTYPE_ED25519: rc = pki_key_generate_ed25519(key); if (rc == SSH_ERROR) { goto error; } break; - case SSH_KEYTYPE_DSS_CERT01: case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_ECDSA_P384_CERT01: @@ -1991,7 +2091,7 @@ error: * @param[in] privkey The private key to get the public key from. * * @param[out] pkey A pointer to store the newly allocated public key. You - * NEED to free the key. + * NEED to free the key using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * @@ -2029,11 +2129,11 @@ int ssh_pki_export_privkey_to_pubkey(const ssh_key privkey, * from. * * @param[out] pblob A pointer to store the newly allocated key blob. You - * NEED to free it. + * need to free it using ssh_string_free(). * * @return SSH_OK on success, SSH_ERROR otherwise. * - * @see SSH_STRING_FREE() + * @see ssh_string_free() */ int ssh_pki_export_pubkey_blob(const ssh_key key, ssh_string *pblob) @@ -2044,7 +2144,42 @@ int ssh_pki_export_pubkey_blob(const ssh_key key, return SSH_OK; } - blob = pki_publickey_to_blob(key); + blob = pki_key_to_blob(key, SSH_KEY_PUBLIC); + if (blob == NULL) { + return SSH_ERROR; + } + + *pblob = blob; + return SSH_OK; +} + +/** + * @internal + * + * @brief Create a key_blob from a private key. + * + * The "key_blob" is encoded as per draft-miller-ssh-agent-08 section 4.2 + * "Adding keys to the agent" for any of the supported key types. + * + * @param[in] key A private key to create the private ssh_string from. + * + * @param[out] pblob A pointer to store the newly allocated key blob. You + * need to free it using ssh_string_free(). + * + * @return SSH_OK on success, SSH_ERROR otherwise. + * + * @see ssh_string_free() + */ +int ssh_pki_export_privkey_blob(const ssh_key key, + ssh_string *pblob) +{ + ssh_string blob; + + if (key == NULL) { + return SSH_OK; + } + + blob = pki_key_to_blob(key, SSH_KEY_PRIVATE); if (blob == NULL) { return SSH_ERROR; } @@ -2059,11 +2194,11 @@ int ssh_pki_export_pubkey_blob(const ssh_key key, * @param[in] key The key to hash * * @param[out] b64_key A pointer to store the allocated base64 encoded key. You - * need to free the buffer. + * need to free the buffer using ssh_string_free_char() * * @return SSH_OK on success, SSH_ERROR on error. * - * @see SSH_STRING_FREE_CHAR() + * @see ssh_string_free_char() */ int ssh_pki_export_pubkey_base64(const ssh_key key, char **b64_key) @@ -2075,7 +2210,7 @@ int ssh_pki_export_pubkey_base64(const ssh_key key, return SSH_ERROR; } - key_blob = pki_publickey_to_blob(key); + key_blob = pki_key_to_blob(key, SSH_KEY_PUBLIC); if (key_blob == NULL) { return SSH_ERROR; } @@ -2091,10 +2226,22 @@ int ssh_pki_export_pubkey_base64(const ssh_key key, return SSH_OK; } +/** + * @brief Export public key to file + * + * Exports the public key in AuthorizedKeysFile acceptable format. + * For more information see `man sshd` + * + * @param key A key to export + * + * @param filename The name of the output file + * + * @returns SSH_OK on success, SSH_ERROR otherwise. + */ int ssh_pki_export_pubkey_file(const ssh_key key, const char *filename) { - char key_buf[4096]; + char key_buf[MAX_LINE_SIZE]; char host[256]; char *b64_key; char *user; @@ -2160,7 +2307,7 @@ int ssh_pki_export_pubkey_file(const ssh_key key, **/ int ssh_pki_copy_cert_to_privkey(const ssh_key certkey, ssh_key privkey) { ssh_buffer cert_buffer; - int rc; + int rc, cmp; if (certkey == NULL || privkey == NULL) { return SSH_ERROR; @@ -2174,6 +2321,12 @@ int ssh_pki_copy_cert_to_privkey(const ssh_key certkey, ssh_key privkey) { return SSH_ERROR; } + /* make sure the public keys match */ + cmp = ssh_key_cmp(certkey, privkey, SSH_KEY_CMP_PUBLIC); + if (cmp != 0) { + return SSH_ERROR; + } + cert_buffer = ssh_buffer_new(); if (cert_buffer == NULL) { return SSH_ERROR; @@ -2238,8 +2391,12 @@ int ssh_pki_export_signature_blob(const ssh_signature sig, return SSH_ERROR; } - ssh_string_fill(str, ssh_buffer_get(buf), ssh_buffer_get_len(buf)); + rc = ssh_string_fill(str, ssh_buffer_get(buf), ssh_buffer_get_len(buf)); SSH_BUFFER_FREE(buf); + if (rc < 0) { + SSH_STRING_FREE(str); + return SSH_ERROR; + } *sig_blob = str; @@ -2340,22 +2497,11 @@ int pki_key_check_hash_compatible(ssh_key key, } switch(key->type) { - case SSH_KEYTYPE_DSS_CERT01: - case SSH_KEYTYPE_DSS: - if (hash_type == SSH_DIGEST_SHA1) { - if (ssh_fips_mode()) { - SSH_LOG(SSH_LOG_WARN, "SHA1 is not allowed in FIPS mode"); - return SSH_ERROR; - } else { - return SSH_OK; - } - } - break; case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_RSA: if (hash_type == SSH_DIGEST_SHA1) { if (ssh_fips_mode()) { - SSH_LOG(SSH_LOG_WARN, "SHA1 is not allowed in FIPS mode"); + SSH_LOG(SSH_LOG_TRACE, "SHA1 is not allowed in FIPS mode"); return SSH_ERROR; } else { return SSH_OK; @@ -2396,14 +2542,16 @@ int pki_key_check_hash_compatible(ssh_key key, return SSH_OK; } break; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_ECDSA: case SSH_KEYTYPE_UNKNOWN: - SSH_LOG(SSH_LOG_WARN, "Unknown key type %d", key->type); + SSH_LOG(SSH_LOG_TRACE, "Unknown key type %d", key->type); return SSH_ERROR; } - SSH_LOG(SSH_LOG_WARN, "Key type %d incompatible with hash type %d", + SSH_LOG(SSH_LOG_TRACE, "Key type %d incompatible with hash type %d", key->type, hash_type); return SSH_ERROR; @@ -2416,6 +2564,7 @@ int ssh_pki_signature_verify(ssh_session session, size_t input_len) { int rc; + bool allowed; enum ssh_keytypes_e key_type; if (session == NULL || sig == NULL || key == NULL || input == NULL) { @@ -2430,12 +2579,19 @@ int ssh_pki_signature_verify(ssh_session session, sig->type_c); if (key_type != sig->type) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Can not verify %s signature with %s key", sig->type_c, key->type_c); return SSH_ERROR; } + allowed = ssh_key_size_allowed(session, key); + if (!allowed) { + ssh_set_error(session, SSH_FATAL, "The '%s' key of size %d is not " + "allowed by RSA_MIN_SIZE", key->type_c, ssh_key_size(key)); + return SSH_ERROR; + } + /* Check if public key and hash type are compatible */ rc = pki_key_check_hash_compatible(key, sig->hash_type); if (rc != SSH_OK) { @@ -2454,7 +2610,7 @@ int ssh_pki_signature_verify(ssh_session session, ctx = sha256_init(); if (ctx == NULL) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Can not create SHA256CTX for application hash"); return SSH_ERROR; } @@ -2464,7 +2620,7 @@ int ssh_pki_signature_verify(ssh_session session, ctx = sha256_init(); if (ctx == NULL) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Can not create SHA256CTX for input hash"); return SSH_ERROR; } @@ -2554,11 +2710,14 @@ ssh_string ssh_pki_do_sign(ssh_session session, } /* Get the session ID */ - session_id = ssh_string_new(crypto->digest_len); + session_id = ssh_string_new(crypto->session_id_len); if (session_id == NULL) { return NULL; } - ssh_string_fill(session_id, crypto->session_id, crypto->digest_len); + rc = ssh_string_fill(session_id, crypto->session_id, crypto->session_id_len); + if (rc < 0) { + goto end; + } /* Fill the input */ sign_input = ssh_buffer_new(); @@ -2598,7 +2757,6 @@ end: return sig_blob; } -#ifndef _WIN32 ssh_string ssh_pki_do_sign_agent(ssh_session session, struct ssh_buffer_struct *buf, const ssh_key pubkey) @@ -2615,11 +2773,15 @@ ssh_string ssh_pki_do_sign_agent(ssh_session session, } /* prepend session identifier */ - session_id = ssh_string_new(crypto->digest_len); + session_id = ssh_string_new(crypto->session_id_len); if (session_id == NULL) { return NULL; } - ssh_string_fill(session_id, crypto->session_id, crypto->digest_len); + rc = ssh_string_fill(session_id, crypto->session_id, crypto->session_id_len); + if (rc < 0) { + SSH_STRING_FREE(session_id); + return NULL; + } sig_buf = ssh_buffer_new(); if (sig_buf == NULL) { @@ -2648,7 +2810,6 @@ ssh_string ssh_pki_do_sign_agent(ssh_session session, return sig_blob; } -#endif /* _WIN32 */ #ifdef WITH_SERVER ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, @@ -2656,7 +2817,7 @@ ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, const enum ssh_digest_e digest) { struct ssh_crypto_struct *crypto = NULL; - + bool allowed; ssh_signature sig = NULL; ssh_string sig_blob = NULL; @@ -2668,11 +2829,17 @@ ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, return NULL; } + allowed = ssh_key_size_allowed(session, privkey); + if (!allowed) { + ssh_set_error(session, SSH_FATAL, "The hostkey size too small"); + return NULL; + } + crypto = session->next_crypto ? session->next_crypto : session->current_crypto; if (crypto->secret_hash == NULL){ - ssh_set_error(session,SSH_FATAL,"Missing secret_hash"); + ssh_set_error(session, SSH_FATAL, "Missing secret_hash"); return NULL; } @@ -2693,9 +2860,9 @@ ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, /* Generate the signature */ sig = pki_do_sign(privkey, - ssh_buffer_get(sign_input), - ssh_buffer_get_len(sign_input), - digest); + ssh_buffer_get(sign_input), + ssh_buffer_get_len(sign_input), + digest); if (sig == NULL) { goto end; } diff --git a/src/pki_container_openssh.c b/src/pki_container_openssh.c index ecde4cdd..82afdf8e 100644 --- a/src/pki_container_openssh.c +++ b/src/pki_container_openssh.c @@ -49,7 +49,7 @@ * code. * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * @@ -69,20 +69,20 @@ static int pki_openssh_import_privkey_blob(ssh_buffer key_blob_buffer, rc = ssh_buffer_unpack(key_blob_buffer, "s", &type_s); if (rc == SSH_ERROR){ - SSH_LOG(SSH_LOG_WARN, "Unpack error"); + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); return SSH_ERROR; } type = ssh_key_type_from_name(type_s); if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "Unknown key type '%s' found!", type_s); + SSH_LOG(SSH_LOG_TRACE, "Unknown key type '%s' found!", type_s); return SSH_ERROR; } SAFE_FREE(type_s); rc = pki_import_privkey_buffer(type, key_blob_buffer, &key); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Failed to read key in OpenSSH format"); + SSH_LOG(SSH_LOG_TRACE, "Failed to read key in OpenSSH format"); goto fail; } @@ -133,17 +133,17 @@ static int pki_private_key_decrypt(ssh_string blob, } if (ciphers[i].name == NULL){ - SSH_LOG(SSH_LOG_WARN, "Unsupported cipher %s", ciphername); + SSH_LOG(SSH_LOG_TRACE, "Unsupported cipher %s", ciphername); return SSH_ERROR; } cmp = strcmp(kdfname, "bcrypt"); if (cmp != 0) { - SSH_LOG(SSH_LOG_WARN, "Unsupported KDF %s", kdfname); + SSH_LOG(SSH_LOG_TRACE, "Unsupported KDF %s", kdfname); return SSH_ERROR; } if (ssh_string_len(blob) % cipher.blocksize != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Encrypted string not multiple of blocksize: %zu", ssh_string_len(blob)); return SSH_ERROR; @@ -167,12 +167,12 @@ static int pki_private_key_decrypt(ssh_string blob, /* We need material for key (keysize bits / 8) and IV (blocksize) */ key_material_len = cipher.keysize/8 + cipher.blocksize; if (key_material_len > sizeof(key_material)) { - SSH_LOG(SSH_LOG_WARN, "Key material too big"); + SSH_LOG(SSH_LOG_TRACE, "Key material too big"); return SSH_ERROR; } SSH_LOG(SSH_LOG_DEBUG, - "Decryption: %d key, %d IV, %d rounds, %zu bytes salt", + "Decryption: %d key, %d IV, %" PRIu32 " rounds, %zu bytes salt", cipher.keysize/8, cipher.blocksize, rounds, @@ -181,7 +181,7 @@ static int pki_private_key_decrypt(ssh_string blob, if (passphrase == NULL) { if (auth_fn == NULL) { SAFE_FREE(salt); - SSH_LOG(SSH_LOG_WARN, "No passphrase provided"); + SSH_LOG(SSH_LOG_TRACE, "No passphrase provided"); return SSH_ERROR; } rc = auth_fn("Passphrase", @@ -251,7 +251,7 @@ ssh_pki_openssh_import(const char *text_key, cmp = strncmp(ptr, OPENSSH_HEADER_BEGIN, strlen(OPENSSH_HEADER_BEGIN)); if (cmp != 0) { - SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (no header)"); + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (no header)"); goto out; } ptr += strlen(OPENSSH_HEADER_BEGIN); @@ -260,7 +260,7 @@ ssh_pki_openssh_import(const char *text_key, } end = strstr(ptr, OPENSSH_HEADER_END); if (end == NULL) { - SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (no footer)"); + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (no footer)"); goto out; } base64 = malloc(end - ptr + 1); @@ -277,7 +277,7 @@ ssh_pki_openssh_import(const char *text_key, buffer = base64_to_bin(base64); SAFE_FREE(base64); if (buffer == NULL) { - SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (base64 error)"); + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (base64 error)"); goto out; } rc = ssh_buffer_unpack(buffer, "PssSdSS", @@ -290,21 +290,21 @@ ssh_pki_openssh_import(const char *text_key, &pubkey0, &privkeys); if (rc == SSH_ERROR) { - SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (unpack error)"); + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (unpack error)"); goto out; } cmp = strncmp(magic, OPENSSH_AUTH_MAGIC, strlen(OPENSSH_AUTH_MAGIC)); if (cmp != 0) { - SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (bad magic)"); + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (bad magic)"); goto out; } - SSH_LOG(SSH_LOG_INFO, - "Opening OpenSSH private key: ciphername: %s, kdf: %s, nkeys: %d", + SSH_LOG(SSH_LOG_DEBUG, + "Opening OpenSSH private key: ciphername: %s, kdf: %s, nkeys: %" PRIu32, ciphername, kdfname, nkeys); if (nkeys != 1) { - SSH_LOG(SSH_LOG_WARN, "Opening OpenSSH private key: only 1 key supported (%d available)", nkeys); + SSH_LOG(SSH_LOG_TRACE, "Opening OpenSSH private key: only 1 key supported (%" PRIu32 " available)", nkeys); goto out; } @@ -314,7 +314,7 @@ ssh_pki_openssh_import(const char *text_key, if (!private) { rc = ssh_pki_import_pubkey_blob(pubkey0, &key); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Failed to import public key blob"); + SSH_LOG(SSH_LOG_TRACE, "Failed to import public key blob"); } /* in either case we clean up here */ goto out; @@ -343,7 +343,7 @@ ssh_pki_openssh_import(const char *text_key, rc = ssh_buffer_unpack(privkey_buffer, "dd", &checkint1, &checkint2); if (rc == SSH_ERROR || checkint1 != checkint2) { - SSH_LOG(SSH_LOG_WARN, "OpenSSH private key unpack error (correct password?)"); + SSH_LOG(SSH_LOG_TRACE, "OpenSSH private key unpack error (correct password?)"); goto out; } rc = pki_openssh_import_privkey_blob(privkey_buffer, &key); @@ -358,7 +358,7 @@ ssh_pki_openssh_import(const char *text_key, if (padding != i) { ssh_key_free(key); key = NULL; - SSH_LOG(SSH_LOG_WARN, "Invalid padding"); + SSH_LOG(SSH_LOG_TRACE, "Invalid padding"); goto out; } } @@ -395,37 +395,6 @@ ssh_key ssh_pki_openssh_pubkey_import(const char *text_key) /** @internal - * @brief exports a private key to a string blob. - * @param[in] privkey private key to convert - * @param[out] buffer buffer to write the blob in. - * @returns SSH_OK on success - * @warning only supports ed25519 key type at the moment. - */ -static int pki_openssh_export_privkey_blob(const ssh_key privkey, - ssh_buffer buffer) -{ - int rc; - - if (privkey->type != SSH_KEYTYPE_ED25519) { - SSH_LOG(SSH_LOG_WARN, "Type %s not supported", privkey->type_c); - return SSH_ERROR; - } - if (privkey->ed25519_privkey == NULL || - privkey->ed25519_pubkey == NULL) { - return SSH_ERROR; - } - rc = ssh_buffer_pack(buffer, - "sdPdPP", - privkey->type_c, - (uint32_t)ED25519_KEY_LEN, - (size_t)ED25519_KEY_LEN, privkey->ed25519_pubkey, - (uint32_t)(2 * ED25519_KEY_LEN), - (size_t)ED25519_KEY_LEN, privkey->ed25519_privkey, - (size_t)ED25519_KEY_LEN, privkey->ed25519_pubkey); - return rc; -} - -/** @internal * @brief encrypts an ed25519 private key blob * */ @@ -462,29 +431,29 @@ static int pki_private_key_encrypt(ssh_buffer privkey_buffer, } if (ciphers[i].name == NULL){ - SSH_LOG(SSH_LOG_WARN, "Unsupported cipher %s", ciphername); + SSH_LOG(SSH_LOG_TRACE, "Unsupported cipher %s", ciphername); return SSH_ERROR; } cmp = strcmp(kdfname, "bcrypt"); if (cmp != 0){ - SSH_LOG(SSH_LOG_WARN, "Unsupported KDF %s", kdfname); + SSH_LOG(SSH_LOG_TRACE, "Unsupported KDF %s", kdfname); return SSH_ERROR; } /* We need material for key (keysize bits / 8) and IV (blocksize) */ key_material_len = cipher.keysize/8 + cipher.blocksize; if (key_material_len > sizeof(key_material)){ - SSH_LOG(SSH_LOG_WARN, "Key material too big"); + SSH_LOG(SSH_LOG_TRACE, "Key material too big"); return SSH_ERROR; } - SSH_LOG(SSH_LOG_WARN, "Encryption: %d key, %d IV, %d rounds, %zu bytes salt", + SSH_LOG(SSH_LOG_DEBUG, "Encryption: %d key, %d IV, %" PRIu32 " rounds, %zu bytes salt", cipher.keysize/8, cipher.blocksize, rounds, ssh_string_len(salt)); if (passphrase == NULL){ if (auth_fn == NULL){ - SSH_LOG(SSH_LOG_WARN, "No passphrase provided"); + SSH_LOG(SSH_LOG_TRACE, "No passphrase provided"); return SSH_ERROR; } rc = auth_fn("Passphrase", @@ -536,8 +505,8 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, ssh_auth_callback auth_fn, void *auth_data) { - ssh_buffer buffer; - ssh_string str = NULL; + ssh_buffer buffer = NULL; + ssh_string str = NULL, blob = NULL; ssh_string pubkey_s=NULL; ssh_buffer privkey_buffer = NULL; uint32_t rnd; @@ -554,17 +523,13 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, if (privkey == NULL) { return NULL; } - if (privkey->type != SSH_KEYTYPE_ED25519){ - SSH_LOG(SSH_LOG_WARN, "Unsupported key type %s", privkey->type_c); - return NULL; - } if (passphrase != NULL || auth_fn != NULL){ - SSH_LOG(SSH_LOG_INFO, "Enabling encryption for private key export"); + SSH_LOG(SSH_LOG_DEBUG, "Enabling encryption for private key export"); to_encrypt = 1; } buffer = ssh_buffer_new(); - pubkey_s = pki_publickey_to_blob(privkey); - if(buffer == NULL || pubkey_s == NULL){ + rc = ssh_pki_export_pubkey_blob(privkey, &pubkey_s); + if (buffer == NULL || rc != SSH_OK) { goto error; } @@ -578,22 +543,17 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, goto error; } - /* checkint1 & 2 */ - rc = ssh_buffer_pack(privkey_buffer, - "dd", - rnd, - rnd); - if (rc == SSH_ERROR){ - goto error; - } - - rc = pki_openssh_export_privkey_blob(privkey, privkey_buffer); - if (rc == SSH_ERROR){ + rc = ssh_pki_export_privkey_blob(privkey, &blob); + if (rc != SSH_OK) { goto error; } - /* comment */ - rc = ssh_buffer_pack(privkey_buffer, "s", "" /* comment */); + rc = ssh_buffer_pack(privkey_buffer, + "ddPs", + rnd, /* checkint 1 & 2 */ + rnd, + ssh_string_len(blob), ssh_string_data(blob), + "" /* comment */); if (rc == SSH_ERROR){ goto error; } @@ -630,7 +590,11 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, goto error; } - ssh_buffer_pack(kdf_buf, "Sd", salt, rounds); + rc = ssh_buffer_pack(kdf_buf, "Sd", salt, rounds); + if (rc != SSH_OK) { + SSH_BUFFER_FREE(kdf_buf); + goto error; + } kdf_options = ssh_string_new(ssh_buffer_get_len(kdf_buf)); if (kdf_options == NULL){ SSH_BUFFER_FREE(kdf_buf); @@ -706,6 +670,8 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, } error: + ssh_string_burn(blob); + ssh_string_free(blob); if (privkey_buffer != NULL) { void *bufptr = ssh_buffer_get(privkey_buffer); explicit_bzero(bufptr, ssh_buffer_get_len(privkey_buffer)); diff --git a/src/pki_crypto.c b/src/pki_crypto.c index 08409209..f4ce8bdf 100644 --- a/src/pki_crypto.c +++ b/src/pki_crypto.c @@ -29,14 +29,24 @@ #include "config.h" #include "libssh/priv.h" +#include "libcrypto-compat.h" #include <openssl/pem.h> -#include <openssl/dsa.h> -#include <openssl/err.h> -#include <openssl/engine.h> #include <openssl/evp.h> +#include <openssl/engine.h> +#include <openssl/err.h> +#if OPENSSL_VERSION_NUMBER < 0x30000000L +#include <openssl/dsa.h> #include <openssl/rsa.h> -#include "libcrypto-compat.h" +#else +#include <openssl/params.h> +#include <openssl/core_names.h> +#include <openssl/param_build.h> +#if defined(WITH_PKCS11_URI) && defined(WITH_PKCS11_PROVIDER) +#include <openssl/store.h> +#include <openssl/provider.h> +#endif +#endif /* OPENSSL_VERSION_NUMBER */ #ifdef HAVE_OPENSSL_EC_H #include <openssl/ec.h> @@ -81,12 +91,24 @@ static int pem_get_password(char *buf, int size, int rwflag, void *userdata) { return 0; } +void pki_key_clean(ssh_key key) +{ + if (key == NULL) + return; + EVP_PKEY_free(key->key); + key->key = NULL; +} + #ifdef HAVE_OPENSSL_ECC +#if OPENSSL_VERSION_NUMBER < 0x30000000L static int pki_key_ecdsa_to_nid(EC_KEY *k) { const EC_GROUP *g = EC_KEY_get0_group(k); int nid; + if (g == NULL) { + return -1; + } nid = EC_GROUP_get_curve_name(g); if (nid) { return nid; @@ -94,8 +116,30 @@ static int pki_key_ecdsa_to_nid(EC_KEY *k) return -1; } +#else +static int pki_key_ecdsa_to_nid(EVP_PKEY *k) +{ + char gname[25] = { 0 }; + int rc; + + rc = EVP_PKEY_get_utf8_string_param(k, + OSSL_PKEY_PARAM_GROUP_NAME, + gname, + 25, + NULL); + if (rc != 1) { + return -1; + } + + return pki_key_ecgroup_name_to_nid(gname); +} +#endif /* OPENSSL_VERSION_NUMBER */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(EC_KEY *k) +#else +static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(EVP_PKEY *k) +#endif /* OPENSSL_VERSION_NUMBER */ { int nid; @@ -158,114 +202,157 @@ int pki_key_ecdsa_nid_from_name(const char *name) return -1; } -static ssh_string make_ecpoint_string(const EC_GROUP *g, - const EC_POINT *p) +int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) { - ssh_string s; - size_t len; - - len = EC_POINT_point2oct(g, - p, - POINT_CONVERSION_UNCOMPRESSED, - NULL, - 0, - NULL); - if (len == 0) { - return NULL; - } + int rc = 0; + BIGNUM *bexp = NULL; - s = ssh_string_new(len); - if (s == NULL) { - return NULL; - } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_POINT *p = NULL; + const EC_GROUP *g = NULL; + EC_KEY *ecdsa = NULL; +#else + const char *group_name = OSSL_EC_curve_nid2name(nid); + OSSL_PARAM_BLD *param_bld = NULL; - len = EC_POINT_point2oct(g, - p, - POINT_CONVERSION_UNCOMPRESSED, - ssh_string_data(s), - ssh_string_len(s), - NULL); - if (len != ssh_string_len(s)) { - SSH_STRING_FREE(s); - return NULL; + if (group_name == NULL) { + return -1; } +#endif /* OPENSSL_VERSION_NUMBER */ - return s; -} - -int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) -{ - EC_POINT *p = NULL; - const EC_GROUP *g = NULL; - int ok; - BIGNUM *bexp = NULL; + bexp = ssh_make_string_bn(exp); + if (bexp == NULL) { + return -1; + } key->ecdsa_nid = nid; key->type_c = pki_key_ecdsa_nid_to_name(nid); - key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); - if (key->ecdsa == NULL) { - return -1; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (ecdsa == NULL) { + rc = -1; + goto cleanup; } - g = EC_KEY_get0_group(key->ecdsa); + g = EC_KEY_get0_group(ecdsa); p = EC_POINT_new(g); if (p == NULL) { - return -1; + rc = -1; + goto cleanup; } - ok = EC_POINT_oct2point(g, + rc = EC_POINT_oct2point(g, p, ssh_string_data(e), ssh_string_len(e), NULL); - if (!ok) { - EC_POINT_free(p); - return -1; + if (rc != 1) { + rc = -1; + goto cleanup; } /* EC_KEY_set_public_key duplicates p */ - ok = EC_KEY_set_public_key(key->ecdsa, p); - EC_POINT_free(p); - if (!ok) { - return -1; + rc = EC_KEY_set_public_key(ecdsa, p); + if (rc != 1) { + rc = -1; + goto cleanup; } - bexp = ssh_make_string_bn(exp); - if (bexp == NULL) { - EC_KEY_free(key->ecdsa); - return -1; - } /* EC_KEY_set_private_key duplicates exp */ - ok = EC_KEY_set_private_key(key->ecdsa, bexp); + rc = EC_KEY_set_private_key(ecdsa, bexp); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + rc = -1; + goto cleanup; + } + + /* ecdsa will be freed when the EVP_PKEY key->key is freed */ + rc = EVP_PKEY_assign_EC_KEY(key->key, ecdsa); + if (rc != 1) { + rc = -1; + goto cleanup; + } + /* ssh_key is now the owner of this memory */ + ecdsa = NULL; + + /* set rc to 0 if everything went well */ + rc = 0; + +cleanup: + EC_KEY_free(ecdsa); + EC_POINT_free(p); BN_free(bexp); - if (!ok) { - EC_KEY_free(key->ecdsa); - return -1; + return rc; +#else + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL){ + rc = -1; + goto cleanup; } - return 0; + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME, + group_name, strlen(group_name)); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY, + ssh_string_data(e), ssh_string_len(e)); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PRIV_KEY, bexp); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + rc = evp_build_pkey("EC", param_bld, &(key->key), EVP_PKEY_KEYPAIR); + +cleanup: + OSSL_PARAM_BLD_free(param_bld); + BN_free(bexp); + return rc; +#endif /* OPENSSL_VERSION_NUMBER */ } int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) { + int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L EC_POINT *p = NULL; const EC_GROUP *g = NULL; + EC_KEY *ecdsa = NULL; int ok; +#else + const char *group_name = OSSL_EC_curve_nid2name(nid); + OSSL_PARAM_BLD *param_bld; +#endif /* OPENSSL_VERSION_NUMBER */ key->ecdsa_nid = nid; key->type_c = pki_key_ecdsa_nid_to_name(nid); - key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); - if (key->ecdsa == NULL) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (ecdsa == NULL) { return -1; } - g = EC_KEY_get0_group(key->ecdsa); + g = EC_KEY_get0_group(ecdsa); p = EC_POINT_new(g); if (p == NULL) { + EC_KEY_free(ecdsa); return -1; } @@ -275,24 +362,60 @@ int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) ssh_string_len(e), NULL); if (!ok) { + EC_KEY_free(ecdsa); EC_POINT_free(p); return -1; } /* EC_KEY_set_public_key duplicates p */ - ok = EC_KEY_set_public_key(key->ecdsa, p); + ok = EC_KEY_set_public_key(ecdsa, p); EC_POINT_free(p); if (!ok) { + EC_KEY_free(ecdsa); + return -1; + } + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + EC_KEY_free(ecdsa); + return -1; + } + + rc = EVP_PKEY_assign_EC_KEY(key->key, ecdsa); + if (rc != 1) { + EC_KEY_free(ecdsa); return -1; } return 0; +#else + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) + goto err; + + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME, + group_name, strlen(group_name)); + if (rc != 1) + goto err; + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY, + ssh_string_data(e), ssh_string_len(e)); + if (rc != 1) + goto err; + + rc = evp_build_pkey("EC", param_bld, &(key->key), EVP_PKEY_PUBLIC_KEY); + OSSL_PARAM_BLD_free(param_bld); + + return rc; +err: + OSSL_PARAM_BLD_free(param_bld); + return -1; +#endif /* OPENSSL_VERSION_NUMBER */ } -#endif +#endif /* HAVE_OPENSSL_ECC */ ssh_key pki_key_dup(const ssh_key key, int demote) { - ssh_key new; + ssh_key new = NULL; int rc; new = ssh_key_new(); @@ -300,10 +423,6 @@ ssh_key pki_key_dup(const ssh_key key, int demote) return NULL; } -#ifdef WITH_PKCS11_URI - new->key = key->key; -#endif - new->type = key->type; new->type_c = key->type_c; if (demote) { @@ -313,75 +432,28 @@ ssh_key pki_key_dup(const ssh_key key, int demote) } switch (key->type) { - case SSH_KEYTYPE_DSS: { - const BIGNUM *p = NULL, *q = NULL, *g = NULL, - *pub_key = NULL, *priv_key = NULL; - BIGNUM *np, *nq, *ng, *npub_key, *npriv_key; - new->dsa = DSA_new(); - if (new->dsa == NULL) { - goto fail; - } - - /* - * p = public prime number - * q = public 160-bit subprime, q | p-1 - * g = public generator of subgroup - * pub_key = public key y = g^x - * priv_key = private key x - */ - DSA_get0_pqg(key->dsa, &p, &q, &g); - np = BN_dup(p); - nq = BN_dup(q); - ng = BN_dup(g); - if (np == NULL || nq == NULL || ng == NULL) { - BN_free(np); - BN_free(nq); - BN_free(ng); - goto fail; - } - - /* Memory management of np, nq and ng is transferred to DSA object */ - rc = DSA_set0_pqg(new->dsa, np, nq, ng); - if (rc == 0) { - BN_free(np); - BN_free(nq); - BN_free(ng); - goto fail; - } - - DSA_get0_key(key->dsa, &pub_key, &priv_key); - npub_key = BN_dup(pub_key); - if (npub_key == NULL) { - goto fail; - } - - /* Memory management of npubkey is transferred to DSA object */ - rc = DSA_set0_key(new->dsa, npub_key, NULL); - if (rc == 0) { - goto fail; - } - - if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { - npriv_key = BN_dup(priv_key); - if (npriv_key == NULL) { - goto fail; - } - - /* Memory management of npriv_key is transferred to DSA object */ - rc = DSA_set0_key(new->dsa, NULL, npriv_key); - if (rc == 0) { - goto fail; - } - } - - break; - } case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: { +#if OPENSSL_VERSION_NUMBER < 0x30000000L const BIGNUM *n = NULL, *e = NULL, *d = NULL; BIGNUM *nn, *ne, *nd; - new->rsa = RSA_new(); - if (new->rsa == NULL) { + RSA *new_rsa = NULL; + const RSA *key_rsa = EVP_PKEY_get0_RSA(key->key); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */ +#ifdef WITH_PKCS11_URI + /* Take the PKCS#11 keys as they are */ + if (key->flags & SSH_KEY_FLAG_PKCS11_URI && !demote) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + return new; + } +#endif /* WITH_PKCS11_URI */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + new_rsa = RSA_new(); + if (new_rsa == NULL) { goto fail; } @@ -395,18 +467,20 @@ ssh_key pki_key_dup(const ssh_key key, int demote) * dmq1 = d mod (q-1) * iqmp = q^-1 mod p */ - RSA_get0_key(key->rsa, &n, &e, &d); + RSA_get0_key(key_rsa, &n, &e, &d); nn = BN_dup(n); ne = BN_dup(e); if (nn == NULL || ne == NULL) { + RSA_free(new_rsa); BN_free(nn); BN_free(ne); goto fail; } /* Memory management of nn and ne is transferred to RSA object */ - rc = RSA_set0_key(new->rsa, nn, ne, NULL); + rc = RSA_set0_key(new_rsa, nn, ne, NULL); if (rc == 0) { + RSA_free(new_rsa); BN_free(nn); BN_free(ne); goto fail; @@ -419,43 +493,48 @@ ssh_key pki_key_dup(const ssh_key key, int demote) nd = BN_dup(d); if (nd == NULL) { + RSA_free(new_rsa); goto fail; } /* Memory management of nd is transferred to RSA object */ - rc = RSA_set0_key(new->rsa, NULL, NULL, nd); + rc = RSA_set0_key(new_rsa, NULL, NULL, nd); if (rc == 0) { + RSA_free(new_rsa); goto fail; } /* p, q, dmp1, dmq1 and iqmp may be NULL in private keys, but the * RSA operations are much faster when these values are available. */ - RSA_get0_factors(key->rsa, &p, &q); + RSA_get0_factors(key_rsa, &p, &q); if (p != NULL && q != NULL) { /* need to set both of them */ np = BN_dup(p); nq = BN_dup(q); if (np == NULL || nq == NULL) { + RSA_free(new_rsa); BN_free(np); BN_free(nq); goto fail; } /* Memory management of np and nq is transferred to RSA object */ - rc = RSA_set0_factors(new->rsa, np, nq); + rc = RSA_set0_factors(new_rsa, np, nq); if (rc == 0) { + RSA_free(new_rsa); BN_free(np); BN_free(nq); goto fail; } } - RSA_get0_crt_params(key->rsa, &dmp1, &dmq1, &iqmp); + RSA_get0_crt_params(key_rsa, &dmp1, &dmq1, &iqmp); if (dmp1 != NULL || dmq1 != NULL || iqmp != NULL) { ndmp1 = BN_dup(dmp1); ndmq1 = BN_dup(dmq1); niqmp = BN_dup(iqmp); if (ndmp1 == NULL || ndmq1 == NULL || niqmp == NULL) { + RSA_free(new_rsa); BN_free(ndmp1); BN_free(ndmq1); BN_free(niqmp); @@ -464,8 +543,9 @@ ssh_key pki_key_dup(const ssh_key key, int demote) /* Memory management of ndmp1, ndmq1 and niqmp is transferred * to RSA object */ - rc = RSA_set0_crt_params(new->rsa, ndmp1, ndmq1, niqmp); + rc = RSA_set0_crt_params(new_rsa, ndmp1, ndmq1, niqmp); if (rc == 0) { + RSA_free(new_rsa); BN_free(ndmp1); BN_free(ndmq1); BN_free(niqmp); @@ -474,6 +554,26 @@ ssh_key pki_key_dup(const ssh_key key, int demote) } } + new->key = EVP_PKEY_new(); + if (new->key == NULL) { + RSA_free(new_rsa); + goto fail; + } + + rc = EVP_PKEY_assign_RSA(new->key, new_rsa); + if (rc != 1) { + EVP_PKEY_free(new->key); + RSA_free(new_rsa); + goto fail; + } + + new_rsa = NULL; +#else + rc = evp_dup_rsa_pkey(key, new, demote); + if (rc != SSH_OK) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ break; } case SSH_KEYTYPE_ECDSA_P256: @@ -481,31 +581,73 @@ ssh_key pki_key_dup(const ssh_key key, int demote) case SSH_KEYTYPE_ECDSA_P521: #ifdef HAVE_OPENSSL_ECC new->ecdsa_nid = key->ecdsa_nid; - +#ifdef WITH_PKCS11_URI + /* Take the PKCS#11 keys as they are */ + if (key->flags & SSH_KEY_FLAG_PKCS11_URI && !demote) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + return new; + } +#endif /* WITH_PKCS11_URI */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L /* privkey -> pubkey */ if (demote && ssh_key_is_private(key)) { - const EC_POINT *p; + const EC_POINT *p = NULL; + EC_KEY *new_ecdsa = NULL, *old_ecdsa = NULL; int ok; - new->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); - if (new->ecdsa == NULL) { + new_ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (new_ecdsa == NULL) { goto fail; } - p = EC_KEY_get0_public_key(key->ecdsa); + old_ecdsa = EVP_PKEY_get0_EC_KEY(key->key); + if (old_ecdsa == NULL) { + EC_KEY_free(new_ecdsa); + goto fail; + } + + p = EC_KEY_get0_public_key(old_ecdsa); if (p == NULL) { + EC_KEY_free(new_ecdsa); + goto fail; + } + + ok = EC_KEY_set_public_key(new_ecdsa, p); + if (ok != 1) { + EC_KEY_free(new_ecdsa); goto fail; } - ok = EC_KEY_set_public_key(new->ecdsa, p); - if (!ok) { + new->key = EVP_PKEY_new(); + if (new->key == NULL) { + EC_KEY_free(new_ecdsa); + goto fail; + } + + ok = EVP_PKEY_assign_EC_KEY(new->key, new_ecdsa); + if (ok != 1) { + EC_KEY_free(new_ecdsa); goto fail; } } else { - new->ecdsa = EC_KEY_dup(key->ecdsa); + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + } +#else + rc = evp_dup_ecdsa_pkey(key, new, demote); + if (rc != SSH_OK) { + goto fail; } +#endif /* OPENSSL_VERSION_NUMBER */ break; -#endif +#endif /* HAVE_OPENSSL_ECC */ case SSH_KEYTYPE_ED25519: rc = pki_ed25519_key_dup(new, key); if (rc != SSH_OK) { @@ -525,172 +667,181 @@ fail: } int pki_key_generate_rsa(ssh_key key, int parameter){ - BIGNUM *e; int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + BIGNUM *e = NULL; + RSA *key_rsa = NULL; +#else + OSSL_PARAM params[3]; + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); + unsigned e = 65537; +#endif /* OPENSSL_VERSION_NUMBER */ + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + e = BN_new(); + key_rsa = RSA_new(); + if (key_rsa == NULL) { + return SSH_ERROR; + } - e = BN_new(); - key->rsa = RSA_new(); - - BN_set_word(e, 65537); - rc = RSA_generate_key_ex(key->rsa, parameter, e, NULL); + BN_set_word(e, 65537); + rc = RSA_generate_key_ex(key_rsa, parameter, e, NULL); - BN_free(e); + BN_free(e); - if (rc <= 0 || key->rsa == NULL) - return SSH_ERROR; - return SSH_OK; -} + if (rc <= 0 || key_rsa == NULL) { + return SSH_ERROR; + } -int pki_key_generate_dss(ssh_key key, int parameter){ - int rc; -#if OPENSSL_VERSION_NUMBER > 0x00908000L - key->dsa = DSA_new(); - if (key->dsa == NULL) { + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + RSA_free(key_rsa); return SSH_ERROR; } - rc = DSA_generate_parameters_ex(key->dsa, - parameter, - NULL, /* seed */ - 0, /* seed_len */ - NULL, /* counter_ret */ - NULL, /* h_ret */ - NULL); /* cb */ + + rc = EVP_PKEY_assign_RSA(key->key, key_rsa); if (rc != 1) { - DSA_free(key->dsa); - key->dsa = NULL; + RSA_free(key_rsa); + EVP_PKEY_free(key->key); return SSH_ERROR; } + + key_rsa = NULL; #else - key->dsa = DSA_generate_parameters(parameter, NULL, 0, NULL, NULL, - NULL, NULL); - if(key->dsa == NULL){ + key->key = NULL; + + rc = EVP_PKEY_keygen_init(pctx); + if (rc != 1) { + EVP_PKEY_CTX_free(pctx); return SSH_ERROR; } -#endif - rc = DSA_generate_key(key->dsa); - if (rc != 1){ - DSA_free(key->dsa); - key->dsa=NULL; + + params[0] = OSSL_PARAM_construct_int("bits", ¶meter); + params[1] = OSSL_PARAM_construct_uint("e", &e); + params[2] = OSSL_PARAM_construct_end(); + rc = EVP_PKEY_CTX_set_params(pctx, params); + if (rc != 1) { + EVP_PKEY_CTX_free(pctx); return SSH_ERROR; } - return SSH_OK; + + rc = EVP_PKEY_generate(pctx, &(key->key)); + + EVP_PKEY_CTX_free(pctx); + + if (rc != 1 || key->key == NULL) + return SSH_ERROR; +#endif /* OPENSSL_VERSION_NUMBER */ + return SSH_OK; } #ifdef HAVE_OPENSSL_ECC -int pki_key_generate_ecdsa(ssh_key key, int parameter) { +int pki_key_generate_ecdsa(ssh_key key, int parameter) +{ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY *ecdsa = NULL; int ok; - +#else + const char *group_name = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ switch (parameter) { + case 256: + key->ecdsa_nid = NID_X9_62_prime256v1; + key->type = SSH_KEYTYPE_ECDSA_P256; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + group_name = NISTP256; +#endif /* OPENSSL_VERSION_NUMBER */ + break; case 384: key->ecdsa_nid = NID_secp384r1; key->type = SSH_KEYTYPE_ECDSA_P384; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + group_name = NISTP384; +#endif /* OPENSSL_VERSION_NUMBER */ break; case 521: key->ecdsa_nid = NID_secp521r1; key->type = SSH_KEYTYPE_ECDSA_P521; - break; - case 256: - key->ecdsa_nid = NID_X9_62_prime256v1; - key->type = SSH_KEYTYPE_ECDSA_P256; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + group_name = NISTP521; +#endif /* OPENSSL_VERSION_NUMBER */ break; default: - SSH_LOG(SSH_LOG_WARN, "Invalid parameter %d for ECDSA key " + SSH_LOG(SSH_LOG_TRACE, "Invalid parameter %d for ECDSA key " "generation", parameter); return SSH_ERROR; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (ecdsa == NULL) { + return SSH_ERROR; + } + ok = EC_KEY_generate_key(ecdsa); + if (!ok) { + EC_KEY_free(ecdsa); + return SSH_ERROR; + } + + EC_KEY_set_asn1_flag(ecdsa, OPENSSL_EC_NAMED_CURVE); - key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); - if (key->ecdsa == NULL) { + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + EC_KEY_free(ecdsa); return SSH_ERROR; } - ok = EC_KEY_generate_key(key->ecdsa); - if (!ok) { - EC_KEY_free(key->ecdsa); + ok = EVP_PKEY_assign_EC_KEY(key->key, ecdsa); + if (ok != 1) { return SSH_ERROR; } - EC_KEY_set_asn1_flag(key->ecdsa, OPENSSL_EC_NAMED_CURVE); +#else + key->key = EVP_EC_gen(group_name); + if (key->key == NULL) { + return SSH_ERROR; + } +#endif /* OPENSSL_VERSION_NUMBER */ return SSH_OK; } -#endif +#endif /* HAVE_OPENSSL_ECC */ +/* With OpenSSL 3.0 and higher the parameter 'what' + * is ignored and the comparison is done by OpenSSL + */ int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) { - switch (k1->type) { - case SSH_KEYTYPE_DSS: { - const BIGNUM *p1, *p2, *q1, *q2, *g1, *g2, - *pub_key1, *pub_key2, *priv_key1, *priv_key2; - if (DSA_size(k1->dsa) != DSA_size(k2->dsa)) { - return 1; - } - DSA_get0_pqg(k1->dsa, &p1, &q1, &g1); - DSA_get0_pqg(k2->dsa, &p2, &q2, &g2); - if (bignum_cmp(p1, p2) != 0) { - return 1; - } - if (bignum_cmp(q1, q2) != 0) { - return 1; - } - if (bignum_cmp(g1, g2) != 0) { - return 1; - } - DSA_get0_key(k1->dsa, &pub_key1, &priv_key1); - DSA_get0_key(k2->dsa, &pub_key2, &priv_key2); - if (bignum_cmp(pub_key1, pub_key2) != 0) { - return 1; - } + int rc; - if (what == SSH_KEY_CMP_PRIVATE) { - if (bignum_cmp(priv_key1, priv_key2) != 0) { - return 1; - } - } - break; - } - case SSH_KEYTYPE_RSA: - case SSH_KEYTYPE_RSA1: { - const BIGNUM *e1, *e2, *n1, *n2, *p1, *p2, *q1, *q2; - if (RSA_size(k1->rsa) != RSA_size(k2->rsa)) { - return 1; - } - RSA_get0_key(k1->rsa, &n1, &e1, NULL); - RSA_get0_key(k2->rsa, &n2, &e2, NULL); - if (bignum_cmp(e1, e2) != 0) { - return 1; - } - if (bignum_cmp(n1, n2) != 0) { - return 1; - } + (void)what; - if (what == SSH_KEY_CMP_PRIVATE) { - RSA_get0_factors(k1->rsa, &p1, &q1); - RSA_get0_factors(k2->rsa, &p2, &q2); - if (bignum_cmp(p1, p2) != 0) { - return 1; - } - - if (bignum_cmp(q1, q2) != 0) { - return 1; - } - } - break; - } + switch (ssh_key_type_plain(k1->type)) { case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: case SSH_KEYTYPE_SK_ECDSA: +#if OPENSSL_VERSION_NUMBER < 0x30000000L #ifdef HAVE_OPENSSL_ECC { - const EC_POINT *p1 = EC_KEY_get0_public_key(k1->ecdsa); - const EC_POINT *p2 = EC_KEY_get0_public_key(k2->ecdsa); - const EC_GROUP *g1 = EC_KEY_get0_group(k1->ecdsa); - const EC_GROUP *g2 = EC_KEY_get0_group(k2->ecdsa); + const EC_KEY *ec1 = EVP_PKEY_get0_EC_KEY(k1->key); + const EC_KEY *ec2 = EVP_PKEY_get0_EC_KEY(k2->key); + const EC_POINT *p1 = NULL; + const EC_POINT *p2 = NULL; + const EC_GROUP *g1 = NULL; + const EC_GROUP *g2 = NULL; + + if (ec1 == NULL || ec2 == NULL) { + return 1; + } + + p1 = EC_KEY_get0_public_key(ec1); + p2 = EC_KEY_get0_public_key(ec2); + g1 = EC_KEY_get0_group(ec1); + g2 = EC_KEY_get0_group(ec2); - if (p1 == NULL || p2 == NULL) { + if (p1 == NULL || p2 == NULL || g1 == NULL || g2 == NULL) { return 1; } @@ -703,23 +854,29 @@ int pki_key_compare(const ssh_key k1, } if (what == SSH_KEY_CMP_PRIVATE) { - if (bignum_cmp(EC_KEY_get0_private_key(k1->ecdsa), - EC_KEY_get0_private_key(k2->ecdsa))) { + if (bignum_cmp(EC_KEY_get0_private_key(ec1), + EC_KEY_get0_private_key(ec2))) { return 1; } } - break; } -#endif +#endif /* HAVE_OPENSSL_ECC */ +#endif /* OPENSSL_VERSION_NUMBER */ + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + rc = EVP_PKEY_eq(k1->key, k2->key); + if (rc != 1) { + return 1; + } + break; case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: - /* ed25519 keys handled globaly */ + /* ed25519 keys handled globally */ case SSH_KEYTYPE_UNKNOWN: default: return 1; } - return 0; } @@ -740,37 +897,22 @@ ssh_string pki_private_key_to_pem(const ssh_key key, } switch (key->type) { - case SSH_KEYTYPE_DSS: - pkey = EVP_PKEY_new(); - if (pkey == NULL) { - goto err; - } - - rc = EVP_PKEY_set1_DSA(pkey, key->dsa); - break; case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: - pkey = EVP_PKEY_new(); - if (pkey == NULL) { - goto err; - } - - rc = EVP_PKEY_set1_RSA(pkey, key->rsa); - break; -#ifdef HAVE_ECC case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: - pkey = EVP_PKEY_new(); - if (pkey == NULL) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { goto err; } + pkey = key->key; + + /* Mark the operation as successful as for the other key types */ + rc = 1; - rc = EVP_PKEY_set1_EC_KEY(pkey, key->ecdsa); break; -#endif case SSH_KEYTYPE_ED25519: -#ifdef HAVE_OPENSSL_ED25519 /* In OpenSSL, the input is the private key seed only, which means * the first half of the SSH private key (the second half is the * public key) */ @@ -787,11 +929,6 @@ ssh_string pki_private_key_to_pem(const ssh_key key, /* Mark the operation as successful as for the other key types */ rc = 1; break; -#else - SSH_LOG(SSH_LOG_WARN, "PEM output not supported for key type ssh-ed25519"); - goto err; -#endif - case SSH_KEYTYPE_DSS_CERT01: case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_ECDSA_P384_CERT01: @@ -799,11 +936,11 @@ ssh_string pki_private_key_to_pem(const ssh_key key, case SSH_KEYTYPE_ED25519_CERT01: case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_WARN, "Unknown or invalid private key type %d", key->type); + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key type %d", key->type); goto err; } if (rc != 1) { - SSH_LOG(SSH_LOG_WARN, "Failed to initialize EVP_PKEY structure"); + SSH_LOG(SSH_LOG_TRACE, "Failed to initialize EVP_PKEY structure"); goto err; } @@ -830,6 +967,9 @@ ssh_string pki_private_key_to_pem(const ssh_key key, pkey = NULL; if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to write private key: %s\n", + ERR_error_string(ERR_get_error(), NULL)); goto err; } @@ -840,7 +980,12 @@ ssh_string pki_private_key_to_pem(const ssh_key key, goto err; } - ssh_string_fill(blob, buf->data, buf->length); + rc = ssh_string_fill(blob, buf->data, buf->length); + if (rc < 0) { + ssh_string_free(blob); + goto err; + } + BIO_free(mem); return blob; @@ -857,20 +1002,13 @@ ssh_key pki_private_key_from_base64(const char *b64_key, void *auth_data) { BIO *mem = NULL; - DSA *dsa = NULL; - RSA *rsa = NULL; -#ifdef HAVE_OPENSSL_ED25519 +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY *ecdsa = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ uint8_t *ed25519 = NULL; -#else - ed25519_privkey *ed25519 = NULL; -#endif + uint8_t *ed25519_pubkey = NULL; ssh_key key = NULL; enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; -#ifdef HAVE_OPENSSL_ECC - EC_KEY *ecdsa = NULL; -#else - void *ecdsa = NULL; -#endif EVP_PKEY *pkey = NULL; mem = BIO_new_mem_buf((void*)b64_key, -1); @@ -891,53 +1029,41 @@ ssh_key pki_private_key_from_base64(const char *b64_key, BIO_free(mem); if (pkey == NULL) { - SSH_LOG(SSH_LOG_WARN, - "Parsing private key: %s", + SSH_LOG(SSH_LOG_TRACE, + "Error parsing private key: %s", ERR_error_string(ERR_get_error(), NULL)); return NULL; } switch (EVP_PKEY_base_id(pkey)) { - case EVP_PKEY_DSA: - dsa = EVP_PKEY_get1_DSA(pkey); - if (dsa == NULL) { - SSH_LOG(SSH_LOG_WARN, - "Parsing private key: %s", - ERR_error_string(ERR_get_error(),NULL)); - goto fail; - } - type = SSH_KEYTYPE_DSS; - break; case EVP_PKEY_RSA: - rsa = EVP_PKEY_get1_RSA(pkey); - if (rsa == NULL) { - SSH_LOG(SSH_LOG_WARN, - "Parsing private key: %s", - ERR_error_string(ERR_get_error(),NULL)); - goto fail; - } type = SSH_KEYTYPE_RSA; break; case EVP_PKEY_EC: #ifdef HAVE_OPENSSL_ECC - ecdsa = EVP_PKEY_get1_EC_KEY(pkey); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EVP_PKEY_get0_EC_KEY(pkey); if (ecdsa == NULL) { - SSH_LOG(SSH_LOG_WARN, - "Parsing private key: %s", + SSH_LOG(SSH_LOG_TRACE, + "Error parsing private key: %s", ERR_error_string(ERR_get_error(), NULL)); goto fail; } +#endif /* OPENSSL_VERSION_NUMBER */ /* pki_privatekey_type_from_string always returns P256 for ECDSA * keys, so we need to figure out the correct type here */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L type = pki_key_ecdsa_to_key_type(ecdsa); +#else + type = pki_key_ecdsa_to_key_type(pkey); +#endif /* OPENSSL_VERSION_NUMBER */ if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "Invalid private key."); + SSH_LOG(SSH_LOG_TRACE, "Invalid private key."); goto fail; } break; -#endif -#ifdef HAVE_OPENSSL_ED25519 +#endif /* HAVE_OPENSSL_ECC */ case EVP_PKEY_ED25519: { size_t key_len; @@ -958,7 +1084,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, ed25519 = malloc(key_len); if (ed25519 == NULL) { - SSH_LOG(SSH_LOG_WARN, "Out of memory"); + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); goto fail; } @@ -970,17 +1096,32 @@ ssh_key pki_private_key_from_base64(const char *b64_key, ERR_error_string(ERR_get_error(), NULL)); goto fail; } + + /* length matches the private key length */ + ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (ed25519_pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto fail; + } + + evp_rc = EVP_PKEY_get_raw_public_key(pkey, (uint8_t *)ed25519_pubkey, + &key_len); + if (evp_rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } type = SSH_KEYTYPE_ED25519; + } break; -#endif default: - EVP_PKEY_free(pkey); - SSH_LOG(SSH_LOG_WARN, "Unknown or invalid private key type %d", + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key type %d", EVP_PKEY_base_id(pkey)); + EVP_PKEY_free(pkey); return NULL; } - EVP_PKEY_free(pkey); key = ssh_key_new(); if (key == NULL) { @@ -990,164 +1131,207 @@ ssh_key pki_private_key_from_base64(const char *b64_key, key->type = type; key->type_c = ssh_key_type_to_char(type); key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; - key->dsa = dsa; - key->rsa = rsa; - key->ecdsa = ecdsa; + key->key = pkey; key->ed25519_privkey = ed25519; + key->ed25519_pubkey = ed25519_pubkey; #ifdef HAVE_OPENSSL_ECC if (is_ecdsa_key_type(key->type)) { - key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + key->ecdsa_nid = pki_key_ecdsa_to_nid(ecdsa); +#else + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->key); +#endif /* OPENSSL_VERSION_NUMBER */ } -#endif +#endif /* HAVE_OPENSSL_ECC */ return key; fail: EVP_PKEY_free(pkey); ssh_key_free(key); - DSA_free(dsa); - RSA_free(rsa); -#ifdef HAVE_OPENSSL_ECC - EC_KEY_free(ecdsa); -#endif -#ifdef HAVE_OPENSSL_ED25519 SAFE_FREE(ed25519); -#endif + SAFE_FREE(ed25519_pubkey); return NULL; } -int pki_privkey_build_dss(ssh_key key, +int pki_privkey_build_rsa(ssh_key key, + ssh_string n, + ssh_string e, + ssh_string d, + ssh_string iqmp, ssh_string p, - ssh_string q, - ssh_string g, - ssh_string pubkey, - ssh_string privkey) + ssh_string q) { int rc; - BIGNUM *bp, *bq, *bg, *bpub_key, *bpriv_key; - - key->dsa = DSA_new(); - if (key->dsa == NULL) { + BIGNUM *be = NULL, *bn = NULL, *bd = NULL; + BIGNUM *biqmp = NULL, *bp = NULL, *bq = NULL; + BIGNUM *aux = NULL, *d_consttime = NULL; + BIGNUM *bdmq1 = NULL, *bdmp1 = NULL; + BN_CTX *ctx = NULL; + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) { return SSH_ERROR; } +#else + RSA *key_rsa = RSA_new(); + if (key_rsa == NULL) { + return SSH_ERROR; + } +#endif /* OPENSSL_VERSION_NUMBER */ + bn = ssh_make_string_bn(n); + be = ssh_make_string_bn(e); + bd = ssh_make_string_bn(d); + biqmp = ssh_make_string_bn(iqmp); bp = ssh_make_string_bn(p); bq = ssh_make_string_bn(q); - bg = ssh_make_string_bn(g); - bpub_key = ssh_make_string_bn(pubkey); - bpriv_key = ssh_make_string_bn(privkey); - if (bp == NULL || bq == NULL || - bg == NULL || bpub_key == NULL) { + if (be == NULL || bn == NULL || bd == NULL || + /*biqmp == NULL ||*/ bp == NULL || bq == NULL) { + rc = SSH_ERROR; goto fail; } - /* Memory management of bp, qq and bg is transferred to DSA object */ - rc = DSA_set0_pqg(key->dsa, bp, bq, bg); - if (rc == 0) { + /* Calculate remaining CRT parameters for OpenSSL to be happy + * taken from OpenSSH */ + if ((ctx = BN_CTX_new()) == NULL) { + rc = SSH_ERROR; goto fail; } - - /* Memory management of bpub_key and bpriv_key is transferred to DSA object */ - rc = DSA_set0_key(key->dsa, bpub_key, bpriv_key); - if (rc == 0) { + if ((aux = BN_new()) == NULL || + (bdmq1 = BN_new()) == NULL || + (bdmp1 = BN_new()) == NULL) { + rc = SSH_ERROR; goto fail; } + if ((d_consttime = BN_dup(bd)) == NULL) { + rc = SSH_ERROR; + goto fail; + } + BN_set_flags(aux, BN_FLG_CONSTTIME); + BN_set_flags(d_consttime, BN_FLG_CONSTTIME); - return SSH_OK; -fail: - DSA_free(key->dsa); - return SSH_ERROR; -} - -int pki_pubkey_build_dss(ssh_key key, - ssh_string p, - ssh_string q, - ssh_string g, - ssh_string pubkey) { - int rc; - BIGNUM *bp = NULL, *bq = NULL, *bg = NULL, *bpub_key = NULL; - - key->dsa = DSA_new(); - if (key->dsa == NULL) { - return SSH_ERROR; + if ((BN_sub(aux, bq, BN_value_one()) == 0) || + (BN_mod(bdmq1, d_consttime, aux, ctx) == 0) || + (BN_sub(aux, bp, BN_value_one()) == 0) || + (BN_mod(bdmp1, d_consttime, aux, ctx) == 0)) { + rc = SSH_ERROR; + goto fail; } - bp = ssh_make_string_bn(p); - bq = ssh_make_string_bn(q); - bg = ssh_make_string_bn(g); - bpub_key = ssh_make_string_bn(pubkey); - if (bp == NULL || bq == NULL || - bg == NULL || bpub_key == NULL) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* Memory management of be, bn and bd is transferred to RSA object */ + rc = RSA_set0_key(key_rsa, bn, be, bd); + if (rc == 0) { goto fail; } - /* Memory management of bp, bq and bg is transferred to DSA object */ - rc = DSA_set0_pqg(key->dsa, bp, bq, bg); + /* Memory management of bp and bq is transferred to RSA object */ + rc = RSA_set0_factors(key_rsa, bp, bq); if (rc == 0) { goto fail; } - /* Memory management of npub_key is transferred to DSA object */ - rc = DSA_set0_key(key->dsa, bpub_key, NULL); + /* p, q, dmp1, dmq1 and iqmp may be NULL in private keys, but the RSA + * operations are much faster when these values are available. + * https://www.openssl.org/docs/man1.0.2/crypto/rsa.html + * And OpenSSL fails to export these keys to PEM if these are missing: + * https://github.com/openssl/openssl/issues/21826 + */ + rc = RSA_set0_crt_params(key_rsa, bdmp1, bdmq1, biqmp); if (rc == 0) { goto fail; } + bignum_safe_free(aux); + bignum_safe_free(d_consttime); + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + goto fail; + } + + rc = EVP_PKEY_assign_RSA(key->key, key_rsa); + if (rc != 1) { + goto fail; + } return SSH_OK; fail: - DSA_free(key->dsa); + RSA_free(key_rsa); + EVP_PKEY_free(key->key); return SSH_ERROR; -} +#else + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_N, bn); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_E, be); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_D, bd); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } -int pki_privkey_build_rsa(ssh_key key, - ssh_string n, - ssh_string e, - ssh_string d, - UNUSED_PARAM(ssh_string iqmp), - ssh_string p, - ssh_string q) -{ - int rc; - BIGNUM *be, *bn, *bd/*, *biqmp*/, *bp, *bq; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_FACTOR1, bp); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } - key->rsa = RSA_new(); - if (key->rsa == NULL) { - return SSH_ERROR; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_FACTOR2, bq); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; } - bn = ssh_make_string_bn(n); - be = ssh_make_string_bn(e); - bd = ssh_make_string_bn(d); - /*biqmp = ssh_make_string_bn(iqmp);*/ - bp = ssh_make_string_bn(p); - bq = ssh_make_string_bn(q); - if (be == NULL || bn == NULL || bd == NULL || - /*biqmp == NULL ||*/ bp == NULL || bq == NULL) { + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_EXPONENT1, bdmp1); + if (rc != 1) { + rc = SSH_ERROR; goto fail; } - /* Memory management of be, bn and bd is transferred to RSA object */ - rc = RSA_set0_key(key->rsa, bn, be, bd); - if (rc == 0) { + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_EXPONENT2, bdmq1); + if (rc != 1) { + rc = SSH_ERROR; goto fail; } - /* Memory management of bp and bq is transferred to RSA object */ - rc = RSA_set0_factors(key->rsa, bp, bq); - if (rc == 0) { + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, biqmp); + if (rc != 1) { + rc = SSH_ERROR; goto fail; } - /* p, q, dmp1, dmq1 and iqmp may be NULL in private keys, but the RSA - * operations are much faster when these values are available. - * https://www.openssl.org/docs/man1.0.2/crypto/rsa.html - */ - /* RSA_set0_crt_params(key->rsa, biqmp, NULL, NULL); - TODO calculate missing crt_params */ + rc = evp_build_pkey("RSA", param_bld, &(key->key), EVP_PKEY_KEYPAIR); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to import private key: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + rc = SSH_ERROR; + goto fail; + } - return SSH_OK; fail: - RSA_free(key->rsa); - return SSH_ERROR; + OSSL_PARAM_BLD_free(param_bld); + bignum_safe_free(bn); + bignum_safe_free(be); + bignum_safe_free(bd); + bignum_safe_free(bp); + bignum_safe_free(bq); + bignum_safe_free(biqmp); + + bignum_safe_free(aux); + bignum_safe_free(d_consttime); + bignum_safe_free(bdmp1); + bignum_safe_free(bdmq1); + BN_CTX_free(ctx); + return rc; +#endif /* OPENSSL_VERSION_NUMBER */ } int pki_pubkey_build_rsa(ssh_key key, @@ -1155,31 +1339,71 @@ int pki_pubkey_build_rsa(ssh_key key, ssh_string n) { int rc; BIGNUM *be = NULL, *bn = NULL; - - key->rsa = RSA_new(); - if (key->rsa == NULL) { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) { return SSH_ERROR; } +#else + RSA *key_rsa = RSA_new(); + if (key_rsa == NULL) { + return SSH_ERROR; + } +#endif /* OPENSSL_VERSION_NUMBER */ be = ssh_make_string_bn(e); bn = ssh_make_string_bn(n); if (be == NULL || bn == NULL) { + rc = SSH_ERROR; goto fail; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L /* Memory management of bn and be is transferred to RSA object */ - rc = RSA_set0_key(key->rsa, bn, be, NULL); + rc = RSA_set0_key(key_rsa, bn, be, NULL); if (rc == 0) { goto fail; } + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + goto fail; + } + + rc = EVP_PKEY_assign_RSA(key->key, key_rsa); + if (rc != 1) { + goto fail; + } + return SSH_OK; fail: - RSA_free(key->rsa); + EVP_PKEY_free(key->key); + RSA_free(key_rsa); return SSH_ERROR; +#else + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_N, bn); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_E, be); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = evp_build_pkey("RSA", param_bld, &(key->key), EVP_PKEY_PUBLIC_KEY); + +fail: + OSSL_PARAM_BLD_free(param_bld); + bignum_safe_free(bn); + bignum_safe_free(be); + + return rc; +#endif /* OPENSSL_VERSION_NUMBER */ } -ssh_string pki_publickey_to_blob(const ssh_key key) +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) { ssh_buffer buffer; ssh_string type_s; @@ -1189,7 +1413,15 @@ ssh_string pki_publickey_to_blob(const ssh_key key) ssh_string p = NULL; ssh_string g = NULL; ssh_string q = NULL; + ssh_string d = NULL; + ssh_string iqmp = NULL; int rc; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + BIGNUM *bp = NULL, *bq = NULL, *bg = NULL, *bpub_key = NULL, + *bn = NULL, *be = NULL, + *bd = NULL, *biqmp = NULL; + OSSL_PARAM *params = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ buffer = ssh_buffer_new(); if (buffer == NULL) { @@ -1219,62 +1451,37 @@ ssh_string pki_publickey_to_blob(const ssh_key key) } switch (key->type) { - case SSH_KEYTYPE_DSS: { - const BIGNUM *bp, *bq, *bg, *bpub_key; - DSA_get0_pqg(key->dsa, &bp, &bq, &bg); - p = ssh_make_bignum_string((BIGNUM *)bp); - if (p == NULL) { - goto fail; - } - - q = ssh_make_bignum_string((BIGNUM *)bq); - if (q == NULL) { - goto fail; - } - - g = ssh_make_bignum_string((BIGNUM *)bg); - if (g == NULL) { - goto fail; - } - - DSA_get0_key(key->dsa, &bpub_key, NULL); - n = ssh_make_bignum_string((BIGNUM *)bpub_key); - if (n == NULL) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const BIGNUM *be = NULL, *bn = NULL; + const RSA *key_rsa = EVP_PKEY_get0_RSA(key->key); + RSA_get0_key(key_rsa, &bn, &be, NULL); +#else + const OSSL_PARAM *out_param = NULL; + rc = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, ¶ms); + if (rc != 1) { goto fail; } - - if (ssh_buffer_add_ssh_string(buffer, p) < 0) { + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_E); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param E has been found"); goto fail; } - if (ssh_buffer_add_ssh_string(buffer, q) < 0) { + rc = OSSL_PARAM_get_BN(out_param, &be); + if (rc != 1) { goto fail; } - if (ssh_buffer_add_ssh_string(buffer, g) < 0) { + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_N); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param N has been found"); goto fail; } - if (ssh_buffer_add_ssh_string(buffer, n) < 0) { + rc = OSSL_PARAM_get_BN(out_param, &bn); + if (rc != 1) { goto fail; } - - ssh_string_burn(p); - SSH_STRING_FREE(p); - p = NULL; - ssh_string_burn(g); - SSH_STRING_FREE(g); - g = NULL; - ssh_string_burn(q); - SSH_STRING_FREE(q); - q = NULL; - ssh_string_burn(n); - SSH_STRING_FREE(n); - n = NULL; - - break; - } - case SSH_KEYTYPE_RSA: - case SSH_KEYTYPE_RSA1: { - const BIGNUM *be, *bn; - RSA_get0_key(key->rsa, &bn, &be, NULL); +#endif /* OPENSSL_VERSION_NUMBER */ e = ssh_make_bignum_string((BIGNUM *)be); if (e == NULL) { goto fail; @@ -1285,31 +1492,165 @@ ssh_string pki_publickey_to_blob(const ssh_key key) goto fail; } - if (ssh_buffer_add_ssh_string(buffer, e) < 0) { - goto fail; - } - if (ssh_buffer_add_ssh_string(buffer, n) < 0) { - goto fail; - } + if (type == SSH_KEY_PUBLIC) { + /* The N and E parts are swapped in the public key export ! */ + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + } else if (type == SSH_KEY_PRIVATE) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const BIGNUM *bd, *biqmp, *bp, *bq; + RSA_get0_key(key_rsa, NULL, NULL, &bd); + RSA_get0_factors(key_rsa, &bp, &bq); + RSA_get0_crt_params(key_rsa, NULL, NULL, &biqmp); +#else + rc = EVP_PKEY_todata(key->key, EVP_PKEY_KEYPAIR, ¶ms); + if (rc != 1) { + goto fail; + } + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_D); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param D has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bd); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_FACTOR1); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param P has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bp); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_FACTOR2); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param Q has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bq); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_COEFFICIENT1); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param IQMP has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &biqmp); + if (rc != 1) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + d = ssh_make_bignum_string((BIGNUM *)bd); + if (d == NULL) { + goto fail; + } + + iqmp = ssh_make_bignum_string((BIGNUM *)biqmp); + if (iqmp == NULL) { + goto fail; + } + + p = ssh_make_bignum_string((BIGNUM *)bp); + if (p == NULL) { + goto fail; + } + + q = ssh_make_bignum_string((BIGNUM *)bq); + if (q == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, iqmp); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, q); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(d); + SSH_STRING_FREE(d); + d = NULL; + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); + iqmp = NULL; + ssh_string_burn(p); + SSH_STRING_FREE(p); + p = NULL; + ssh_string_burn(q); + SSH_STRING_FREE(q); + q = NULL; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bd); + bignum_safe_free(biqmp); + bignum_safe_free(bp); + bignum_safe_free(bq); +#endif /* OPENSSL_VERSION_NUMBER */ + } ssh_string_burn(e); SSH_STRING_FREE(e); e = NULL; ssh_string_burn(n); SSH_STRING_FREE(n); n = NULL; - +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bn); + bignum_safe_free(be); + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ break; } case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: - rc = pki_ed25519_public_key_to_blob(buffer, key); - if (rc == SSH_ERROR){ - goto fail; - } - if (key->type == SSH_KEYTYPE_SK_ED25519 && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; + if (type == SSH_KEY_PUBLIC) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR){ + goto fail; + } + /* public key can contain certificate sk information */ + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto fail; + } + } + } else { + rc = pki_ed25519_private_key_to_blob(buffer, key); + if (rc == SSH_ERROR){ + goto fail; + } } break; case SSH_KEYTYPE_ECDSA_P256: @@ -1317,50 +1658,150 @@ ssh_string pki_publickey_to_blob(const ssh_key key) case SSH_KEYTYPE_ECDSA_P521: case SSH_KEYTYPE_SK_ECDSA: #ifdef HAVE_OPENSSL_ECC - type_s = ssh_string_from_char(pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); - if (type_s == NULL) { - SSH_BUFFER_FREE(buffer); - return NULL; - } + { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EC_GROUP *group = NULL; + EC_POINT *point = NULL; + const void *pubkey; + size_t pubkey_len; + OSSL_PARAM *locate_param = NULL; +#else + const EC_GROUP *group = NULL; + const EC_POINT *point = NULL; + const BIGNUM *exp = NULL; + EC_KEY *ec = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + + type_s = ssh_string_from_char(pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); + if (type_s == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } - rc = ssh_buffer_add_ssh_string(buffer, type_s); - SSH_STRING_FREE(type_s); - if (rc < 0) { - SSH_BUFFER_FREE(buffer); - return NULL; - } + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ec = EVP_PKEY_get0_EC_KEY(key->key); + if (ec == NULL) { + goto fail; + } #ifdef WITH_PKCS11_URI - if (ssh_key_is_private(key) && !EC_KEY_get0_public_key(key->ecdsa)) { - SSH_LOG(SSH_LOG_INFO, "It is mandatory to have separate public" - " ECDSA key objects in the PKCS #11 device. Unlike RSA," - " ECDSA public keys cannot be derived from their private keys."); - goto fail; - } -#endif - e = make_ecpoint_string(EC_KEY_get0_group(key->ecdsa), - EC_KEY_get0_public_key(key->ecdsa)); - if (e == NULL) { - SSH_BUFFER_FREE(buffer); - return NULL; - } + if (ssh_key_is_private(key) && !EC_KEY_get0_public_key(ec)) { + SSH_LOG(SSH_LOG_TRACE, "It is mandatory to have separate" + " public ECDSA key objects in the PKCS #11 device." + " Unlike RSA, ECDSA public keys cannot be derived" + " from their private keys."); + goto fail; + } +#endif /* WITH_PKCS11_URI */ + group = EC_KEY_get0_group(ec); + point = EC_KEY_get0_public_key(ec); + if (group == NULL || point == NULL) { + goto fail; + } + e = pki_key_make_ecpoint_string(group, point); +#else + rc = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, ¶ms); + if (rc < 0) { + goto fail; + } - rc = ssh_buffer_add_ssh_string(buffer, e); - if (rc < 0) { - goto fail; - } + locate_param = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PUB_KEY); +#ifdef WITH_PKCS11_URI + if (ssh_key_is_private(key) && !locate_param) { + SSH_LOG(SSH_LOG_TRACE, "It is mandatory to have separate" + " public ECDSA key objects in the PKCS #11 device." + " Unlike RSA, ECDSA public keys cannot be derived" + " from their private keys."); + goto fail; + } +#endif /* WITH_PKCS11_URI */ - ssh_string_burn(e); - SSH_STRING_FREE(e); - e = NULL; + rc = OSSL_PARAM_get_octet_string_ptr(locate_param, &pubkey, &pubkey_len); + if (rc != 1) { + goto fail; + } + /* Convert the data to low-level representation */ + group = EC_GROUP_new_by_curve_name_ex(NULL, NULL, key->ecdsa_nid); + point = EC_POINT_new(group); + rc = EC_POINT_oct2point(group, point, pubkey, pubkey_len, NULL); + if (group == NULL || point == NULL || rc != 1) { + EC_GROUP_free(group); + EC_POINT_free(point); + goto fail; + } - if (key->type == SSH_KEYTYPE_SK_ECDSA && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; - } + e = pki_key_make_ecpoint_string(group, point); + EC_GROUP_free(group); + EC_POINT_free(point); +#endif /* OPENSSL_VERSION_NUMBER */ + if (e == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } - break; -#endif + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(e); + SSH_STRING_FREE(e); + e = NULL; + if (type == SSH_KEY_PRIVATE) { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + rc = EVP_PKEY_todata(key->key, EVP_PKEY_KEYPAIR, ¶ms); + if (rc < 0) { + goto fail; + } + + locate_param = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PRIV_KEY); + rc = OSSL_PARAM_get_BN(locate_param, &bd); + if (rc != 1) { + goto fail; + } + d = ssh_make_bignum_string((BIGNUM *)bd); + if (d == NULL) { + goto fail; + } + if (ssh_buffer_add_ssh_string(buffer, d) < 0) { + goto fail; + } +#else + exp = EC_KEY_get0_private_key(ec); + if (exp == NULL) { + goto fail; + } + d = ssh_make_bignum_string((BIGNUM *)exp); + if (d == NULL) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + ssh_string_burn(d); + SSH_STRING_FREE(d); + d = NULL; + } else if (key->type == SSH_KEYTYPE_SK_ECDSA) { + /* public key can contain certificate sk information */ + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto fail; + } + } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ + break; + } +#endif /* HAVE_OPENSSL_ECC */ case SSH_KEYTYPE_UNKNOWN: default: goto fail; @@ -1393,91 +1834,25 @@ fail: SSH_STRING_FREE(q); ssh_string_burn(n); SSH_STRING_FREE(n); + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bp); + bignum_safe_free(bq); + bignum_safe_free(bg); + bignum_safe_free(bpub_key); + bignum_safe_free(bn); + bignum_safe_free(be); + bignum_safe_free(bd); + bignum_safe_free(biqmp); + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ return NULL; } -static ssh_string pki_dsa_signature_to_blob(const ssh_signature sig) -{ - char buffer[40] = { 0 }; - ssh_string sig_blob = NULL; - const BIGNUM *pr = NULL, *ps = NULL; - - ssh_string r = NULL; - int r_len, r_offset_in, r_offset_out; - - ssh_string s = NULL; - int s_len, s_offset_in, s_offset_out; - - const unsigned char *raw_sig_data = NULL; - size_t raw_sig_len; - - DSA_SIG *dsa_sig; - - if (sig == NULL || sig->raw_sig == NULL) { - return NULL; - } - raw_sig_data = ssh_string_data(sig->raw_sig); - if (raw_sig_data == NULL) { - return NULL; - } - raw_sig_len = ssh_string_len(sig->raw_sig); - - dsa_sig = d2i_DSA_SIG(NULL, &raw_sig_data, raw_sig_len); - if (dsa_sig == NULL) { - return NULL; - } - - DSA_SIG_get0(dsa_sig, &pr, &ps); - if (pr == NULL || ps == NULL) { - goto error; - } - - r = ssh_make_bignum_string((BIGNUM *)pr); - if (r == NULL) { - goto error; - } - - s = ssh_make_bignum_string((BIGNUM *)ps); - if (s == NULL) { - goto error; - } - - r_len = ssh_string_len(r); - r_offset_in = (r_len > 20) ? (r_len - 20) : 0; - r_offset_out = (r_len < 20) ? (20 - r_len) : 0; - - s_len = ssh_string_len(s); - s_offset_in = (s_len > 20) ? (s_len - 20) : 0; - s_offset_out = (s_len < 20) ? (20 - s_len) : 0; - - memcpy(buffer + r_offset_out, - ((char *)ssh_string_data(r)) + r_offset_in, - r_len - r_offset_in); - memcpy(buffer + 20 + s_offset_out, - ((char *)ssh_string_data(s)) + s_offset_in, - s_len - s_offset_in); - - DSA_SIG_free(dsa_sig); - SSH_STRING_FREE(r); - SSH_STRING_FREE(s); - - sig_blob = ssh_string_new(40); - if (sig_blob == NULL) { - return NULL; - } - - ssh_string_fill(sig_blob, buffer, 40); - - return sig_blob; - -error: - DSA_SIG_free(dsa_sig); - SSH_STRING_FREE(r); - SSH_STRING_FREE(s); - return NULL; -} - static ssh_string pki_ecdsa_signature_to_blob(const ssh_signature sig) { ssh_string r = NULL; @@ -1544,7 +1919,10 @@ static ssh_string pki_ecdsa_signature_to_blob(const ssh_signature sig) goto error; } - ssh_string_fill(sig_blob, ssh_buffer_get(buf), ssh_buffer_get_len(buf)); + rc = ssh_string_fill(sig_blob, ssh_buffer_get(buf), ssh_buffer_get_len(buf)); + if (rc < 0) { + goto error; + } SSH_STRING_FREE(r); SSH_STRING_FREE(s); @@ -1554,6 +1932,7 @@ static ssh_string pki_ecdsa_signature_to_blob(const ssh_signature sig) return sig_blob; error: + SSH_STRING_FREE(sig_blob); SSH_STRING_FREE(r); SSH_STRING_FREE(s); ECDSA_SIG_free(ecdsa_sig); @@ -1566,9 +1945,6 @@ ssh_string pki_signature_to_blob(const ssh_signature sig) ssh_string sig_blob = NULL; switch(sig->type) { - case SSH_KEYTYPE_DSS: - sig_blob = pki_dsa_signature_to_blob(sig); - break; case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: sig_blob = ssh_string_copy(sig->raw_sig); @@ -1582,10 +1958,10 @@ ssh_string pki_signature_to_blob(const ssh_signature sig) #ifdef HAVE_OPENSSL_ECC sig_blob = pki_ecdsa_signature_to_blob(sig); break; -#endif +#endif /* HAVE_OPENSSL_ECC */ default: case SSH_KEYTYPE_UNKNOWN: - SSH_LOG(SSH_LOG_WARN, "Unknown signature key type: %s", sig->type_c); + SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %s", sig->type_c); return NULL; } @@ -1604,14 +1980,25 @@ static int pki_signature_from_rsa_blob(const ssh_key pubkey, size_t rsalen = 0; size_t len = ssh_string_len(sig_blob); - if (pubkey->rsa == NULL) { - SSH_LOG(SSH_LOG_WARN, "Pubkey RSA field NULL"); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const RSA *rsa = EVP_PKEY_get0_RSA(pubkey->key); + + if (rsa == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA field NULL"); + goto errout; + } + + rsalen = RSA_size(rsa); +#else + if (EVP_PKEY_get_base_id(pubkey->key) != EVP_PKEY_RSA) { + SSH_LOG(SSH_LOG_TRACE, "Key has no RSA pubkey"); goto errout; } - rsalen = RSA_size(pubkey->rsa); + rsalen = EVP_PKEY_size(pubkey->key); +#endif /* OPENSSL_VERSION_NUMBER */ if (len > rsalen) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Signature is too big: %lu > %lu", (unsigned long)len, (unsigned long)rsalen); @@ -1619,9 +2006,9 @@ static int pki_signature_from_rsa_blob(const ssh_key pubkey, } #ifdef DEBUG_CRYPTO - SSH_LOG(SSH_LOG_WARN, "RSA signature len: %lu", (unsigned long)len); + SSH_LOG(SSH_LOG_DEBUG, "RSA signature len: %lu", (unsigned long)len); ssh_log_hexdump("RSA signature", ssh_string_data(sig_blob), len); -#endif +#endif /* DEBUG_CRYPTO */ if (len == rsalen) { sig->raw_sig = ssh_string_copy(sig_blob); @@ -1661,125 +2048,6 @@ errout: return SSH_ERROR; } -static int pki_signature_from_dsa_blob(UNUSED_PARAM(const ssh_key pubkey), - const ssh_string sig_blob, - ssh_signature sig) -{ - DSA_SIG *dsa_sig = NULL; - BIGNUM *pr = NULL, *ps = NULL; - - ssh_string r; - ssh_string s; - - size_t len; - - int raw_sig_len = 0; - unsigned char *raw_sig_data = NULL; - unsigned char *temp_raw_sig = NULL; - - int rc; - - len = ssh_string_len(sig_blob); - - /* 40 is the dual signature blob len. */ - if (len != 40) { - SSH_LOG(SSH_LOG_WARN, - "Signature has wrong size: %lu", - (unsigned long)len); - goto error; - } - -#ifdef DEBUG_CRYPTO - ssh_log_hexdump("r", ssh_string_data(sig_blob), 20); - ssh_log_hexdump("s", (unsigned char *)ssh_string_data(sig_blob) + 20, 20); -#endif - - r = ssh_string_new(20); - if (r == NULL) { - goto error; - } - ssh_string_fill(r, ssh_string_data(sig_blob), 20); - - pr = ssh_make_string_bn(r); - ssh_string_burn(r); - SSH_STRING_FREE(r); - if (pr == NULL) { - goto error; - } - - s = ssh_string_new(20); - if (s == NULL) { - goto error; - } - ssh_string_fill(s, (char *)ssh_string_data(sig_blob) + 20, 20); - - ps = ssh_make_string_bn(s); - ssh_string_burn(s); - SSH_STRING_FREE(s); - if (ps == NULL) { - goto error; - } - - dsa_sig = DSA_SIG_new(); - if (dsa_sig == NULL) { - goto error; - } - - /* Memory management of pr and ps is transferred to DSA signature - * object */ - rc = DSA_SIG_set0(dsa_sig, pr, ps); - if (rc == 0) { - goto error; - } - ps = NULL; - pr = NULL; - - /* Get the expected size of the buffer */ - rc = i2d_DSA_SIG(dsa_sig, NULL); - if (rc <= 0) { - goto error; - } - raw_sig_len = rc; - - raw_sig_data = (unsigned char *)calloc(1, raw_sig_len); - if (raw_sig_data == NULL) { - goto error; - } - temp_raw_sig = raw_sig_data; - - /* It is necessary to use a temporary pointer as i2d_* "advances" the - * pointer */ - raw_sig_len = i2d_DSA_SIG(dsa_sig, &temp_raw_sig); - if (raw_sig_len <= 0) { - goto error; - } - - sig->raw_sig = ssh_string_new(raw_sig_len); - if (sig->raw_sig == NULL) { - explicit_bzero(raw_sig_data, raw_sig_len); - goto error; - } - - rc = ssh_string_fill(sig->raw_sig, raw_sig_data, raw_sig_len); - if (rc < 0) { - explicit_bzero(raw_sig_data, raw_sig_len); - goto error; - } - - explicit_bzero(raw_sig_data, raw_sig_len); - SAFE_FREE(raw_sig_data); - DSA_SIG_free(dsa_sig); - - return SSH_OK; - -error: - bignum_safe_free(ps); - bignum_safe_free(pr); - SAFE_FREE(raw_sig_data); - DSA_SIG_free(dsa_sig); - return SSH_ERROR; -} - static int pki_signature_from_ecdsa_blob(UNUSED_PARAM(const ssh_key pubkey), const ssh_string sig_blob, ssh_signature sig) @@ -1838,7 +2106,7 @@ static int pki_signature_from_ecdsa_blob(UNUSED_PARAM(const ssh_key pubkey), if (rlen != 0) { ssh_string_burn(s); SSH_STRING_FREE(s); - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Signature has remaining bytes in inner " "sigblob: %lu", (unsigned long)rlen); @@ -1927,7 +2195,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, int rc; if (ssh_key_type_plain(pubkey->type) != type) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Incompatible public key provided (%d) expecting (%d)", type, pubkey->type); @@ -1944,12 +2212,6 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, sig->hash_type = hash_type; switch(type) { - case SSH_KEYTYPE_DSS: - rc = pki_signature_from_dsa_blob(pubkey, sig_blob, sig); - if (rc != SSH_OK) { - goto error; - } - break; case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: rc = pki_signature_from_rsa_blob(pubkey, sig_blob, sig); @@ -1981,7 +2243,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, #endif default: case SSH_KEYTYPE_UNKNOWN: - SSH_LOG(SSH_LOG_WARN, "Unknown signature type"); + SSH_LOG(SSH_LOG_TRACE, "Unknown signature type"); goto error; } @@ -2024,44 +2286,12 @@ static const EVP_MD *pki_digest_to_md(enum ssh_digest_e hash_type) static EVP_PKEY *pki_key_to_pkey(ssh_key key) { EVP_PKEY *pkey = NULL; + int rc = 0; -#ifdef WITH_PKCS11_URI - if (key->flags & SSH_KEY_FLAG_PKCS11_URI) { - pkey = key->key; - return pkey; - } -#endif - - switch(key->type) { - case SSH_KEYTYPE_DSS: - case SSH_KEYTYPE_DSS_CERT01: - if (key->dsa == NULL) { - SSH_LOG(SSH_LOG_TRACE, "NULL key->dsa"); - goto error; - } - pkey = EVP_PKEY_new(); - if (pkey == NULL) { - SSH_LOG(SSH_LOG_TRACE, "Out of memory"); - return NULL; - } - - EVP_PKEY_set1_DSA(pkey, key->dsa); - break; + switch (key->type) { case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_RSA_CERT01: - if (key->rsa == NULL) { - SSH_LOG(SSH_LOG_TRACE, "NULL key->rsa"); - goto error; - } - pkey = EVP_PKEY_new(); - if (pkey == NULL) { - SSH_LOG(SSH_LOG_TRACE, "Out of memory"); - return NULL; - } - - EVP_PKEY_set1_RSA(pkey, key->rsa); - break; case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: @@ -2070,25 +2300,21 @@ static EVP_PKEY *pki_key_to_pkey(ssh_key key) case SSH_KEYTYPE_ECDSA_P521_CERT01: case SSH_KEYTYPE_SK_ECDSA: case SSH_KEYTYPE_SK_ECDSA_CERT01: -# if defined(HAVE_OPENSSL_ECC) - if (key->ecdsa == NULL) { - SSH_LOG(SSH_LOG_TRACE, "NULL key->ecdsa"); + if (key->key == NULL) { + SSH_LOG(SSH_LOG_TRACE, "NULL key->key"); goto error; } - pkey = EVP_PKEY_new(); - if (pkey == NULL) { - SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to reference EVP_PKEY"); return NULL; } - - EVP_PKEY_set1_EC_KEY(pkey, key->ecdsa); + pkey = key->key; break; -# endif case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_ED25519_CERT01: case SSH_KEYTYPE_SK_ED25519: case SSH_KEYTYPE_SK_ED25519_CERT01: -# if defined(HAVE_OPENSSL_ED25519) if (ssh_key_is_private(key)) { if (key->ed25519_privkey == NULL) { SSH_LOG(SSH_LOG_TRACE, "NULL key->ed25519_privkey"); @@ -2116,7 +2342,6 @@ static EVP_PKEY *pki_key_to_pkey(ssh_key key) return NULL; } break; -#endif case SSH_KEYTYPE_UNKNOWN: default: SSH_LOG(SSH_LOG_TRACE, "Unknown private key algorithm for type: %d", @@ -2134,7 +2359,7 @@ error: /** * @internal * - * @brief Sign the given input data. The digest of to be signed is calculated + * @brief Sign the given input data. The digest to be signed is calculated * internally as necessary. * * @param[in] privkey The private key to be used for signing. @@ -2172,14 +2397,6 @@ ssh_signature pki_sign_data(const ssh_key privkey, return NULL; } -#ifndef HAVE_OPENSSL_ED25519 - if (privkey->type == SSH_KEYTYPE_ED25519 || - privkey->type == SSH_KEYTYPE_ED25519_CERT01) - { - return pki_do_sign_hash(privkey, input, input_len, hash_type); - } -#endif - /* Set hash algorithm to be used */ md = pki_digest_to_md(hash_type); if (md == NULL) { @@ -2203,7 +2420,7 @@ ssh_signature pki_sign_data(const ssh_key privkey, } /* Create the context */ - ctx = EVP_MD_CTX_create(); + ctx = EVP_MD_CTX_new(); if (ctx == NULL) { SSH_LOG(SSH_LOG_TRACE, "Out of memory"); goto out; @@ -2218,7 +2435,6 @@ ssh_signature pki_sign_data(const ssh_key privkey, goto out; } -#ifdef HAVE_OPENSSL_EVP_DIGESTSIGN rc = EVP_DigestSign(ctx, raw_sig_data, &raw_sig_len, input, input_len); if (rc != 1) { SSH_LOG(SSH_LOG_TRACE, @@ -2226,23 +2442,6 @@ ssh_signature pki_sign_data(const ssh_key privkey, ERR_error_string(ERR_get_error(), NULL)); goto out; } -#else - rc = EVP_DigestSignUpdate(ctx, input, input_len); - if (rc != 1) { - SSH_LOG(SSH_LOG_TRACE, - "EVP_DigestSignUpdate() failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - goto out; - } - - rc = EVP_DigestSignFinal(ctx, raw_sig_data, &raw_sig_len); - if (rc != 1) { - SSH_LOG(SSH_LOG_TRACE, - "EVP_DigestSignFinal() failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - goto out; - } -#endif #ifdef DEBUG_CRYPTO ssh_log_hexdump("Generated signature", raw_sig_data, raw_sig_len); @@ -2280,9 +2479,7 @@ out: explicit_bzero(raw_sig_data, raw_sig_len); } SAFE_FREE(raw_sig_data); - if (pkey != NULL) { - EVP_PKEY_free(pkey); - } + EVP_PKEY_free(pkey); return sig; } @@ -2311,15 +2508,15 @@ int pki_verify_data_signature(ssh_signature signature, unsigned char *raw_sig_data = NULL; unsigned int raw_sig_len; + /* Function return code + * Do not change this variable throughout the function until the signature + * is successfully verified! + */ int rc = SSH_ERROR; - int evp_rc; + int ok; if (pubkey == NULL || ssh_key_is_private(pubkey) || input == NULL || - signature == NULL || (signature->raw_sig == NULL -#ifndef HAVE_OPENSSL_ED25519 - && signature->ed25519_sig == NULL -#endif - )) + signature == NULL || signature->raw_sig == NULL) { SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " "pki_verify_data_signature()"); @@ -2327,21 +2524,11 @@ int pki_verify_data_signature(ssh_signature signature, } /* Check if public key and hash type are compatible */ - rc = pki_key_check_hash_compatible(pubkey, signature->hash_type); - if (rc != SSH_OK) { + ok = pki_key_check_hash_compatible(pubkey, signature->hash_type); + if (ok != SSH_OK) { return SSH_ERROR; } -#ifndef HAVE_OPENSSL_ED25519 - if (pubkey->type == SSH_KEYTYPE_ED25519 || - pubkey->type == SSH_KEYTYPE_ED25519_CERT01 || - pubkey->type == SSH_KEYTYPE_SK_ED25519 || - pubkey->type == SSH_KEYTYPE_SK_ED25519_CERT01) - { - return pki_ed25519_verify(pubkey, signature, input, input_len); - } -#endif - /* Get the signature to be verified */ raw_sig_data = ssh_string_data(signature->raw_sig); raw_sig_len = ssh_string_len(signature->raw_sig); @@ -2364,7 +2551,7 @@ int pki_verify_data_signature(ssh_signature signature, } /* Create the context */ - ctx = EVP_MD_CTX_create(); + ctx = EVP_MD_CTX_new(); if (ctx == NULL) { SSH_LOG(SSH_LOG_TRACE, "Failed to create EVP_MD_CTX: %s", @@ -2373,48 +2560,69 @@ int pki_verify_data_signature(ssh_signature signature, } /* Verify the signature */ - evp_rc = EVP_DigestVerifyInit(ctx, NULL, md, NULL, pkey); - if (evp_rc != 1){ + ok = EVP_DigestVerifyInit(ctx, NULL, md, NULL, pkey); + if (ok != 1){ SSH_LOG(SSH_LOG_TRACE, "EVP_DigestVerifyInit() failed: %s", ERR_error_string(ERR_get_error(), NULL)); goto out; } -#ifdef HAVE_OPENSSL_EVP_DIGESTVERIFY - evp_rc = EVP_DigestVerify(ctx, raw_sig_data, raw_sig_len, input, input_len); -#else - evp_rc = EVP_DigestVerifyUpdate(ctx, input, input_len); - if (evp_rc != 1) { + ok = EVP_DigestVerify(ctx, raw_sig_data, raw_sig_len, input, input_len); + if (ok != 1) { SSH_LOG(SSH_LOG_TRACE, - "EVP_DigestVerifyUpdate() failed: %s", + "Signature invalid: %s", ERR_error_string(ERR_get_error(), NULL)); goto out; } - evp_rc = EVP_DigestVerifyFinal(ctx, raw_sig_data, raw_sig_len); -#endif - if (evp_rc == 1) { - SSH_LOG(SSH_LOG_TRACE, "Signature valid"); - rc = SSH_OK; - } else { - SSH_LOG(SSH_LOG_TRACE, - "Signature invalid: %s", - ERR_error_string(ERR_get_error(), NULL)); - rc = SSH_ERROR; - } + SSH_LOG(SSH_LOG_TRACE, "Signature valid"); + rc = SSH_OK; out: - if (ctx != NULL) { - EVP_MD_CTX_free(ctx); - } - if (pkey != NULL) { + EVP_MD_CTX_free(ctx); + EVP_PKEY_free(pkey); + return rc; +} + +int ssh_key_size(ssh_key key) +{ + int bits = 0; + EVP_PKEY *pkey = NULL; + + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + pkey = pki_key_to_pkey(key); + if (pkey == NULL) { + return SSH_ERROR; + } + bits = EVP_PKEY_bits(pkey); EVP_PKEY_free(pkey); + return bits; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys have fixed size */ + return 255; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_UNKNOWN: + default: + return SSH_ERROR; } - return rc; } -#ifdef HAVE_OPENSSL_ED25519 int pki_key_generate_ed25519(ssh_key key) { int evp_rc; @@ -2499,50 +2707,21 @@ error: return SSH_ERROR; } -#else -ssh_signature pki_do_sign_hash(const ssh_key privkey, - const unsigned char *hash, - size_t hlen, - enum ssh_digest_e hash_type) -{ - ssh_signature sig = NULL; - int rc; - - sig = ssh_signature_new(); - if (sig == NULL) { - return NULL; - } - - sig->type = privkey->type; - sig->type_c = ssh_key_signature_to_char(privkey->type, hash_type); - sig->hash_type = hash_type; - switch(privkey->type) { - case SSH_KEYTYPE_ED25519: - rc = pki_ed25519_sign(privkey, sig, hash, hlen); - if (rc != SSH_OK) { - ssh_signature_free(sig); - return NULL; - } - break; - default: - ssh_signature_free(sig); - return NULL; - } - - return sig; -} -#endif /* HAVE_OPENSSL_ED25519 */ +#ifdef WITH_PKCS11_URI +#ifdef WITH_PKCS11_PROVIDER +static bool pkcs11_provider_failed = false; +#endif /** * @internal * - * @brief Populate the public/private ssh_key from the engine with + * @brief Populate the public/private ssh_key from the engine/provider with * PKCS#11 URIs as the look up. * * @param[in] uri_name The PKCS#11 URI * @param[in] nkey The ssh-key context for - * the key loaded from the engine. + * the key loaded from the engine/provider. * @param[in] key_type The type of the key used. Public/Private. * * @return SSH_OK if ssh-key is valid; SSH_ERROR otherwise. @@ -2551,64 +2730,112 @@ int pki_uri_import(const char *uri_name, ssh_key *nkey, enum ssh_key_e key_type) { - ENGINE *engine = NULL; EVP_PKEY *pkey = NULL; - RSA *rsa = NULL; ssh_key key = NULL; enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; -#ifdef HAVE_OPENSSL_ECC +#if OPENSSL_VERSION_NUMBER < 0x30000000L && HAVE_OPENSSL_ECC EC_KEY *ecdsa = NULL; -#else - void *ecdsa = NULL; #endif - int ok; - - ENGINE_load_builtin_engines(); +#ifndef WITH_PKCS11_PROVIDER + ENGINE *engine = NULL; - engine = ENGINE_by_id("pkcs11"); + /* Do the init only once */ + engine = pki_get_engine(); if (engine == NULL) { - SSH_LOG(SSH_LOG_WARN, - "Could not load the engine: %s", - ERR_error_string(ERR_get_error(),NULL)); - return SSH_ERROR; - } - SSH_LOG(SSH_LOG_INFO, "Engine loaded successfully"); - - ok = ENGINE_init(engine); - if (!ok) { - SSH_LOG(SSH_LOG_WARN, - "Could not initialize the engine: %s", - ERR_error_string(ERR_get_error(),NULL)); - ENGINE_free(engine); - return SSH_ERROR; + SSH_LOG(SSH_LOG_TRACE, "Failed to initialize engine"); + goto fail; } - SSH_LOG(SSH_LOG_INFO, "Engine init success"); - switch (key_type) { case SSH_KEY_PRIVATE: pkey = ENGINE_load_private_key(engine, uri_name, NULL, NULL); if (pkey == NULL) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Could not load key: %s", - ERR_error_string(ERR_get_error(),NULL)); + ERR_error_string(ERR_get_error(), NULL)); goto fail; } break; case SSH_KEY_PUBLIC: pkey = ENGINE_load_public_key(engine, uri_name, NULL, NULL); if (pkey == NULL) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Could not load key: %s", - ERR_error_string(ERR_get_error(),NULL)); + ERR_error_string(ERR_get_error(), NULL)); goto fail; } break; default: - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Invalid key type: %d", key_type); goto fail; } +#else /* WITH_PKCS11_PROVIDER */ + OSSL_STORE_CTX *store = NULL; + OSSL_STORE_INFO *info = NULL; + int rv, expect_type = OSSL_STORE_INFO_PKEY; + + /* The provider can be either configured in openssl.cnf or dynamically + * loaded, assuming it does not need any special configuration */ + if (OSSL_PROVIDER_available(NULL, "pkcs11") == 0 && + !pkcs11_provider_failed) { + OSSL_PROVIDER *pkcs11_provider = NULL; + + pkcs11_provider = OSSL_PROVIDER_try_load(NULL, "pkcs11", 1); + if (pkcs11_provider == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize provider: %s", + ERR_error_string(ERR_get_error(), NULL)); + /* Do not attempt to load it again */ + pkcs11_provider_failed = true; + goto fail; + } + } + + store = OSSL_STORE_open(uri_name, NULL, NULL, NULL, NULL); + if (store == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to open OpenSSL store: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + if (key_type == SSH_KEY_PUBLIC) { + expect_type = OSSL_STORE_INFO_PUBKEY; + } + rv = OSSL_STORE_expect(store, expect_type); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to set the store preference. Ignoring the error: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + for (info = OSSL_STORE_load(store); + info != NULL; + info = OSSL_STORE_load(store)) { + int ossl_type = OSSL_STORE_INFO_get_type(info); + + if (ossl_type == OSSL_STORE_INFO_PUBKEY && key_type == SSH_KEY_PUBLIC) { + pkey = OSSL_STORE_INFO_get1_PUBKEY(info); + break; + } else if (ossl_type == OSSL_STORE_INFO_PKEY && + key_type == SSH_KEY_PRIVATE) { + pkey = OSSL_STORE_INFO_get1_PKEY(info); + break; + } else { + SSH_LOG(SSH_LOG_TRACE, + "Ignoring object not matching our type: %d", + ossl_type); + } + } + OSSL_STORE_close(store); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "No key found in the pkcs11 store: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + +#endif /* WITH_PKCS11_PROVIDER */ key = ssh_key_new(); if (key == NULL) { @@ -2617,20 +2844,14 @@ int pki_uri_import(const char *uri_name, switch (EVP_PKEY_base_id(pkey)) { case EVP_PKEY_RSA: - rsa = EVP_PKEY_get1_RSA(pkey); - if (rsa == NULL) { - SSH_LOG(SSH_LOG_WARN, - "Parsing pub key: %s", - ERR_error_string(ERR_get_error(),NULL)); - goto fail; - } type = SSH_KEYTYPE_RSA; break; case EVP_PKEY_EC: #ifdef HAVE_OPENSSL_ECC - ecdsa = EVP_PKEY_get1_EC_KEY(pkey); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EVP_PKEY_get0_EC_KEY(pkey); if (ecdsa == NULL) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Parsing pub key: %s", ERR_error_string(ERR_get_error(), NULL)); goto fail; @@ -2639,15 +2860,18 @@ int pki_uri_import(const char *uri_name, /* pki_privatekey_type_from_string always returns P256 for ECDSA * keys, so we need to figure out the correct type here */ type = pki_key_ecdsa_to_key_type(ecdsa); +#else + type = pki_key_ecdsa_to_key_type(pkey); +#endif /* OPENSSL_VERSION_NUMBER */ if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "Invalid pub key."); + SSH_LOG(SSH_LOG_TRACE, "Invalid pub key."); goto fail; } break; #endif default: - SSH_LOG(SSH_LOG_WARN, "Unknown or invalid public key type %d", + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid public key type %d", EVP_PKEY_base_id(pkey)); goto fail; } @@ -2655,36 +2879,30 @@ int pki_uri_import(const char *uri_name, key->key = pkey; key->type = type; key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PUBLIC | SSH_KEY_FLAG_PKCS11_URI; if (key_type == SSH_KEY_PRIVATE) { - key->flags = SSH_KEY_FLAG_PUBLIC | SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PKCS11_URI; - } else { - key->flags = SSH_KEY_FLAG_PUBLIC | SSH_KEY_FLAG_PKCS11_URI; + key->flags |= SSH_KEY_FLAG_PRIVATE; } - key->rsa = rsa; - key->ecdsa = ecdsa; #ifdef HAVE_OPENSSL_ECC if (is_ecdsa_key_type(key->type)) { - key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + key->ecdsa_nid = pki_key_ecdsa_to_nid(ecdsa); +#else + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->key); +#endif /* OPENSSL_VERSION_NUMBER */ } #endif *nkey = key; - ENGINE_finish(engine); - ENGINE_free(engine); return SSH_OK; fail: - ENGINE_finish(engine); - ENGINE_free(engine); EVP_PKEY_free(pkey); ssh_key_free(key); - RSA_free(rsa); -#ifdef HAVE_OPENSSL_ECC - EC_KEY_free(ecdsa); -#endif return SSH_ERROR; } +#endif /* WITH_PKCS11_URI */ #endif /* _PKI_CRYPTO_H */ diff --git a/src/pki_ed25519.c b/src/pki_ed25519.c index fdf94b4e..6a5a4a8a 100644 --- a/src/pki_ed25519.c +++ b/src/pki_ed25519.c @@ -1,5 +1,5 @@ /* - * pki_ed25519 .c - PKI infrastructure using ed25519 + * pki_ed25519.c - PKI infrastructure using ed25519 * * This file is part of the SSH Library * diff --git a/src/pki_ed25519_common.c b/src/pki_ed25519_common.c index 9db14dac..03859f7c 100644 --- a/src/pki_ed25519_common.c +++ b/src/pki_ed25519_common.c @@ -34,11 +34,11 @@ int pki_privkey_build_ed25519(ssh_key key, if (ssh_string_len(pubkey) != ED25519_KEY_LEN || ssh_string_len(privkey) != (2 * ED25519_KEY_LEN)) { - SSH_LOG(SSH_LOG_WARN, "Invalid ed25519 key len"); + SSH_LOG(SSH_LOG_TRACE, "Invalid ed25519 key len"); return SSH_ERROR; } -#ifdef HAVE_OPENSSL_ED25519 +#ifdef HAVE_LIBCRYPTO /* In OpenSSL implementation, the private key is the original private seed, * without the public key. */ key->ed25519_privkey = malloc(ED25519_KEY_LEN); @@ -56,7 +56,7 @@ int pki_privkey_build_ed25519(ssh_key key, goto error; } -#ifdef HAVE_OPENSSL_ED25519 +#ifdef HAVE_LIBCRYPTO memcpy(key->ed25519_privkey, ssh_string_data(privkey), ED25519_KEY_LEN); #else @@ -99,7 +99,7 @@ int pki_ed25519_key_cmp(const ssh_key k1, if (k1->ed25519_privkey == NULL || k2->ed25519_privkey == NULL) { return 1; } -#ifdef HAVE_OPENSSL_ED25519 +#ifdef HAVE_LIBCRYPTO /* In OpenSSL implementation, the private key is the original private * seed, without the public key. */ cmp = memcmp(k1->ed25519_privkey, k2->ed25519_privkey, ED25519_KEY_LEN); @@ -121,6 +121,10 @@ int pki_ed25519_key_cmp(const ssh_key k1, if (cmp != 0) { return 1; } + break; + case SSH_KEY_CMP_CERTIFICATE: + /* handled globally */ + return 1; } return 0; @@ -137,39 +141,39 @@ int pki_ed25519_key_cmp(const ssh_key k1, * * @return SSH_ERROR on error, SSH_OK on success */ -int pki_ed25519_key_dup(ssh_key new, const ssh_key key) +int pki_ed25519_key_dup(ssh_key new_key, const ssh_key key) { if (key->ed25519_privkey == NULL && key->ed25519_pubkey == NULL) { return SSH_ERROR; } if (key->ed25519_privkey != NULL) { -#ifdef HAVE_OPENSSL_ED25519 +#ifdef HAVE_LIBCRYPTO /* In OpenSSL implementation, the private key is the original private * seed, without the public key. */ - new->ed25519_privkey = malloc(ED25519_KEY_LEN); + new_key->ed25519_privkey = malloc(ED25519_KEY_LEN); #else /* In the internal implementation, the private key is the concatenation * of the private seed with the public key. */ - new->ed25519_privkey = malloc(2 * ED25519_KEY_LEN); + new_key->ed25519_privkey = malloc(2 * ED25519_KEY_LEN); #endif - if (new->ed25519_privkey == NULL) { + if (new_key->ed25519_privkey == NULL) { return SSH_ERROR; } -#ifdef HAVE_OPENSSL_ED25519 - memcpy(new->ed25519_privkey, key->ed25519_privkey, ED25519_KEY_LEN); +#ifdef HAVE_LIBCRYPTO + memcpy(new_key->ed25519_privkey, key->ed25519_privkey, ED25519_KEY_LEN); #else - memcpy(new->ed25519_privkey, key->ed25519_privkey, 2 * ED25519_KEY_LEN); + memcpy(new_key->ed25519_privkey, key->ed25519_privkey, 2 * ED25519_KEY_LEN); #endif } if (key->ed25519_pubkey != NULL) { - new->ed25519_pubkey = malloc(ED25519_KEY_LEN); - if (new->ed25519_pubkey == NULL) { - SAFE_FREE(new->ed25519_privkey); + new_key->ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (new_key->ed25519_pubkey == NULL) { + SAFE_FREE(new_key->ed25519_privkey); return SSH_ERROR; } - memcpy(new->ed25519_pubkey, key->ed25519_pubkey, ED25519_KEY_LEN); + memcpy(new_key->ed25519_pubkey, key->ed25519_pubkey, ED25519_KEY_LEN); } return SSH_OK; @@ -202,6 +206,34 @@ int pki_ed25519_public_key_to_blob(ssh_buffer buffer, ssh_key key) return rc; } +/** @internal + * @brief exports a ed25519 private key to a string blob. + * @param[in] privkey private key to convert + * @param[out] buffer buffer to write the blob in. + * @returns SSH_OK on success + */ +int pki_ed25519_private_key_to_blob(ssh_buffer buffer, const ssh_key privkey) +{ + int rc; + + if (privkey->type != SSH_KEYTYPE_ED25519) { + SSH_LOG(SSH_LOG_TRACE, "Type %s not supported", privkey->type_c); + return SSH_ERROR; + } + if (privkey->ed25519_privkey == NULL || + privkey->ed25519_pubkey == NULL) { + return SSH_ERROR; + } + rc = ssh_buffer_pack(buffer, + "dPdPP", + (uint32_t)ED25519_KEY_LEN, + (size_t)ED25519_KEY_LEN, privkey->ed25519_pubkey, + (uint32_t)(2 * ED25519_KEY_LEN), + (size_t)ED25519_KEY_LEN, privkey->ed25519_privkey, + (size_t)ED25519_KEY_LEN, privkey->ed25519_pubkey); + return rc; +} + /** * @internal * @@ -214,8 +246,9 @@ int pki_ed25519_public_key_to_blob(ssh_buffer buffer, ssh_key key) ssh_string pki_ed25519_signature_to_blob(ssh_signature sig) { ssh_string sig_blob; + int rc; -#ifdef HAVE_OPENSSL_ED25519 +#ifdef HAVE_LIBCRYPTO /* When using the OpenSSL implementation, the signature is stored in raw_sig * which is shared by all algorithms.*/ if (sig->raw_sig == NULL) { @@ -234,12 +267,16 @@ ssh_string pki_ed25519_signature_to_blob(ssh_signature sig) return NULL; } -#ifdef HAVE_OPENSSL_ED25519 - ssh_string_fill(sig_blob, ssh_string_data(sig->raw_sig), - ssh_string_len(sig->raw_sig)); +#ifdef HAVE_LIBCRYPTO + rc = ssh_string_fill(sig_blob, ssh_string_data(sig->raw_sig), + ssh_string_len(sig->raw_sig)); #else - ssh_string_fill(sig_blob, sig->ed25519_sig, ED25519_SIG_LEN); + rc = ssh_string_fill(sig_blob, sig->ed25519_sig, ED25519_SIG_LEN); #endif + if (rc < 0) { + SSH_STRING_FREE(sig_blob); + return NULL; + } return sig_blob; } @@ -261,11 +298,11 @@ int pki_signature_from_ed25519_blob(ssh_signature sig, ssh_string sig_blob) len = ssh_string_len(sig_blob); if (len != ED25519_SIG_LEN){ - SSH_LOG(SSH_LOG_WARN, "Invalid ssh-ed25519 signature len: %zu", len); + SSH_LOG(SSH_LOG_TRACE, "Invalid ssh-ed25519 signature len: %zu", len); return SSH_ERROR; } -#ifdef HAVE_OPENSSL_ED25519 +#ifdef HAVE_LIBCRYPTO sig->raw_sig = ssh_string_copy(sig_blob); #else sig->ed25519_sig = malloc(ED25519_SIG_LEN); @@ -277,4 +314,3 @@ int pki_signature_from_ed25519_blob(ssh_signature sig, ssh_string sig_blob) return SSH_OK; } - diff --git a/src/pki_gcrypt.c b/src/pki_gcrypt.c index 62ec8ea7..65bb77e6 100644 --- a/src/pki_gcrypt.c +++ b/src/pki_gcrypt.c @@ -45,8 +45,6 @@ #define MAXLINESIZE 80 #define RSA_HEADER_BEGIN "-----BEGIN RSA PRIVATE KEY-----" #define RSA_HEADER_END "-----END RSA PRIVATE KEY-----" -#define DSA_HEADER_BEGIN "-----BEGIN DSA PRIVATE KEY-----" -#define DSA_HEADER_END "-----END DSA PRIVATE KEY-----" #define ECDSA_HEADER_BEGIN "-----BEGIN EC PRIVATE KEY-----" #define ECDSA_HEADER_END "-----END EC PRIVATE KEY-----" @@ -283,6 +281,20 @@ static int passphrase_to_key(char *data, unsigned int datalen, return 0; } +void pki_key_clean(ssh_key key) +{ + if (key == NULL) + return; + + if (key->rsa) + gcry_sexp_release(key->rsa); + if (key->ecdsa) + gcry_sexp_release(key->ecdsa); + + key->rsa = NULL; + key->ecdsa = NULL; +} + static int privatekey_decrypt(int algo, int mode, unsigned int key_len, unsigned char *iv, unsigned int iv_len, ssh_buffer data, ssh_auth_callback cb, @@ -419,10 +431,6 @@ static ssh_buffer privatekey_string_to_buffer(const char *pkey, int type, } switch(type) { - case SSH_KEYTYPE_DSS: - header_begin = DSA_HEADER_BEGIN; - header_end = DSA_HEADER_END; - break; case SSH_KEYTYPE_RSA: header_begin = RSA_HEADER_BEGIN; header_end = RSA_HEADER_END; @@ -626,79 +634,6 @@ error: return rc; } -static int b64decode_dsa_privatekey(const char *pkey, gcry_sexp_t *r, ssh_auth_callback cb, - void *userdata, const char *desc) { - const unsigned char *data; - ssh_buffer buffer = NULL; - ssh_string p = NULL; - ssh_string q = NULL; - ssh_string g = NULL; - ssh_string y = NULL; - ssh_string x = NULL; - ssh_string v = NULL; - int rc = 1; - - buffer = privatekey_string_to_buffer(pkey, SSH_KEYTYPE_DSS, cb, userdata, desc); - if (buffer == NULL) { - return 0; - } - - if (!asn1_check_sequence(buffer)) { - SSH_BUFFER_FREE(buffer); - return 0; - } - - v = asn1_get_int(buffer); - if (v == NULL) { - SSH_BUFFER_FREE(buffer); - return 0; - } - - data = ssh_string_data(v); - if (ssh_string_len(v) != 1 || data[0] != 0) { - SSH_STRING_FREE(v); - SSH_BUFFER_FREE(buffer); - return 0; - } - - p = asn1_get_int(buffer); - q = asn1_get_int(buffer); - g = asn1_get_int(buffer); - y = asn1_get_int(buffer); - x = asn1_get_int(buffer); - SSH_BUFFER_FREE(buffer); - - if (p == NULL || q == NULL || g == NULL || y == NULL || x == NULL) { - rc = 0; - goto error; - } - - if (gcry_sexp_build(r, NULL, - "(private-key(dsa(p %b)(q %b)(g %b)(y %b)(x %b)))", - ssh_string_len(p), ssh_string_data(p), - ssh_string_len(q), ssh_string_data(q), - ssh_string_len(g), ssh_string_data(g), - ssh_string_len(y), ssh_string_data(y), - ssh_string_len(x), ssh_string_data(x))) { - rc = 0; - } - -error: - ssh_string_burn(p); - SSH_STRING_FREE(p); - ssh_string_burn(q); - SSH_STRING_FREE(q); - ssh_string_burn(g); - SSH_STRING_FREE(g); - ssh_string_burn(y); - SSH_STRING_FREE(y); - ssh_string_burn(x); - SSH_STRING_FREE(x); - SSH_STRING_FREE(v); - - return rc; -} - #ifdef HAVE_GCRYPT_ECC static int pki_key_ecdsa_to_nid(gcry_sexp_t k) { @@ -938,7 +873,7 @@ ssh_string pki_private_key_to_pem(const ssh_key key, (void) auth_fn; (void) auth_data; - SSH_LOG(SSH_LOG_WARN, "PEM export not supported by gcrypt backend!"); + SSH_LOG(SSH_LOG_TRACE, "PEM export not supported by gcrypt backend!"); return NULL; } @@ -948,7 +883,6 @@ ssh_key pki_private_key_from_base64(const char *b64_key, ssh_auth_callback auth_fn, void *auth_data) { - gcry_sexp_t dsa = NULL; gcry_sexp_t rsa = NULL; gcry_sexp_t ecdsa = NULL; ssh_key key = NULL; @@ -957,30 +891,11 @@ ssh_key pki_private_key_from_base64(const char *b64_key, type = pki_privatekey_type_from_string(b64_key); if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "Unknown or invalid private key."); + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key."); return NULL; } switch (type) { - case SSH_KEYTYPE_DSS: - if (passphrase == NULL) { - if (auth_fn) { - valid = b64decode_dsa_privatekey(b64_key, &dsa, auth_fn, - auth_data, "Passphrase for private key:"); - } else { - valid = b64decode_dsa_privatekey(b64_key, &dsa, NULL, NULL, - NULL); - } - } else { - valid = b64decode_dsa_privatekey(b64_key, &dsa, NULL, (void *) - passphrase, NULL); - } - - if (!valid) { - SSH_LOG(SSH_LOG_WARN, "Parsing private key"); - goto fail; - } - break; case SSH_KEYTYPE_RSA: if (passphrase == NULL) { if (auth_fn) { @@ -996,7 +911,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, } if (!valid) { - SSH_LOG(SSH_LOG_WARN, "Parsing private key"); + SSH_LOG(SSH_LOG_TRACE, "Error parsing private key"); goto fail; } break; @@ -1027,7 +942,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, } if (!valid) { - SSH_LOG(SSH_LOG_WARN, "Parsing private key"); + SSH_LOG(SSH_LOG_TRACE, "Error parsing private key"); goto fail; } @@ -1035,7 +950,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, * keys, so we need to figure out the correct type here */ type = pki_key_ecdsa_to_key_type(ecdsa); if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "Invalid private key."); + SSH_LOG(SSH_LOG_TRACE, "Invalid private key."); goto fail; } break; @@ -1045,7 +960,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_WARN, "Unknown or invalid private key type %d", type); + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key type %d", type); return NULL; } @@ -1057,7 +972,6 @@ ssh_key pki_private_key_from_base64(const char *b64_key, key->type = type; key->type_c = ssh_key_type_to_char(type); key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; - key->dsa = dsa; key->rsa = rsa; key->ecdsa = ecdsa; #ifdef HAVE_GCRYPT_ECC @@ -1069,52 +983,12 @@ ssh_key pki_private_key_from_base64(const char *b64_key, return key; fail: ssh_key_free(key); - gcry_sexp_release(dsa); gcry_sexp_release(rsa); gcry_sexp_release(ecdsa); return NULL; } -int pki_privkey_build_dss(ssh_key key, - ssh_string p, - ssh_string q, - ssh_string g, - ssh_string pubkey, - ssh_string privkey) -{ - gcry_sexp_build(&key->dsa, NULL, - "(private-key(dsa(p %b)(q %b)(g %b)(y %b)(x %b)))", - ssh_string_len(p), ssh_string_data(p), - ssh_string_len(q), ssh_string_data(q), - ssh_string_len(g), ssh_string_data(g), - ssh_string_len(pubkey), ssh_string_data(pubkey), - ssh_string_len(privkey), ssh_string_data(privkey)); - if (key->dsa == NULL) { - return SSH_ERROR; - } - - return SSH_OK; -} - -int pki_pubkey_build_dss(ssh_key key, - ssh_string p, - ssh_string q, - ssh_string g, - ssh_string pubkey) { - gcry_sexp_build(&key->dsa, NULL, - "(public-key(dsa(p %b)(q %b)(g %b)(y %b)))", - ssh_string_len(p), ssh_string_data(p), - ssh_string_len(q), ssh_string_data(q), - ssh_string_len(g), ssh_string_data(g), - ssh_string_len(pubkey), ssh_string_data(pubkey)); - if (key->dsa == NULL) { - return SSH_ERROR; - } - - return SSH_OK; -} - int pki_privkey_build_rsa(ssh_key key, ssh_string n, ssh_string e, @@ -1226,32 +1100,6 @@ ssh_key pki_key_dup(const ssh_key key, int demote) } switch(key->type) { - case SSH_KEYTYPE_DSS: - err = gcry_sexp_extract_param(key->dsa, - NULL, - "pqgyx?", - &p, - &q, - &g, - &y, - &x, - NULL); - if (err != 0) { - break; - } - - if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { - err = gcry_sexp_build(&new->dsa, - NULL, - "(private-key(dsa(p %m)(q %m)(g %m)(y %m)(x %m)))", - p, q, g, y, x); - } else { - err = gcry_sexp_build(&new->dsa, - NULL, - "(public-key(dsa(p %m)(q %m)(g %m)(y %m)))", - p, q, g, y); - } - break; case SSH_KEYTYPE_RSA: err = gcry_sexp_extract_param(key->rsa, NULL, @@ -1353,9 +1201,9 @@ ssh_key pki_key_dup(const ssh_key key, int demote) } static int pki_key_generate(ssh_key key, int parameter, const char *type_s, int type){ - gcry_sexp_t parms; + gcry_sexp_t params; int rc; - rc = gcry_sexp_build(&parms, + rc = gcry_sexp_build(¶ms, NULL, "(genkey(%s(nbits %d)(transient-key)))", type_s, @@ -1364,20 +1212,17 @@ static int pki_key_generate(ssh_key key, int parameter, const char *type_s, int return SSH_ERROR; switch (type) { case SSH_KEYTYPE_RSA: - rc = gcry_pk_genkey(&key->rsa, parms); - break; - case SSH_KEYTYPE_DSS: - rc = gcry_pk_genkey(&key->dsa, parms); + rc = gcry_pk_genkey(&key->rsa, params); break; case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: - rc = gcry_pk_genkey(&key->ecdsa, parms); + rc = gcry_pk_genkey(&key->ecdsa, params); break; default: assert (! "reached"); } - gcry_sexp_release(parms); + gcry_sexp_release(params); if (rc != 0) return SSH_ERROR; return SSH_OK; @@ -1386,9 +1231,6 @@ static int pki_key_generate(ssh_key key, int parameter, const char *type_s, int int pki_key_generate_rsa(ssh_key key, int parameter){ return pki_key_generate(key, parameter, "rsa", SSH_KEYTYPE_RSA); } -int pki_key_generate_dss(ssh_key key, int parameter){ - return pki_key_generate(key, parameter, "dsa", SSH_KEYTYPE_DSS); -} #ifdef HAVE_GCRYPT_ECC int pki_key_generate_ecdsa(ssh_key key, int parameter) { @@ -1455,30 +1297,8 @@ int pki_key_compare(const ssh_key k1, enum ssh_keycmp_e what) { switch (k1->type) { - case SSH_KEYTYPE_DSS: - if (_bignum_cmp(k1->dsa, k2->dsa, "p") != 0) { - return 1; - } - - if (_bignum_cmp(k1->dsa, k2->dsa, "q") != 0) { - return 1; - } - - if (_bignum_cmp(k1->dsa, k2->dsa, "g") != 0) { - return 1; - } - - if (_bignum_cmp(k1->dsa, k2->dsa, "y") != 0) { - return 1; - } - - if (what == SSH_KEY_CMP_PRIVATE) { - if (_bignum_cmp(k1->dsa, k2->dsa, "x") != 0) { - return 1; - } - } - break; case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: if (_bignum_cmp(k1->rsa, k2->rsa, "e") != 0) { return 1; } @@ -1506,13 +1326,19 @@ int pki_key_compare(const ssh_key k1, } break; case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: case SSH_KEYTYPE_SK_ED25519: - /* ed25519 keys handled globaly */ - return 0; + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys handled globally */ + return 0; case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: #ifdef HAVE_GCRYPT_ECC if (k1->ecdsa_nid != k2->ecdsa_nid) { return 1; @@ -1529,15 +1355,9 @@ int pki_key_compare(const ssh_key k1, } break; #endif - case SSH_KEYTYPE_DSS_CERT01: - case SSH_KEYTYPE_RSA_CERT01: - case SSH_KEYTYPE_ECDSA: - case SSH_KEYTYPE_ECDSA_P256_CERT01: - case SSH_KEYTYPE_ECDSA_P384_CERT01: - case SSH_KEYTYPE_ECDSA_P521_CERT01: - case SSH_KEYTYPE_SK_ECDSA_CERT01: - case SSH_KEYTYPE_ED25519_CERT01: - case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_ECDSA: /* deprecated */ case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_UNKNOWN: return 1; @@ -1546,16 +1366,18 @@ int pki_key_compare(const ssh_key k1, return 0; } -ssh_string pki_publickey_to_blob(const ssh_key key) +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) { ssh_buffer buffer; ssh_string type_s; ssh_string str = NULL; ssh_string e = NULL; ssh_string n = NULL; + ssh_string d = NULL; ssh_string p = NULL; ssh_string g = NULL; ssh_string q = NULL; + ssh_string u = NULL; int rc; buffer = ssh_buffer_new(); @@ -1586,66 +1408,6 @@ ssh_string pki_publickey_to_blob(const ssh_key key) } switch (key->type) { - case SSH_KEYTYPE_DSS: - p = ssh_sexp_extract_mpi(key->dsa, - "p", - GCRYMPI_FMT_USG, - GCRYMPI_FMT_STD); - if (p == NULL) { - goto fail; - } - - q = ssh_sexp_extract_mpi(key->dsa, - "q", - GCRYMPI_FMT_USG, - GCRYMPI_FMT_STD); - if (q == NULL) { - goto fail; - } - - g = ssh_sexp_extract_mpi(key->dsa, - "g", - GCRYMPI_FMT_USG, - GCRYMPI_FMT_STD); - if (g == NULL) { - goto fail; - } - - n = ssh_sexp_extract_mpi(key->dsa, - "y", - GCRYMPI_FMT_USG, - GCRYMPI_FMT_STD); - if (n == NULL) { - goto fail; - } - - rc = ssh_buffer_add_ssh_string(buffer, p); - if (rc < 0) { - goto fail; - } - rc = ssh_buffer_add_ssh_string(buffer, q); - if (rc < 0) { - goto fail; - } - rc = ssh_buffer_add_ssh_string(buffer, g); - if (rc < 0) { - goto fail; - } - rc = ssh_buffer_add_ssh_string(buffer, n); - if (rc < 0) { - goto fail; - } - - ssh_string_burn(p); - SSH_STRING_FREE(p); - ssh_string_burn(g); - SSH_STRING_FREE(g); - ssh_string_burn(q); - SSH_STRING_FREE(q); - ssh_string_burn(n); - SSH_STRING_FREE(n); - - break; case SSH_KEYTYPE_RSA: e = ssh_sexp_extract_mpi(key->rsa, "e", @@ -1663,30 +1425,108 @@ ssh_string pki_publickey_to_blob(const ssh_key key) goto fail; } - rc = ssh_buffer_add_ssh_string(buffer, e); - if (rc < 0) { - goto fail; - } - rc = ssh_buffer_add_ssh_string(buffer, n); - if (rc < 0) { - goto fail; - } + if (type == SSH_KEY_PUBLIC) { + /* The N and E parts are swapped in the public key export ! */ + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + } else if (type == SSH_KEY_PRIVATE) { + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + d = ssh_sexp_extract_mpi(key->rsa, + "d", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (d == NULL) { + goto fail; + } + + p = ssh_sexp_extract_mpi(key->rsa, + "p", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (p == NULL) { + goto fail; + } + q = ssh_sexp_extract_mpi(key->rsa, + "q", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (q == NULL) { + goto fail; + } + + u = ssh_sexp_extract_mpi(key->rsa, + "u", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (u == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, u); + if (rc < 0) { + goto fail; + } + /* Swap the P and Q as the iqmp in gcrypt is ipmq ... */ + rc = ssh_buffer_add_ssh_string(buffer, q); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { + goto fail; + } + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); + ssh_string_burn(u); + SSH_STRING_FREE(u); + } ssh_string_burn(e); SSH_STRING_FREE(e); ssh_string_burn(n); SSH_STRING_FREE(n); - break; case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: - rc = pki_ed25519_public_key_to_blob(buffer, key); - if (rc != SSH_OK){ - goto fail; - } - if (key->type == SSH_KEYTYPE_SK_ED25519 && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; + if (type == SSH_KEY_PUBLIC) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + /* public key can contain certificate sk information */ + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto fail; + } + } + } else { + rc = pki_ed25519_private_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } } break; case SSH_KEYTYPE_ECDSA_P256: @@ -1697,22 +1537,19 @@ ssh_string pki_publickey_to_blob(const ssh_key key) type_s = ssh_string_from_char( pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); if (type_s == NULL) { - SSH_BUFFER_FREE(buffer); - return NULL; + goto fail; } rc = ssh_buffer_add_ssh_string(buffer, type_s); SSH_STRING_FREE(type_s); if (rc < 0) { - SSH_BUFFER_FREE(buffer); - return NULL; + goto fail; } e = ssh_sexp_extract_mpi(key->ecdsa, "q", GCRYMPI_FMT_STD, GCRYMPI_FMT_STD); if (e == NULL) { - SSH_BUFFER_FREE(buffer); - return NULL; + goto fail; } rc = ssh_buffer_add_ssh_string(buffer, e); @@ -1724,9 +1561,27 @@ ssh_string pki_publickey_to_blob(const ssh_key key) SSH_STRING_FREE(e); e = NULL; - if (key->type == SSH_KEYTYPE_SK_ECDSA && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; + if (type == SSH_KEY_PRIVATE) { + d = ssh_sexp_extract_mpi(key->ecdsa, "d", GCRYMPI_FMT_STD, + GCRYMPI_FMT_STD); + if (d == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(d); + SSH_STRING_FREE(d); + d = NULL; + } else if (key->type == SSH_KEYTYPE_SK_ECDSA) { + /* public key can contain certificate sk information */ + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto fail; + } } break; @@ -1770,66 +1625,14 @@ fail: ssh_string pki_signature_to_blob(const ssh_signature sig) { - char buffer[40] = { 0 }; - - const char *r = NULL; - size_t r_len, r_offset_in, r_offset_out; - - const char *s = NULL; - size_t s_len, s_offset_in, s_offset_out; + const char *s = NULL; /* used in RSA */ gcry_sexp_t sexp; size_t size = 0; ssh_string sig_blob = NULL; + int rc; switch(sig->type) { - case SSH_KEYTYPE_DSS: - sexp = gcry_sexp_find_token(sig->dsa_sig, "r", 0); - if (sexp == NULL) { - return NULL; - } - r = gcry_sexp_nth_data(sexp, 1, &size); - /* libgcrypt put 0 when first bit is set */ - if (*r == 0) { - size--; - r++; - } - - r_len = size; - r_offset_in = (r_len > 20) ? (r_len - 20) : 0; - r_offset_out = (r_len < 20) ? (20 - r_len) : 0; - memcpy(buffer + r_offset_out, - r + r_offset_in, - r_len - r_offset_in); - - gcry_sexp_release(sexp); - - sexp = gcry_sexp_find_token(sig->dsa_sig, "s", 0); - if (sexp == NULL) { - return NULL; - } - s = gcry_sexp_nth_data(sexp,1,&size); - if (*s == 0) { - size--; - s++; - } - - s_len = size; - s_offset_in = (s_len > 20) ? (s_len - 20) : 0; - s_offset_out = (s_len < 20) ? (20 - s_len) : 0; - memcpy(buffer + 20 + s_offset_out, - s + s_offset_in, - s_len - s_offset_in); - - gcry_sexp_release(sexp); - - sig_blob = ssh_string_new(40); - if (sig_blob == NULL) { - return NULL; - } - - ssh_string_fill(sig_blob, buffer, 40); - break; case SSH_KEYTYPE_RSA: sexp = gcry_sexp_find_token(sig->rsa_sig, "s", 0); if (sexp == NULL) { @@ -1845,13 +1648,16 @@ ssh_string pki_signature_to_blob(const ssh_signature sig) if (sig_blob == NULL) { return NULL; } - ssh_string_fill(sig_blob, discard_const_p(char, s), size); - + rc = ssh_string_fill(sig_blob, discard_const_p(char, s), size); gcry_sexp_release(sexp); + if (rc < 0) { + SSH_STRING_FREE(sig_blob); + return NULL; + } break; case SSH_KEYTYPE_ED25519: - sig_blob = pki_ed25519_signature_to_blob(sig); - break; + sig_blob = pki_ed25519_signature_to_blob(sig); + break; case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: @@ -1860,7 +1666,6 @@ ssh_string pki_signature_to_blob(const ssh_signature sig) ssh_string R; ssh_string S; ssh_buffer b; - int rc; b = ssh_buffer_new(); if (b == NULL) { @@ -1901,16 +1706,20 @@ ssh_string pki_signature_to_blob(const ssh_signature sig) return NULL; } - ssh_string_fill(sig_blob, + rc = ssh_string_fill(sig_blob, ssh_buffer_get(b), ssh_buffer_get_len(b)); SSH_BUFFER_FREE(b); + if (rc < 0) { + SSH_STRING_FREE(sig_blob); + return NULL; + } break; } #endif case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_WARN, "Unknown signature key type: %d", sig->type); + SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %d", sig->type); return NULL; break; } @@ -1930,7 +1739,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, int rc; if (ssh_key_type_plain(pubkey->type) != type) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Incompatible public key provided (%d) expecting (%d)", type, pubkey->type); @@ -1949,42 +1758,14 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, len = ssh_string_len(sig_blob); switch(type) { - case SSH_KEYTYPE_DSS: - /* 40 is the dual signature blob len. */ - if (len != 40) { - SSH_LOG(SSH_LOG_WARN, - "Signature has wrong size: %lu", - (unsigned long)len); - ssh_signature_free(sig); - return NULL; - } - -#ifdef DEBUG_CRYPTO - SSH_LOG(SSH_LOG_DEBUG, - "DSA signature len: %lu", - (unsigned long)len); - ssh_log_hexdump("DSA signature", ssh_string_data(sig_blob), len); -#endif - - err = gcry_sexp_build(&sig->dsa_sig, - NULL, - "(sig-val(dsa(r %b)(s %b)))", - 20, - ssh_string_data(sig_blob), - 20, - (unsigned char *)ssh_string_data(sig_blob) + 20); - if (err) { - ssh_signature_free(sig); - return NULL; - } - break; case SSH_KEYTYPE_RSA: rsalen = (gcry_pk_get_nbits(pubkey->rsa) + 7) / 8; if (len > rsalen) { - SSH_LOG(SSH_LOG_WARN, - "Signature is to big size: %lu", - (unsigned long)len); + SSH_LOG(SSH_LOG_TRACE, + "Signature is too big: %lu > %lu", + (unsigned long)len, + (unsigned long)rsalen); ssh_signature_free(sig); return NULL; } @@ -2062,7 +1843,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, } if (rlen != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Signature has remaining bytes in inner " "sigblob: %lu", (unsigned long)rlen); @@ -2100,7 +1881,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_WARN, "Unknown signature type"); + SSH_LOG(SSH_LOG_TRACE, "Unknown signature type"); return NULL; } @@ -2112,7 +1893,6 @@ ssh_signature pki_do_sign_hash(const ssh_key privkey, size_t hlen, enum ssh_digest_e hash_type) { - unsigned char ghash[hlen + 1]; const char *hash_c = NULL; ssh_signature sig; gcry_sexp_t sexp; @@ -2126,28 +1906,6 @@ ssh_signature pki_do_sign_hash(const ssh_key privkey, sig->type_c = ssh_key_signature_to_char(privkey->type, hash_type); sig->hash_type = hash_type; switch (privkey->type) { - case SSH_KEYTYPE_DSS: - /* That is to mark the number as positive */ - if(hash[0] >= 0x80) { - memcpy(ghash + 1, hash, hlen); - ghash[0] = 0; - hash = ghash; - hlen += 1; - } - - err = gcry_sexp_build(&sexp, NULL, "%b", hlen, hash); - if (err) { - ssh_signature_free(sig); - return NULL; - } - - err = gcry_pk_sign(&sig->dsa_sig, sexp, privkey->dsa); - gcry_sexp_release(sexp); - if (err) { - ssh_signature_free(sig); - return NULL; - } - break; case SSH_KEYTYPE_RSA: switch (hash_type) { case SSH_DIGEST_SHA1: @@ -2161,7 +1919,7 @@ ssh_signature pki_do_sign_hash(const ssh_key privkey, break; case SSH_DIGEST_AUTO: default: - SSH_LOG(SSH_LOG_WARN, "Incompatible key algorithm"); + SSH_LOG(SSH_LOG_TRACE, "Incompatible key algorithm"); return NULL; } err = gcry_sexp_build(&sexp, @@ -2380,32 +2138,6 @@ int pki_verify_data_signature(ssh_signature signature, } switch(pubkey->type) { - case SSH_KEYTYPE_DSS: - case SSH_KEYTYPE_DSS_CERT01: - /* That is to mark the number as positive */ - if(hash[0] >= 0x80) { - hash = ghash; - hlen += 1; - } - - err = gcry_sexp_build(&sexp, NULL, "%b", hlen, hash); - if (err) { - SSH_LOG(SSH_LOG_TRACE, - "DSA hash error: %s", gcry_strerror(err)); - return SSH_ERROR; - } - err = gcry_pk_verify(signature->dsa_sig, sexp, pubkey->dsa); - gcry_sexp_release(sexp); - if (err) { - SSH_LOG(SSH_LOG_TRACE, "Invalid DSA signature"); - if (gcry_err_code(err) != GPG_ERR_BAD_SIGNATURE) { - SSH_LOG(SSH_LOG_TRACE, - "DSA verify error: %s", - gcry_strerror(err)); - } - return SSH_ERROR; - } - break; case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA_CERT01: err = gcry_sexp_build(&sexp, @@ -2483,13 +2215,45 @@ int pki_verify_data_signature(ssh_signature signature, return SSH_OK; } +int ssh_key_size(ssh_key key) +{ + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA1: + return gcry_pk_get_nbits(key->rsa); + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + return gcry_pk_get_nbits(key->ecdsa); + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys have fixed size */ + return 255; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_UNKNOWN: + default: + return SSH_ERROR; + } +} + +#ifdef WITH_PKCS11_URI int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type) { (void) uri_name; (void) key; (void) key_type; - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "gcrypt does not support PKCS #11"); return SSH_ERROR; } +#endif /* WITH_PKCS11_URI */ #endif /* HAVE_LIBGCRYPT */ diff --git a/src/pki_mbedcrypto.c b/src/pki_mbedcrypto.c index cac357f8..962ae1fe 100644 --- a/src/pki_mbedcrypto.c +++ b/src/pki_mbedcrypto.c @@ -26,6 +26,7 @@ #ifdef HAVE_LIBMBEDCRYPTO #include <mbedtls/pk.h> #include <mbedtls/error.h> +#include "mbedcrypto-compat.h" #include "libssh/priv.h" #include "libssh/pki.h" @@ -37,6 +38,22 @@ #define MAX_PASSPHRASE_SIZE 1024 #define MAX_KEY_SIZE 32 +void pki_key_clean(ssh_key key) +{ + if (key == NULL) + return; + + if (key->rsa != NULL) { + mbedtls_pk_free(key->rsa); + SAFE_FREE(key->rsa); + } + + if (key->ecdsa != NULL) { + mbedtls_ecdsa_free(key->ecdsa); + SAFE_FREE(key->ecdsa); + } +} + ssh_string pki_private_key_to_pem(const ssh_key key, const char *passphrase, ssh_auth_callback auth_fn, void *auth_data) { @@ -50,7 +67,7 @@ static int pki_key_ecdsa_to_nid(mbedtls_ecdsa_context *ecdsa) { mbedtls_ecp_group_id id; - id = ecdsa->grp.id; + id = ecdsa->MBEDTLS_PRIVATE(grp.id); if (id == MBEDTLS_ECP_DP_SECP256R1) { return NID_mbedtls_nistp256; } else if (id == MBEDTLS_ECP_DP_SECP384R1) { @@ -84,118 +101,110 @@ ssh_key pki_private_key_from_base64(const char *b64_key, const char *passphrase, ssh_auth_callback auth_fn, void *auth_data) { ssh_key key = NULL; - mbedtls_pk_context *rsa = NULL; - mbedtls_pk_context *ecdsa = NULL; - ed25519_privkey *ed25519 = NULL; - enum ssh_keytypes_e type; + mbedtls_pk_context *pk = NULL; + mbedtls_pk_type_t mbed_type; int valid; /* mbedtls pk_parse_key expects strlen to count the 0 byte */ size_t b64len = strlen(b64_key) + 1; unsigned char tmp[MAX_PASSPHRASE_SIZE] = {0}; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_ctr_drbg_context *ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); +#endif - type = pki_privatekey_type_from_string(b64_key); - if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "Unknown or invalid private key."); - return NULL; + pk = malloc(sizeof(mbedtls_pk_context)); + if (pk == NULL) { + goto fail; } - - switch (type) { - case SSH_KEYTYPE_RSA: - rsa = malloc(sizeof(mbedtls_pk_context)); - if (rsa == NULL) { - return NULL; - } - - mbedtls_pk_init(rsa); - - if (passphrase == NULL) { - if (auth_fn) { - valid = auth_fn("Passphrase for private key:", (char *) tmp, - MAX_PASSPHRASE_SIZE, 0, 0, auth_data); - if (valid < 0) { - goto fail; - } - /* TODO fix signedness and strlen */ - valid = mbedtls_pk_parse_key(rsa, - (const unsigned char *) b64_key, - b64len, tmp, - strnlen((const char *) tmp, MAX_PASSPHRASE_SIZE)); - } else { - valid = mbedtls_pk_parse_key(rsa, - (const unsigned char *) b64_key, - b64len, NULL, - 0); - } - } else { - valid = mbedtls_pk_parse_key(rsa, - (const unsigned char *) b64_key, b64len, - (const unsigned char *) passphrase, - strnlen(passphrase, MAX_PASSPHRASE_SIZE)); - } - - if (valid != 0) { - char error_buf[100]; - mbedtls_strerror(valid, error_buf, 100); - SSH_LOG(SSH_LOG_WARN,"Parsing private key %s", error_buf); + mbedtls_pk_init(pk); + + if (passphrase == NULL) { + if (auth_fn) { + valid = auth_fn("Passphrase for private key:", + (char *)tmp, + MAX_PASSPHRASE_SIZE, + 0, + 0, + auth_data); + if (valid < 0) { goto fail; } - break; - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: - ecdsa = malloc(sizeof(mbedtls_pk_context)); - if (ecdsa == NULL) { - return NULL; - } - - mbedtls_pk_init(ecdsa); - - if (passphrase == NULL) { - if (auth_fn) { - valid = auth_fn("Passphrase for private key:", (char *) tmp, - MAX_PASSPHRASE_SIZE, 0, 0, auth_data); - if (valid < 0) { - goto fail; - } - valid = mbedtls_pk_parse_key(ecdsa, - (const unsigned char *) b64_key, - b64len, tmp, - strnlen((const char *) tmp, MAX_PASSPHRASE_SIZE)); - } else { - valid = mbedtls_pk_parse_key(ecdsa, - (const unsigned char *) b64_key, - b64len, NULL, - 0); - } - } else { - valid = mbedtls_pk_parse_key(ecdsa, - (const unsigned char *) b64_key, b64len, - (const unsigned char *) passphrase, - strnlen(passphrase, MAX_PASSPHRASE_SIZE)); - } - - if (valid != 0) { - char error_buf[100]; - mbedtls_strerror(valid, error_buf, 100); - SSH_LOG(SSH_LOG_WARN,"Parsing private key %s", error_buf); - goto fail; - } - break; - case SSH_KEYTYPE_ED25519: - /* Cannot open ed25519 keys with libmbedcrypto */ - default: - SSH_LOG(SSH_LOG_WARN, "Unknown or invalid private key type %d", - type); - return NULL; +#if MBEDTLS_VERSION_MAJOR > 2 + valid = mbedtls_pk_parse_key( + pk, + (const unsigned char *)b64_key, + b64len, + tmp, + strnlen((const char *)tmp, MAX_PASSPHRASE_SIZE), + mbedtls_ctr_drbg_random, + ctr_drbg); +#else + valid = mbedtls_pk_parse_key( + pk, + (const unsigned char *)b64_key, + b64len, + tmp, + strnlen((const char *)tmp, MAX_PASSPHRASE_SIZE)); +#endif + } else { +#if MBEDTLS_VERSION_MAJOR > 2 + valid = mbedtls_pk_parse_key(pk, + (const unsigned char *)b64_key, + b64len, + NULL, + 0, + mbedtls_ctr_drbg_random, + ctr_drbg); +#else + valid = mbedtls_pk_parse_key(pk, + (const unsigned char *)b64_key, + b64len, + NULL, + 0); +#endif + } + } else { +#if MBEDTLS_VERSION_MAJOR > 2 + valid = mbedtls_pk_parse_key(pk, + (const unsigned char *)b64_key, + b64len, + (const unsigned char *)passphrase, + strnlen(passphrase, MAX_PASSPHRASE_SIZE), + mbedtls_ctr_drbg_random, + ctr_drbg); +#else + valid = mbedtls_pk_parse_key(pk, + (const unsigned char *)b64_key, + b64len, + (const unsigned char *)passphrase, + strnlen(passphrase, MAX_PASSPHRASE_SIZE)); +#endif } + if (valid != 0) { + char error_buf[100]; + mbedtls_strerror(valid, error_buf, 100); + SSH_LOG(SSH_LOG_WARN, "Parsing private key %s", error_buf); + goto fail; + } + + mbed_type = mbedtls_pk_get_type(pk); key = ssh_key_new(); if (key == NULL) { goto fail; } - if (ecdsa != NULL) { - mbedtls_ecp_keypair *keypair = mbedtls_pk_ec(*ecdsa); + switch (mbed_type) { + case MBEDTLS_PK_RSA: + case MBEDTLS_PK_RSA_ALT: + key->rsa = pk; + pk = NULL; + key->type = SSH_KEYTYPE_RSA; + break; + case MBEDTLS_PK_ECKEY: + case MBEDTLS_PK_ECDSA: { + /* type will be set later */ + mbedtls_ecp_keypair *keypair = mbedtls_pk_ec(*pk); + pk = NULL; key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); if (key->ecdsa == NULL) { @@ -204,40 +213,36 @@ ssh_key pki_private_key_from_base64(const char *b64_key, const char *passphrase, mbedtls_ecdsa_init(key->ecdsa); mbedtls_ecdsa_from_keypair(key->ecdsa, keypair); - mbedtls_pk_free(ecdsa); - SAFE_FREE(ecdsa); + mbedtls_pk_free(pk); + SAFE_FREE(pk); key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa); /* pki_privatekey_type_from_string always returns P256 for ECDSA - * keys, so we need to figure out the correct type here */ - type = pki_key_ecdsa_to_key_type(key->ecdsa); - if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "Invalid private key."); + * keys, so we need to figure out the correct type here */ + key->type = pki_key_ecdsa_to_key_type(key->ecdsa); + if (key->type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid private key."); goto fail; } - } else { - key->ecdsa = NULL; + break; + } + default: + SSH_LOG(SSH_LOG_WARN, + "Unknown or invalid private key type %d", + mbed_type); + return NULL; } - key->type = type; - key->type_c = ssh_key_type_to_char(type); + key->type_c = ssh_key_type_to_char(key->type); key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; - key->rsa = rsa; - key->ed25519_privkey = ed25519; - rsa = NULL; - ecdsa = NULL; return key; fail: ssh_key_free(key); - if (rsa != NULL) { - mbedtls_pk_free(rsa); - SAFE_FREE(rsa); - } - if (ecdsa != NULL) { - mbedtls_pk_free(ecdsa); - SAFE_FREE(ecdsa); + if (pk != NULL) { + mbedtls_pk_free(pk); + SAFE_FREE(pk); } return NULL; } @@ -276,19 +281,19 @@ int pki_privkey_build_rsa(ssh_key key, ssh_string_data(d), ssh_string_len(d), ssh_string_data(e), ssh_string_len(e)); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, "Failed to import private RSA key"); + SSH_LOG(SSH_LOG_TRACE, "Failed to import private RSA key"); goto fail; } rc = mbedtls_rsa_complete(rsa); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, "Failed to complete private RSA key"); + SSH_LOG(SSH_LOG_TRACE, "Failed to complete private RSA key"); goto fail; } rc = mbedtls_rsa_check_privkey(rsa); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, "Inconsistent private RSA key"); + SSH_LOG(SSH_LOG_TRACE, "Inconsistent private RSA key"); goto fail; } @@ -304,6 +309,10 @@ int pki_pubkey_build_rsa(ssh_key key, ssh_string e, ssh_string n) { mbedtls_rsa_context *rsa = NULL; const mbedtls_pk_info_t *pk_info = NULL; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi N; + mbedtls_mpi E; +#endif int rc; key->rsa = malloc(sizeof(mbedtls_pk_context)); @@ -320,26 +329,59 @@ int pki_pubkey_build_rsa(ssh_key key, ssh_string e, ssh_string n) goto fail; } +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&E); +#endif + rsa = mbedtls_pk_rsa(*key->rsa); +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_mpi_read_binary(&N, ssh_string_data(n), + ssh_string_len(n)); +#else rc = mbedtls_mpi_read_binary(&rsa->N, ssh_string_data(n), ssh_string_len(n)); +#endif if (rc != 0) { goto fail; } +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_mpi_read_binary(&E, ssh_string_data(e), + ssh_string_len(e)); +#else rc = mbedtls_mpi_read_binary(&rsa->E, ssh_string_data(e), ssh_string_len(e)); +#endif if (rc != 0) { goto fail; } - rsa->len = (mbedtls_mpi_bitlen(&rsa->N) + 7) >> 3; +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_import(rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } - return SSH_OK; + rc = mbedtls_rsa_complete(rsa); + if (rc != 0) { + goto fail; + } +#else + rsa->len = (mbedtls_mpi_bitlen(&rsa->N) + 7) >> 3; +#endif + rc = SSH_OK; + goto exit; fail: + rc = SSH_ERROR; mbedtls_pk_free(key->rsa); SAFE_FREE(key->rsa); - return SSH_ERROR; +exit: +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&E); +#endif + return rc; } ssh_key pki_key_dup(const ssh_key key, int demote) @@ -347,7 +389,13 @@ ssh_key pki_key_dup(const ssh_key key, int demote) ssh_key new = NULL; int rc; const mbedtls_pk_info_t *pk_info = NULL; - +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi N; + mbedtls_mpi E; + mbedtls_mpi D; + mbedtls_mpi P; + mbedtls_mpi Q; +#endif new = ssh_key_new(); if (new == NULL) { @@ -362,6 +410,13 @@ ssh_key pki_key_dup(const ssh_key key, int demote) new->flags = key->flags; } +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&E); + mbedtls_mpi_init(&D); + mbedtls_mpi_init(&P); + mbedtls_mpi_init(&Q); +#endif switch(key->type) { case SSH_KEYTYPE_RSA: { @@ -376,11 +431,26 @@ ssh_key pki_key_dup(const ssh_key key, int demote) pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); mbedtls_pk_setup(new->rsa, pk_info); - if (mbedtls_pk_can_do(key->rsa, MBEDTLS_PK_RSA) && - mbedtls_pk_can_do(new->rsa, MBEDTLS_PK_RSA)) { - rsa = mbedtls_pk_rsa(*key->rsa); - new_rsa = mbedtls_pk_rsa(*new->rsa); + if (!mbedtls_pk_can_do(key->rsa, MBEDTLS_PK_RSA) || + !mbedtls_pk_can_do(new->rsa, MBEDTLS_PK_RSA)) + { + goto fail; + } + + rsa = mbedtls_pk_rsa(*key->rsa); + new_rsa = mbedtls_pk_rsa(*new->rsa); + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, &N, &P, &Q, &D, &E); + if (rc != 0) { + goto fail; + } + rc = mbedtls_rsa_import(new_rsa, &N, &P, &Q, &D, &E); + if (rc != 0) { + goto fail; + } +#else rc = mbedtls_mpi_copy(&new_rsa->N, &rsa->N); if (rc != 0) { goto fail; @@ -390,42 +460,70 @@ ssh_key pki_key_dup(const ssh_key key, int demote) if (rc != 0) { goto fail; } + new_rsa->len = (mbedtls_mpi_bitlen(&new_rsa->N) + 7) >> 3; - if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { - rc = mbedtls_mpi_copy(&new_rsa->D, &rsa->D); - if (rc != 0) { - goto fail; - } + rc = mbedtls_mpi_copy(&new_rsa->D, &rsa->D); + if (rc != 0) { + goto fail; + } - rc = mbedtls_mpi_copy(&new_rsa->P, &rsa->P); - if (rc != 0) { - goto fail; - } + rc = mbedtls_mpi_copy(&new_rsa->P, &rsa->P); + if (rc != 0) { + goto fail; + } - rc = mbedtls_mpi_copy(&new_rsa->Q, &rsa->Q); - if (rc != 0) { - goto fail; - } + rc = mbedtls_mpi_copy(&new_rsa->Q, &rsa->Q); + if (rc != 0) { + goto fail; + } - rc = mbedtls_mpi_copy(&new_rsa->DP, &rsa->DP); - if (rc != 0) { - goto fail; - } + rc = mbedtls_mpi_copy(&new_rsa->DP, &rsa->DP); + if (rc != 0) { + goto fail; + } - rc = mbedtls_mpi_copy(&new_rsa->DQ, &rsa->DQ); - if (rc != 0) { - goto fail; - } + rc = mbedtls_mpi_copy(&new_rsa->DQ, &rsa->DQ); + if (rc != 0) { + goto fail; + } - rc = mbedtls_mpi_copy(&new_rsa->QP, &rsa->QP); - if (rc != 0) { - goto fail; - } + rc = mbedtls_mpi_copy(&new_rsa->QP, &rsa->QP); + if (rc != 0) { + goto fail; } +#endif } else { +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } + rc = mbedtls_rsa_import(new_rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } +#else + rc = mbedtls_mpi_copy(&new_rsa->N, &rsa->N); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->E, &rsa->E); + if (rc != 0) { + goto fail; + } + + new_rsa->len = (mbedtls_mpi_bitlen(&new_rsa->N) + 7) >> 3; +#endif + } + +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_complete(new_rsa); + if (rc != 0) { goto fail; } +#endif break; } @@ -443,12 +541,14 @@ ssh_key pki_key_dup(const ssh_key key, int demote) mbedtls_ecdsa_init(new->ecdsa); if (demote && ssh_key_is_private(key)) { - rc = mbedtls_ecp_copy(&new->ecdsa->Q, &key->ecdsa->Q); + rc = mbedtls_ecp_copy(&new->ecdsa->MBEDTLS_PRIVATE(Q), + &key->ecdsa->MBEDTLS_PRIVATE(Q)); if (rc != 0) { goto fail; } - rc = mbedtls_ecp_group_copy(&new->ecdsa->grp, &key->ecdsa->grp); + rc = mbedtls_ecp_group_copy(&new->ecdsa->MBEDTLS_PRIVATE(grp), + &key->ecdsa->MBEDTLS_PRIVATE(grp)); if (rc != 0) { goto fail; } @@ -467,10 +567,19 @@ ssh_key pki_key_dup(const ssh_key key, int demote) goto fail; } - return new; + goto cleanup; + fail: - ssh_key_free(new); - return NULL; + SSH_KEY_FREE(new); +cleanup: +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&E); + mbedtls_mpi_free(&D); + mbedtls_mpi_free(&P); + mbedtls_mpi_free(&Q); +#endif + return new; } int pki_key_generate_rsa(ssh_key key, int parameter) @@ -508,36 +617,140 @@ int pki_key_generate_rsa(ssh_key key, int parameter) int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) { - switch (k1->type) { + int rc = 0; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi N1; + mbedtls_mpi N2; + mbedtls_mpi P1; + mbedtls_mpi P2; + mbedtls_mpi Q1; + mbedtls_mpi Q2; + mbedtls_mpi E1; + mbedtls_mpi E2; + + mbedtls_mpi_init(&N1); + mbedtls_mpi_init(&N2); + mbedtls_mpi_init(&P1); + mbedtls_mpi_init(&P2); + mbedtls_mpi_init(&Q1); + mbedtls_mpi_init(&Q2); + mbedtls_mpi_init(&E1); + mbedtls_mpi_init(&E2); +#endif + + switch (ssh_key_type_plain(k1->type)) { case SSH_KEYTYPE_RSA: { mbedtls_rsa_context *rsa1, *rsa2; - if (mbedtls_pk_can_do(k1->rsa, MBEDTLS_PK_RSA) && - mbedtls_pk_can_do(k2->rsa, MBEDTLS_PK_RSA)) { - if (mbedtls_pk_get_type(k1->rsa) != mbedtls_pk_get_type(k2->rsa) || - mbedtls_pk_get_bitlen(k1->rsa) != - mbedtls_pk_get_bitlen(k2->rsa)) { - return 1; + if (!mbedtls_pk_can_do(k1->rsa, MBEDTLS_PK_RSA) || + !mbedtls_pk_can_do(k2->rsa, MBEDTLS_PK_RSA)) + { + break; + } + + if (mbedtls_pk_get_type(k1->rsa) != mbedtls_pk_get_type(k2->rsa) || + mbedtls_pk_get_bitlen(k1->rsa) != + mbedtls_pk_get_bitlen(k2->rsa)) + { + rc = 1; + goto cleanup; + } + + if (what == SSH_KEY_CMP_PUBLIC) { +#if MBEDTLS_VERSION_MAJOR > 2 + rsa1 = mbedtls_pk_rsa(*k1->rsa); + rc = mbedtls_rsa_export(rsa1, &N1, NULL, NULL, NULL, &E1); + if (rc != 0) { + rc = 1; + goto cleanup; } + rsa2 = mbedtls_pk_rsa(*k2->rsa); + rc = mbedtls_rsa_export(rsa2, &N2, NULL, NULL, NULL, &E2); + if (rc != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&N1, &N2) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&E1, &E2) != 0) { + rc = 1; + goto cleanup; + } +#else rsa1 = mbedtls_pk_rsa(*k1->rsa); rsa2 = mbedtls_pk_rsa(*k2->rsa); if (mbedtls_mpi_cmp_mpi(&rsa1->N, &rsa2->N) != 0) { - return 1; + rc = 1; + goto cleanup; } if (mbedtls_mpi_cmp_mpi(&rsa1->E, &rsa2->E) != 0) { - return 1; + rc = 1; + goto cleanup; + } +#endif + } else if (what == SSH_KEY_CMP_PRIVATE) { +#if MBEDTLS_VERSION_MAJOR > 2 + rsa1 = mbedtls_pk_rsa(*k1->rsa); + rc = mbedtls_rsa_export(rsa1, &N1, &P1, &Q1, NULL, &E1); + if (rc != 0) { + rc = 1; + goto cleanup; } - if (what == SSH_KEY_CMP_PRIVATE) { - if (mbedtls_mpi_cmp_mpi(&rsa1->P, &rsa2->P) != 0) { - return 1; - } + rsa2 = mbedtls_pk_rsa(*k2->rsa); + rc = mbedtls_rsa_export(rsa2, &N2, &P2, &Q2, NULL, &E2); + if (rc != 0) { + rc = 1; + goto cleanup; + } - if (mbedtls_mpi_cmp_mpi(&rsa1->Q, &rsa2->Q) != 0) { - return 1; - } + if (mbedtls_mpi_cmp_mpi(&N1, &N2) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&E1, &E2) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&P1, &P2) != 0) { + rc = 1; + goto cleanup; } + + if (mbedtls_mpi_cmp_mpi(&Q1, &Q2) != 0) { + rc = 1; + goto cleanup; + } +#else + rsa1 = mbedtls_pk_rsa(*k1->rsa); + rsa2 = mbedtls_pk_rsa(*k2->rsa); + if (mbedtls_mpi_cmp_mpi(&rsa1->N, &rsa2->N) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->E, &rsa2->E) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->P, &rsa2->P) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->Q, &rsa2->Q) != 0) { + rc = 1; + goto cleanup; + } +#endif } break; } @@ -548,25 +761,39 @@ int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) mbedtls_ecp_keypair *ecdsa1 = k1->ecdsa; mbedtls_ecp_keypair *ecdsa2 = k2->ecdsa; - if (ecdsa1->grp.id != ecdsa2->grp.id) { - return 1; + if (ecdsa1->MBEDTLS_PRIVATE(grp).id != + ecdsa2->MBEDTLS_PRIVATE(grp).id) { + rc = 1; + goto cleanup; } - if (mbedtls_mpi_cmp_mpi(&ecdsa1->Q.X, &ecdsa2->Q.X)) { - return 1; + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X), + &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X))) + { + rc = 1; + goto cleanup; } - if (mbedtls_mpi_cmp_mpi(&ecdsa1->Q.Y, &ecdsa2->Q.Y)) { - return 1; + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y), + &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y))) + { + rc = 1; + goto cleanup; } - if (mbedtls_mpi_cmp_mpi(&ecdsa1->Q.Z, &ecdsa2->Q.Z)) { - return 1; + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Z), + &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Z))) + { + rc = 1; + goto cleanup; } if (what == SSH_KEY_CMP_PRIVATE) { - if (mbedtls_mpi_cmp_mpi(&ecdsa1->d, &ecdsa2->d)) { - return 1; + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(d), + &ecdsa2->MBEDTLS_PRIVATE(d))) + { + rc = 1; + goto cleanup; } } @@ -575,12 +802,25 @@ int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: /* ed25519 keys handled globally */ - return 0; + rc = 0; + break; default: - return 1; - } - - return 0; + rc = 1; + break; + } + +cleanup: +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N1); + mbedtls_mpi_free(&N2); + mbedtls_mpi_free(&P1); + mbedtls_mpi_free(&P2); + mbedtls_mpi_free(&Q1); + mbedtls_mpi_free(&Q2); + mbedtls_mpi_free(&E1); + mbedtls_mpi_free(&E2); +#endif + return rc; } ssh_string make_ecpoint_string(const mbedtls_ecp_group *g, const @@ -638,15 +878,28 @@ static const char* pki_key_ecdsa_nid_to_char(int nid) return "unknown"; } -ssh_string pki_publickey_to_blob(const ssh_key key) +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) { ssh_buffer buffer = NULL; ssh_string type_s = NULL; ssh_string e = NULL; ssh_string n = NULL; ssh_string str = NULL; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi E; + mbedtls_mpi N; + mbedtls_mpi D; + mbedtls_mpi IQMP; + mbedtls_mpi P; + mbedtls_mpi Q; +#endif int rc; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_init(&E); + mbedtls_mpi_init(&N); +#endif + buffer = ssh_buffer_new(); if (buffer == NULL) { return NULL; @@ -685,31 +938,151 @@ ssh_string pki_publickey_to_blob(const ssh_key key) rsa = mbedtls_pk_rsa(*key->rsa); - e = ssh_make_bignum_string(&rsa->E); +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } + + e = ssh_make_bignum_string(&E); if (e == NULL) { goto fail; } - n = ssh_make_bignum_string(&rsa->N); + n = ssh_make_bignum_string(&N); if (n == NULL) { goto fail; } - - if (ssh_buffer_add_ssh_string(buffer, e) < 0) { +#else + e = ssh_make_bignum_string(&rsa->E); + if (e == NULL) { goto fail; } - if (ssh_buffer_add_ssh_string(buffer, n) < 0) { + n = ssh_make_bignum_string(&rsa->N); + if (n == NULL) { goto fail; } +#endif + + if (type == SSH_KEY_PUBLIC) { + /* The N and E parts are swapped in the public key export ! */ + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + } else if (type == SSH_KEY_PRIVATE) { + ssh_string p = NULL; + ssh_string q = NULL; + ssh_string d = NULL; + ssh_string iqmp = NULL; + + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, NULL, &P, &Q, &D, NULL); + if (rc != 0) { + goto fail; + } + + p = ssh_make_bignum_string(&P); + if (p == NULL) { + goto fail; + } + + q = ssh_make_bignum_string(&Q); + if (q == NULL) { + goto fail; + } + + d = ssh_make_bignum_string(&D); + if (d == NULL) { + goto fail; + } + rc = mbedtls_rsa_export_crt(rsa, NULL, NULL, &IQMP); + if (rc != 0) { + goto fail; + } + iqmp = ssh_make_bignum_string(&IQMP); + if (iqmp == NULL) { + goto fail; + } + +#else + p = ssh_make_bignum_string(&rsa->P); + if (p == NULL) { + goto fail; + } + + q = ssh_make_bignum_string(&rsa->Q); + if (q == NULL) { + goto fail; + } + + d = ssh_make_bignum_string(&rsa->D); + if (d == NULL) { + goto fail; + } + + iqmp = ssh_make_bignum_string(&rsa->QP); + if (iqmp == NULL) { + goto fail; + } +#endif + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, iqmp); + if (rc < 0) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, q); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(d); + SSH_STRING_FREE(d); + d = NULL; + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); + iqmp = NULL; + ssh_string_burn(p); + SSH_STRING_FREE(p); + p = NULL; + ssh_string_burn(q); + SSH_STRING_FREE(q); + q = NULL; + } ssh_string_burn(e); SSH_STRING_FREE(e); e = NULL; ssh_string_burn(n); SSH_STRING_FREE(n); n = NULL; - break; } case SSH_KEYTYPE_ECDSA_P256: @@ -730,7 +1103,8 @@ ssh_string pki_publickey_to_blob(const ssh_key key) return NULL; } - e = make_ecpoint_string(&key->ecdsa->grp, &key->ecdsa->Q); + e = make_ecpoint_string(&key->ecdsa->MBEDTLS_PRIVATE(grp), + &key->ecdsa->MBEDTLS_PRIVATE(Q)); if (e == NULL) { SSH_BUFFER_FREE(buffer); @@ -746,21 +1120,51 @@ ssh_string pki_publickey_to_blob(const ssh_key key) SSH_STRING_FREE(e); e = NULL; - if (key->type == SSH_KEYTYPE_SK_ECDSA && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; - } + if (type == SSH_KEY_PRIVATE) { + ssh_string d = NULL; + d = ssh_make_bignum_string(&key->ecdsa->MBEDTLS_PRIVATE(d)); + + if (d == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(d); + SSH_STRING_FREE(d); + d = NULL; + } else if (key->type == SSH_KEYTYPE_SK_ECDSA) { + /* public key can contain certificate sk information */ + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto fail; + } + } break; case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: - rc = pki_ed25519_public_key_to_blob(buffer, key); - if (rc != SSH_OK) { - goto fail; - } - if (key->type == SSH_KEYTYPE_SK_ED25519 && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; + if (type == SSH_KEY_PUBLIC) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + /* public key can contain certificate sk information */ + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto fail; + } + } + } else { + rc = pki_ed25519_private_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } } break; default: @@ -779,6 +1183,10 @@ makestring: } SSH_BUFFER_FREE(buffer); +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&E); +#endif return str; fail: SSH_BUFFER_FREE(buffer); @@ -788,6 +1196,10 @@ fail: SSH_STRING_FREE(e); ssh_string_burn(n); SSH_STRING_FREE(n); +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&E); +#endif return NULL; } @@ -845,15 +1257,20 @@ ssh_string pki_signature_to_blob(const ssh_signature sig) return NULL; } - ssh_string_fill(sig_blob, ssh_buffer_get(b), ssh_buffer_get_len(b)); + rc = ssh_string_fill(sig_blob, ssh_buffer_get(b), ssh_buffer_get_len(b)); SSH_BUFFER_FREE(b); + if (rc < 0) { + SSH_STRING_FREE(sig_blob); + return NULL; + } + break; } case SSH_KEYTYPE_ED25519: sig_blob = pki_ed25519_signature_to_blob(sig); break; default: - SSH_LOG(SSH_LOG_WARN, "Unknown signature key type: %s", + SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %s", sig->type_c); return NULL; } @@ -873,20 +1290,20 @@ static ssh_signature pki_signature_from_rsa_blob(const ssh_key pubkey, const size_t len = ssh_string_len(sig_blob); if (pubkey->rsa == NULL) { - SSH_LOG(SSH_LOG_WARN, "Pubkey RSA field NULL"); + SSH_LOG(SSH_LOG_TRACE, "Pubkey RSA field NULL"); goto errout; } rsalen = mbedtls_pk_get_bitlen(pubkey->rsa) / 8; if (len > rsalen) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Signature is too big: %lu > %lu", (unsigned long) len, (unsigned long) rsalen); goto errout; } #ifdef DEBUG_CRYPTO - SSH_LOG(SSH_LOG_WARN, "RSA signature len: %lu", (unsigned long)len); + SSH_LOG(SSH_LOG_TRACE, "RSA signature len: %lu", (unsigned long)len); ssh_log_hexdump("RSA signature", ssh_string_data(sig_blob), len); #endif @@ -927,7 +1344,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, int rc; if (ssh_key_type_plain(pubkey->type) != type) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Incompatible public key provided (%d) expecting (%d)", type, pubkey->type); @@ -1012,7 +1429,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, } if (rlen != 0) { - SSH_LOG(SSH_LOG_WARN, "Signature has remaining bytes in inner " + SSH_LOG(SSH_LOG_TRACE, "Signature has remaining bytes in inner " "sigblob: %lu", (unsigned long)rlen); ssh_signature_free(sig); @@ -1030,7 +1447,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, } break; default: - SSH_LOG(SSH_LOG_WARN, "Unknown signature type"); + SSH_LOG(SSH_LOG_TRACE, "Unknown signature type"); return NULL; } @@ -1046,6 +1463,7 @@ static ssh_string rsa_do_sign_hash(const unsigned char *digest, mbedtls_md_type_t md = 0; unsigned char *sig = NULL; size_t slen; + size_t sig_size; int ok; switch (hash_type) { @@ -1060,11 +1478,12 @@ static ssh_string rsa_do_sign_hash(const unsigned char *digest, break; case SSH_DIGEST_AUTO: default: - SSH_LOG(SSH_LOG_WARN, "Incompatible key algorithm"); + SSH_LOG(SSH_LOG_TRACE, "Incompatible key algorithm"); return NULL; } - sig = malloc(mbedtls_pk_get_bitlen(privkey) / 8); + sig_size = mbedtls_pk_get_bitlen(privkey) / 8; + sig = malloc(sig_size); if (sig == NULL) { return NULL; } @@ -1074,6 +1493,9 @@ static ssh_string rsa_do_sign_hash(const unsigned char *digest, digest, dlen, sig, +#if MBEDTLS_VERSION_MAJOR > 2 + sig_size, +#endif &slen, mbedtls_ctr_drbg_random, ssh_get_mbedtls_ctr_drbg_context()); @@ -1089,9 +1511,13 @@ static ssh_string rsa_do_sign_hash(const unsigned char *digest, return NULL; } - ssh_string_fill(sig_blob, sig, slen); + ok = ssh_string_fill(sig_blob, sig, slen); explicit_bzero(sig, slen); SAFE_FREE(sig); + if (ok < 0) { + SSH_STRING_FREE(sig_blob); + return NULL; + } return sig_blob; } @@ -1136,10 +1562,10 @@ ssh_signature pki_do_sign_hash(const ssh_key privkey, return NULL; } - rc = mbedtls_ecdsa_sign(&privkey->ecdsa->grp, + rc = mbedtls_ecdsa_sign(&privkey->ecdsa->MBEDTLS_PRIVATE(grp), sig->ecdsa_sig.r, sig->ecdsa_sig.s, - &privkey->ecdsa->d, + &privkey->ecdsa->MBEDTLS_PRIVATE(d), hash, hlen, mbedtls_ctr_drbg_random, @@ -1342,8 +1768,9 @@ int pki_verify_data_signature(ssh_signature signature, case SSH_KEYTYPE_ECDSA_P521_CERT01: case SSH_KEYTYPE_SK_ECDSA: case SSH_KEYTYPE_SK_ECDSA_CERT01: - rc = mbedtls_ecdsa_verify(&pubkey->ecdsa->grp, hash, hlen, - &pubkey->ecdsa->Q, signature->ecdsa_sig.r, + rc = mbedtls_ecdsa_verify(&pubkey->ecdsa->MBEDTLS_PRIVATE(grp), hash, + hlen, &pubkey->ecdsa->MBEDTLS_PRIVATE(Q), + signature->ecdsa_sig.r, signature->ecdsa_sig.s); if (rc != 0) { char error_buf[100]; @@ -1446,18 +1873,19 @@ int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) goto fail; } - rc = mbedtls_ecp_copy(&keypair.Q, &Q); + rc = mbedtls_ecp_copy(&keypair.MBEDTLS_PRIVATE(Q), &Q); if (rc != 0) { goto fail; } - rc = mbedtls_ecp_group_copy(&keypair.grp, &group); + rc = mbedtls_ecp_group_copy(&keypair.MBEDTLS_PRIVATE(grp), &group); if (rc != 0) { goto fail; } - rc = mbedtls_mpi_read_binary(&keypair.d, ssh_string_data(exp), - ssh_string_len(exp)); + rc = mbedtls_mpi_read_binary(&keypair.MBEDTLS_PRIVATE(d), + ssh_string_data(exp), + ssh_string_len(exp)); if (rc != 0) { goto fail; } @@ -1513,17 +1941,17 @@ int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) goto fail; } - rc = mbedtls_ecp_copy(&keypair.Q, &Q); + rc = mbedtls_ecp_copy(&keypair.MBEDTLS_PRIVATE(Q), &Q); if (rc != 0) { goto fail; } - rc = mbedtls_ecp_group_copy(&keypair.grp, &group); + rc = mbedtls_ecp_group_copy(&keypair.MBEDTLS_PRIVATE(grp), &group); if (rc != 0) { goto fail; } - mbedtls_mpi_init(&keypair.d); + mbedtls_mpi_init(&keypair.MBEDTLS_PRIVATE(d)); rc = mbedtls_ecdsa_from_keypair(key->ecdsa, &keypair); if (rc != 0) { @@ -1583,36 +2011,39 @@ int pki_key_generate_ecdsa(ssh_key key, int parameter) return SSH_OK; } -int pki_privkey_build_dss(ssh_key key, ssh_string p, ssh_string q, ssh_string g, - ssh_string pubkey, ssh_string privkey) -{ - (void) key; - (void) p; - (void) q; - (void) g; - (void) pubkey; - (void) privkey; - return SSH_ERROR; -} - -int pki_pubkey_build_dss(ssh_key key, ssh_string p, ssh_string q, ssh_string g, - ssh_string pubkey) -{ - (void) key; - (void) p; - (void) q; - (void) g; - (void) pubkey; - return SSH_ERROR; -} - -int pki_key_generate_dss(ssh_key key, int parameter) +int ssh_key_size(ssh_key key) { - (void) key; - (void) parameter; - return SSH_ERROR; + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA1: + return mbedtls_pk_get_bitlen(key->rsa); + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + return 256; + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + return 384; + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + return 521; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys have fixed size */ + return 255; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_UNKNOWN: + default: + return SSH_ERROR; + } } +#ifdef WITH_PKCS11_URI int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type) { (void) uri_name; @@ -1622,4 +2053,5 @@ int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type) "mbedcrypto does not support PKCS #11"); return SSH_ERROR; } +#endif /* WITH_PKCS11_URI */ #endif /* HAVE_LIBMBEDCRYPTO */ @@ -44,14 +44,14 @@ #endif /** - * @defgroup libssh_poll The SSH poll functions. + * @defgroup libssh_poll The SSH poll functions * @ingroup libssh * * Add a generic way to handle sockets asynchronously. * * It's based on poll objects, each of which store a socket, its events and a * callback, which gets called whenever an event is set. The poll objects are - * attached to a poll context, which should be allocated on per thread basis. + * attached to a poll context, which should be allocated on a per thread basis. * * Polling the poll context will poll all the attached poll objects and call * their callbacks (handlers) if any of the socket events are set. This should @@ -68,7 +68,7 @@ struct ssh_poll_handle_struct { size_t idx; } x; short events; - int lock; + uint32_t lock_cnt; ssh_poll_callback cb; void *cb_data; }; @@ -84,15 +84,18 @@ struct ssh_poll_ctx_struct { #ifdef HAVE_POLL #include <poll.h> -void ssh_poll_init(void) { +void ssh_poll_init(void) +{ return; } -void ssh_poll_cleanup(void) { +void ssh_poll_cleanup(void) +{ return; } -int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) { +int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) +{ return poll((struct pollfd *) fds, nfds, timeout); } @@ -210,8 +213,8 @@ static short bsd_socket_compute_revents(int fd, short events) * poll implementation. * * Keep in mind that select is terribly inefficient. The interface is simply not - * meant to be used with maximum descriptor value greater, say, 32 or so. With - * a value as high as 1024 on Linux you'll pay dearly in every single call. + * meant to be used with maximum descriptor value greater than, say, 32 or so. + * With a value as high as 1024 on Linux you'll pay dearly in every single call. * poll() will be orders of magnitude faster. */ static int bsd_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) @@ -246,19 +249,17 @@ static int bsd_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) } #endif - if (fds[i].events & (POLLIN | POLLRDNORM)) { - FD_SET (fds[i].fd, &readfds); - } + // we use the readfds to get POLLHUP and POLLERR, which are provided even when not requested + FD_SET (fds[i].fd, &readfds); + if (fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND)) { FD_SET (fds[i].fd, &writefds); } if (fds[i].events & (POLLPRI | POLLRDBAND)) { FD_SET (fds[i].fd, &exceptfds); } - if (fds[i].fd > max_fd && - (fds[i].events & (POLLIN | POLLOUT | POLLPRI | - POLLRDNORM | POLLRDBAND | - POLLWRNORM | POLLWRBAND))) { + + if (fds[i].fd > max_fd) { max_fd = fds[i].fd; rc = 0; } @@ -286,7 +287,7 @@ static int bsd_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) if (rc < 0) { return -1; } - /* A timeout occured */ + /* A timeout occurred */ if (rc == 0) { return 0; } @@ -335,21 +336,24 @@ int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) { /** * @brief Allocate a new poll object, which could be used within a poll context. * - * @param fd Socket that will be polled. - * @param events Poll events that will be monitored for the socket. i.e. - * POLLIN, POLLPRI, POLLOUT - * @param cb Function to be called if any of the events are set. - * The prototype of cb is: - * int (*ssh_poll_callback)(ssh_poll_handle p, socket_t fd, - * int revents, void *userdata); - * @param userdata Userdata to be passed to the callback function. NULL if - * not needed. + * @param[in] fd Socket that will be polled. + * @param[in] events Poll events that will be monitored for the socket. + * i.e. POLLIN, POLLPRI, POLLOUT + * @param[in] cb Function to be called if any of the events are set. + * The prototype of cb is: + * int (*ssh_poll_callback)(ssh_poll_handle p, + * socket_t fd, + * int revents, + * void *userdata); + * @param[in] userdata Userdata to be passed to the callback function. + * NULL if not needed. * - * @return A new poll object, NULL on error + * @return A new poll object, NULL on error */ -ssh_poll_handle ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, - void *userdata) { +ssh_poll_handle +ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, void *userdata) +{ ssh_poll_handle p; p = malloc(sizeof(struct ssh_poll_handle_struct)); @@ -373,12 +377,13 @@ ssh_poll_handle ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, * @param p Pointer to an already allocated poll object. */ -void ssh_poll_free(ssh_poll_handle p) { - if(p->ctx != NULL){ - ssh_poll_ctx_remove(p->ctx,p); - p->ctx=NULL; - } - SAFE_FREE(p); +void ssh_poll_free(ssh_poll_handle p) +{ + if (p->ctx != NULL) { + ssh_poll_ctx_remove(p->ctx, p); + p->ctx = NULL; + } + SAFE_FREE(p); } /** @@ -388,8 +393,9 @@ void ssh_poll_free(ssh_poll_handle p) { * * @return Poll context or NULL if the poll object isn't attached. */ -ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p) { - return p->ctx; +ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p) +{ + return p->ctx; } /** @@ -399,22 +405,31 @@ ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p) { * * @return Poll events. */ -short ssh_poll_get_events(ssh_poll_handle p) { - return p->events; +short ssh_poll_get_events(ssh_poll_handle p) +{ + return p->events; } /** * @brief Set the events of a poll object. The events will also be propagated - * to an associated poll context. + * to an associated poll context unless the fd is locked. In that case, + * only the POLLOUT can be set. * * @param p Pointer to an already allocated poll object. * @param events Poll events. */ -void ssh_poll_set_events(ssh_poll_handle p, short events) { - p->events = events; - if (p->ctx != NULL && !p->lock) { - p->ctx->pollfds[p->x.idx].events = events; - } +void ssh_poll_set_events(ssh_poll_handle p, short events) +{ + p->events = events; + if (p->ctx != NULL) { + if (p->lock_cnt == 0) { + p->ctx->pollfds[p->x.idx].events = events; + } else if (!(p->ctx->pollfds[p->x.idx].events & POLLOUT)) { + /* if locked, allow only setting POLLOUT to prevent recursive + * callbacks */ + p->ctx->pollfds[p->x.idx].events = events & POLLOUT; + } + } } /** @@ -424,12 +439,13 @@ void ssh_poll_set_events(ssh_poll_handle p, short events) { * @param p Pointer to an already allocated poll object. * @param fd New file descriptor. */ -void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd) { - if (p->ctx != NULL) { - p->ctx->pollfds[p->x.idx].fd = fd; - } else { - p->x.fd = fd; - } +void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd) +{ + if (p->ctx != NULL) { + p->ctx->pollfds[p->x.idx].fd = fd; + } else { + p->x.fd = fd; + } } /** @@ -439,8 +455,9 @@ void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd) { * @param p Pointer to an already allocated poll object. * @param events Poll events. */ -void ssh_poll_add_events(ssh_poll_handle p, short events) { - ssh_poll_set_events(p, ssh_poll_get_events(p) | events); +void ssh_poll_add_events(ssh_poll_handle p, short events) +{ + ssh_poll_set_events(p, ssh_poll_get_events(p) | events); } /** @@ -450,8 +467,9 @@ void ssh_poll_add_events(ssh_poll_handle p, short events) { * @param p Pointer to an already allocated poll object. * @param events Poll events. */ -void ssh_poll_remove_events(ssh_poll_handle p, short events) { - ssh_poll_set_events(p, ssh_poll_get_events(p) & ~events); +void ssh_poll_remove_events(ssh_poll_handle p, short events) +{ + ssh_poll_set_events(p, ssh_poll_get_events(p) & ~events); } /** @@ -462,12 +480,13 @@ void ssh_poll_remove_events(ssh_poll_handle p, short events) { * @return Raw socket. */ -socket_t ssh_poll_get_fd(ssh_poll_handle p) { - if (p->ctx != NULL) { - return p->ctx->pollfds[p->x.idx].fd; - } +socket_t ssh_poll_get_fd(ssh_poll_handle p) +{ + if (p->ctx != NULL) { + return p->ctx->pollfds[p->x.idx].fd; + } - return p->x.fd; + return p->x.fd; } /** * @brief Set the callback of a poll object. @@ -477,11 +496,12 @@ socket_t ssh_poll_get_fd(ssh_poll_handle p) { * @param userdata Userdata to be passed to the callback function. NULL if * not needed. */ -void ssh_poll_set_callback(ssh_poll_handle p, ssh_poll_callback cb, void *userdata) { - if (cb != NULL) { - p->cb = cb; - p->cb_data = userdata; - } +void ssh_poll_set_callback(ssh_poll_handle p, ssh_poll_callback cb, void *userdata) +{ + if (cb != NULL) { + p->cb = cb; + p->cb_data = userdata; + } } /** @@ -495,7 +515,8 @@ void ssh_poll_set_callback(ssh_poll_handle p, ssh_poll_callback cb, void *userda * for the next 5. Set it to 0 if you want to use the * library's default value. */ -ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size) { +ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size) +{ ssh_poll_ctx ctx; ctx = malloc(sizeof(struct ssh_poll_ctx_struct)); @@ -518,25 +539,27 @@ ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size) { * * @param ctx Pointer to an already allocated poll context. */ -void ssh_poll_ctx_free(ssh_poll_ctx ctx) { - if (ctx->polls_allocated > 0) { - while (ctx->polls_used > 0){ - ssh_poll_handle p = ctx->pollptrs[0]; - /* - * The free function calls ssh_poll_ctx_remove() and decrements - * ctx->polls_used - */ - ssh_poll_free(p); - } +void ssh_poll_ctx_free(ssh_poll_ctx ctx) +{ + if (ctx->polls_allocated > 0) { + while (ctx->polls_used > 0){ + ssh_poll_handle p = ctx->pollptrs[0]; + /* + * The free function calls ssh_poll_ctx_remove() and decrements + * ctx->polls_used + */ + ssh_poll_free(p); + } - SAFE_FREE(ctx->pollptrs); - SAFE_FREE(ctx->pollfds); - } + SAFE_FREE(ctx->pollptrs); + SAFE_FREE(ctx->pollfds); + } - SAFE_FREE(ctx); + SAFE_FREE(ctx); } -static int ssh_poll_ctx_resize(ssh_poll_ctx ctx, size_t new_size) { +static int ssh_poll_ctx_resize(ssh_poll_ctx ctx, size_t new_size) +{ ssh_poll_handle *pollptrs; ssh_pollfd_t *pollfds; @@ -570,7 +593,8 @@ static int ssh_poll_ctx_resize(ssh_poll_ctx ctx, size_t new_size) { * * @return 0 on success, < 0 on error */ -int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p) { +int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p) +{ socket_t fd; if (p->ctx != NULL) { @@ -604,7 +628,7 @@ int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p) { */ int ssh_poll_ctx_add_socket (ssh_poll_ctx ctx, ssh_socket s) { - ssh_poll_handle p; + ssh_poll_handle p = NULL; int ret; p = ssh_socket_get_poll_handle(s); @@ -622,7 +646,8 @@ int ssh_poll_ctx_add_socket (ssh_poll_ctx ctx, ssh_socket s) * @param ctx Pointer to an already allocated poll context. * @param p Pointer to an already allocated poll object. */ -void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p) { +void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p) +{ size_t i; i = p->x.idx; @@ -648,7 +673,7 @@ void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p) { * @brief Poll all the sockets associated through a poll object with a * poll context. If any of the events are set after the poll, the * call back function of the socket will be called. - * This function should be called once within the programs main loop. + * This function should be called once within the program's main loop. * * @param ctx Pointer to an already allocated poll context. * @param timeout An upper limit on the time for which ssh_poll_ctx() will @@ -657,7 +682,7 @@ void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p) { * the poll() function. * @returns SSH_OK No error. * SSH_ERROR Error happened during the poll. - * SSH_AGAIN Timeout occured + * SSH_AGAIN Timeout occurred */ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) @@ -673,6 +698,15 @@ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) return SSH_ERROR; } + /* Allow only POLLOUT events on locked sockets as that means we are called + * recursively and we only want process the POLLOUT events here to flush + * output buffer */ + for (i = 0; i < ctx->polls_used; i++) { + /* The lock allows only POLLOUT events: drop the rest */ + if (ctx->pollptrs[i]->lock_cnt > 0) { + ctx->pollfds[i].events &= POLLOUT; + } + } ssh_timestamp_init(&ts); do { int tm = ssh_timeout_update(&ts, timeout); @@ -688,17 +722,24 @@ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) used = ctx->polls_used; for (i = 0; i < used && rc > 0; ) { - if (!ctx->pollfds[i].revents || ctx->pollptrs[i]->lock) { + revents = ctx->pollfds[i].revents; + /* Do not pass any other events except for POLLOUT to callback when + * called recursively more than 2 times. On s390x the poll will be + * spammed with POLLHUP events causing infinite recursion when the user + * callback issues some write/flush/poll calls. */ + if (ctx->pollptrs[i]->lock_cnt > 2) { + revents &= POLLOUT; + } + if (revents == 0) { i++; } else { int ret; p = ctx->pollptrs[i]; fd = ctx->pollfds[i].fd; - revents = ctx->pollfds[i].revents; /* avoid having any event caught during callback */ ctx->pollfds[i].events = 0; - p->lock = 1; + p->lock_cnt++; if (p->cb && (ret = p->cb(p, fd, revents, p->cb_data)) < 0) { if (ret == -2) { return -1; @@ -709,7 +750,7 @@ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) } else { ctx->pollfds[i].revents = 0; ctx->pollfds[i].events = p->events; - p->lock = 0; + p->lock_cnt--; i++; } @@ -727,12 +768,13 @@ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) * @param session SSH session * @returns the default ssh_poll_ctx */ -ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session){ - if(session->default_poll_ctx != NULL) - return session->default_poll_ctx; - /* 2 is enough for the default one */ - session->default_poll_ctx = ssh_poll_ctx_new(2); - return session->default_poll_ctx; +ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session) +{ + if(session->default_poll_ctx != NULL) + return session->default_poll_ctx; + /* 2 is enough for the default one */ + session->default_poll_ctx = ssh_poll_ctx_new(2); + return session->default_poll_ctx; } /* public event API */ @@ -754,10 +796,11 @@ struct ssh_event_struct { * ssh_session objects and socket fd which are going to be polled at the * same time as the event context. You would need a single event context * per thread. - * + * * @return The ssh_event object on success, NULL on failure. */ -ssh_event ssh_event_new(void) { +ssh_event ssh_event_new(void) +{ ssh_event event; event = malloc(sizeof(struct ssh_event_struct)); @@ -784,12 +827,14 @@ ssh_event ssh_event_new(void) { return event; } -static int ssh_event_fd_wrapper_callback(ssh_poll_handle p, socket_t fd, int revents, - void *userdata) { +static int +ssh_event_fd_wrapper_callback(ssh_poll_handle p, socket_t fd, int revents, + void *userdata) +{ struct ssh_event_fd_wrapper *pw = (struct ssh_event_fd_wrapper *)userdata; (void)p; - if(pw->cb != NULL) { + if (pw->cb != NULL) { return pw->cb(fd, revents, pw->userdata); } return 0; @@ -812,11 +857,13 @@ static int ssh_event_fd_wrapper_callback(ssh_poll_handle p, socket_t fd, int rev * @returns SSH_OK on success * SSH_ERROR on failure */ -int ssh_event_add_fd(ssh_event event, socket_t fd, short events, - ssh_event_callback cb, void *userdata) { +int +ssh_event_add_fd(ssh_event event, socket_t fd, short events, + ssh_event_callback cb, void *userdata) +{ ssh_poll_handle p; struct ssh_event_fd_wrapper *pw; - + if(event == NULL || event->ctx == NULL || cb == NULL || fd == SSH_INVALID_SOCKET) { return SSH_ERROR; @@ -872,7 +919,7 @@ void ssh_event_remove_poll(ssh_event event, ssh_poll_handle p) } /** - * @brief remove the poll handle from session and assign them to a event, + * @brief remove the poll handle from session and assign them to an event, * when used in blocking mode. * * @param event The ssh_event object @@ -881,7 +928,8 @@ void ssh_event_remove_poll(ssh_event event, ssh_poll_handle p) * @returns SSH_OK on success * SSH_ERROR on failure */ -int ssh_event_add_session(ssh_event event, ssh_session session) { +int ssh_event_add_session(ssh_event event, ssh_session session) +{ ssh_poll_handle p; #ifdef WITH_SERVER struct ssh_iterator *iterator; @@ -933,16 +981,19 @@ int ssh_event_add_session(ssh_event event, ssh_session session) { * * @return SSH_ERROR in case of error */ -int ssh_event_add_connector(ssh_event event, ssh_connector connector){ +int ssh_event_add_connector(ssh_event event, ssh_connector connector) +{ return ssh_connector_set_event(connector, event); } /** - * @brief Poll all the sockets and sessions associated through an event object.i + * @brief Poll all the sockets and sessions associated through an event object. * * If any of the events are set after the poll, the call back functions of the * sessions or sockets will be called. * This function should be called once within the programs main loop. + * In case of failure, the errno should be consulted to find more information + * about the failure set by underlying poll imlpementation. * * @param event The ssh_event object to poll. * @@ -951,13 +1002,15 @@ int ssh_event_add_connector(ssh_event event, ssh_connector connector){ * means an infinite timeout. This parameter is passed to * the poll() function. * @returns SSH_OK on success. - * SSH_ERROR Error happened during the poll. - * SSH_AGAIN Timeout occured + * SSH_ERROR Error happened during the poll. Check errno to get more + * details about why it failed. + * SSH_AGAIN Timeout occurred */ -int ssh_event_dopoll(ssh_event event, int timeout) { +int ssh_event_dopoll(ssh_event event, int timeout) +{ int rc; - if(event == NULL || event->ctx == NULL) { + if (event == NULL || event->ctx == NULL) { return SSH_ERROR; } rc = ssh_poll_ctx_dopoll(event->ctx, timeout); @@ -973,7 +1026,8 @@ int ssh_event_dopoll(ssh_event event, int timeout) { * @returns SSH_OK on success * SSH_ERROR on failure */ -int ssh_event_remove_fd(ssh_event event, socket_t fd) { +int ssh_event_remove_fd(ssh_event event, socket_t fd) +{ register size_t i, used; int rc = SSH_ERROR; @@ -1019,7 +1073,8 @@ int ssh_event_remove_fd(ssh_event event, socket_t fd) { * @returns SSH_OK on success * SSH_ERROR on failure */ -int ssh_event_remove_session(ssh_event event, ssh_session session) { +int ssh_event_remove_session(ssh_event event, ssh_session session) +{ ssh_poll_handle p; register size_t i, used; int rc = SSH_ERROR; @@ -1027,14 +1082,14 @@ int ssh_event_remove_session(ssh_event event, ssh_session session) { struct ssh_iterator *iterator; #endif - if(event == NULL || event->ctx == NULL || session == NULL) { + if (event == NULL || event->ctx == NULL || session == NULL) { return SSH_ERROR; } used = event->ctx->polls_used; - for(i = 0; i < used; i++) { - p = event->ctx->pollptrs[i]; - if(p->session == session){ + for (i = 0; i < used; i++) { + p = event->ctx->pollptrs[i]; + if (p->session == session) { /* * ssh_poll_ctx_remove() decrements * event->ctx->polls_used @@ -1054,8 +1109,8 @@ int ssh_event_remove_session(ssh_event event, ssh_session session) { } #ifdef WITH_SERVER iterator = ssh_list_get_iterator(event->sessions); - while(iterator != NULL) { - if((ssh_session)iterator->data == session) { + while (iterator != NULL) { + if ((ssh_session)iterator->data == session) { ssh_list_remove(event->sessions, iterator); /* there should be only one instance of this session */ break; @@ -1073,7 +1128,8 @@ int ssh_event_remove_session(ssh_event event, ssh_session session) { * @return SSH_OK on success * @return SSH_ERROR on failure */ -int ssh_event_remove_connector(ssh_event event, ssh_connector connector){ +int ssh_event_remove_connector(ssh_event event, ssh_connector connector) +{ (void)event; return ssh_connector_remove_event(connector); } @@ -1091,13 +1147,13 @@ void ssh_event_free(ssh_event event) size_t used, i; ssh_poll_handle p; - if(event == NULL) { + if (event == NULL) { return; } if (event->ctx != NULL) { used = event->ctx->polls_used; - for(i = 0; i < used; i++) { + for (i = 0; i < used; i++) { p = event->ctx->pollptrs[i]; if (p->session != NULL) { ssh_poll_ctx_remove(event->ctx, p); @@ -1110,7 +1166,7 @@ void ssh_event_free(ssh_event event) ssh_poll_ctx_free(event->ctx); } #ifdef WITH_SERVER - if(event->sessions != NULL) { + if (event->sessions != NULL) { ssh_list_free(event->sessions); } #endif @@ -37,10 +37,14 @@ * * SCP protocol over SSH functions * + * @deprecated Please use SFTP instead + * * @{ */ /** + * @deprecated Please use SFTP instead + * * @brief Create a new scp session. * * @param[in] session The SSH session to use. @@ -62,7 +66,7 @@ ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location) { ssh_scp scp = NULL; - if (session == NULL) { + if (session == NULL || location == NULL) { goto error; } @@ -108,6 +112,8 @@ error: } /** + * @deprecated Please use SFTP instead + * * @brief Initialize the scp channel. * * @param[in] scp The scp context to initialize. @@ -119,7 +125,7 @@ error: int ssh_scp_init(ssh_scp scp) { int rc; - char execbuffer[1024] = {0}; + char execbuffer[PATH_MAX] = {0}; char *quoted_location = NULL; size_t quoted_location_len = 0; size_t scp_location_len; @@ -140,7 +146,7 @@ int ssh_scp_init(ssh_scp scp) return SSH_ERROR; } - SSH_LOG(SSH_LOG_PROTOCOL, "Initializing scp session %s %son location '%s'", + SSH_LOG(SSH_LOG_DEBUG, "Initializing scp session %s %son location '%s'", scp->mode == SSH_SCP_WRITE?"write":"read", scp->recursive ? "recursive " : "", scp->location); @@ -230,6 +236,8 @@ int ssh_scp_init(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Close the scp channel. * * @param[in] scp The scp context to close. @@ -277,6 +285,8 @@ int ssh_scp_close(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Free a scp context. * * @param[in] scp The context to free. @@ -304,6 +314,8 @@ void ssh_scp_free(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Create a directory in a scp in sink mode. * * @param[in] scp The scp handle. @@ -313,13 +325,13 @@ void ssh_scp_free(ssh_scp scp) * @param[in] mode The UNIX permissions for the new directory, e.g. 0755. * * @returns SSH_OK if the directory has been created, SSH_ERROR if - * an error occured. + * an error occurred. * * @see ssh_scp_leave_directory() */ int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode) { - char buffer[1024] = {0}; + char buffer[PATH_MAX] = {0}; int rc; char *dir = NULL; char *perms = NULL; @@ -364,7 +376,7 @@ int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode) goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, + SSH_LOG(SSH_LOG_DEBUG, "SCP pushing directory %s with permissions '%s'", vis_encoded, perms); @@ -399,10 +411,12 @@ error: } /** + * @deprecated Please use SFTP instead + * * @brief Leave a directory. * * @returns SSH_OK if the directory has been left, SSH_ERROR if an - * error occured. + * error occurred. * * @see ssh_scp_push_directory() */ @@ -436,6 +450,8 @@ int ssh_scp_leave_directory(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Initialize the sending of a file to a scp in sink mode, using a 64-bit * size. * @@ -449,14 +465,14 @@ int ssh_scp_leave_directory(ssh_scp scp) * @param[in] mode The UNIX permissions for the new file, e.g. 0644. * * @returns SSH_OK if the file is ready to be sent, SSH_ERROR if an - * error occured. + * error occurred. * * @see ssh_scp_push_file() */ int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, int mode) { - char buffer[1024] = {0}; + char buffer[PATH_MAX] = {0}; int rc; char *file = NULL; char *perms = NULL; @@ -501,7 +517,7 @@ int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, + SSH_LOG(SSH_LOG_DEBUG, "SCP pushing file %s, size %" PRIu64 " with permissions '%s'", vis_encoded, size, perms); @@ -540,6 +556,8 @@ error: } /** + * @deprecated Please use SFTP instead + * * @brief Initialize the sending of a file to a scp in sink mode. * * @param[in] scp The scp handle. @@ -552,7 +570,7 @@ error: * @param[in] mode The UNIX permissions for the new file, e.g. 0644. * * @returns SSH_OK if the file is ready to be sent, SSH_ERROR if an - * error occured. + * error occurred. */ int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int mode) { @@ -562,6 +580,8 @@ int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int mode) /** * @internal * + * @deprecated Please use SFTP instead + * * @brief Wait for a response of the scp server. * * @param[in] scp The scp handle. @@ -569,7 +589,7 @@ int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int mode) * @param[out] response A pointer where the response message must be copied if * any. This pointer must then be free'd. * - * @returns The return code, SSH_ERROR a error occured. + * @returns The return code, SSH_ERROR a error occurred. */ int ssh_scp_response(ssh_scp scp, char **response) { @@ -628,6 +648,8 @@ int ssh_scp_response(ssh_scp scp, char **response) } /** + * @deprecated Please use SFTP instead + * * @brief Write into a remote scp file. * * @param[in] scp The scp handle. @@ -637,7 +659,7 @@ int ssh_scp_response(ssh_scp scp, char **response) * @param[in] len The number of bytes to write. * * @returns SSH_OK if the write was successful, SSH_ERROR an error - * occured while writing. + * occurred while writing. */ int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len) { @@ -671,7 +693,6 @@ int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len) scp->processed += w; } else { scp->state = SSH_SCP_ERROR; - //return = channel_get_exit_status(scp->channel); return SSH_ERROR; } @@ -702,6 +723,8 @@ int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len) } /** + * @deprecated Please use SFTP instead + * * @brief Read a string on a channel, terminated by '\n' * * @param[in] scp The scp handle. @@ -713,7 +736,7 @@ int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len) * null-terminated. * * @returns SSH_OK if the string was read, SSH_ERROR if an error - * occured while reading. + * occurred while reading. */ int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len) { @@ -748,6 +771,8 @@ int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len) } /** + * @deprecated Please use SFTP instead + * * @brief Wait for a scp request (file, directory). * * @returns SSH_SCP_REQUEST_NEWFILE: The other side is sending @@ -769,7 +794,7 @@ int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len) */ int ssh_scp_pull_request(ssh_scp scp) { - char buffer[MAX_BUF_SIZE] = {0}; + char buffer[PATH_MAX] = {0}; char *mode = NULL; char *p, *tmp; uint64_t size; @@ -800,7 +825,7 @@ int ssh_scp_pull_request(ssh_scp scp) *p = '\0'; } - SSH_LOG(SSH_LOG_PROTOCOL, "Received SCP request: '%s'", buffer); + SSH_LOG(SSH_LOG_DEBUG, "Received SCP request: '%s'", buffer); switch(buffer[0]) { case 'C': /* File */ @@ -859,7 +884,7 @@ int ssh_scp_pull_request(ssh_scp scp) return SSH_ERROR; } - /* a parsing error occured */ + /* a parsing error occurred */ error: SAFE_FREE(name); SAFE_FREE(mode); @@ -869,6 +894,8 @@ error: } /** + * @deprecated Please use SFTP instead + * * @brief Deny the transfer of a file or creation of a directory coming from the * remote party. * @@ -881,7 +908,8 @@ error: */ int ssh_scp_deny_request(ssh_scp scp, const char *reason) { - char buffer[MAX_BUF_SIZE] = {0}; + char *buffer = NULL; + size_t len; int rc; if (scp == NULL) { @@ -894,8 +922,15 @@ int ssh_scp_deny_request(ssh_scp scp, const char *reason) return SSH_ERROR; } - snprintf(buffer, sizeof(buffer), "%c%s\n", 2, reason); - rc = ssh_channel_write(scp->channel, buffer, strlen(buffer)); + len = strlen(reason) + 3; + buffer = malloc(len); + if (buffer == NULL) { + return SSH_ERROR; + } + + snprintf(buffer, len, "%c%s\n", 2, reason); + rc = ssh_channel_write(scp->channel, buffer, len - 1); + free(buffer); if (rc == SSH_ERROR) { return SSH_ERROR; } @@ -907,6 +942,8 @@ int ssh_scp_deny_request(ssh_scp scp, const char *reason) } /** + * @deprecated Please use SFTP instead + * * @brief Accepts transfer of a file or creation of a directory coming from the * remote party. * @@ -943,14 +980,18 @@ int ssh_scp_accept_request(ssh_scp scp) return SSH_OK; } -/** @brief Read from a remote scp file +/** + * @deprecated Please use SFTP instead + * + * @brief Read from a remote scp file + * * @param[in] scp The scp handle. * * @param[in] buffer The destination buffer. * * @param[in] size The size of the buffer. * - * @returns The nNumber of bytes read, SSH_ERROR if an error occured + * @returns The number of bytes read, SSH_ERROR if an error occurred * while reading. */ int ssh_scp_read(ssh_scp scp, void *buffer, size_t size) @@ -1014,6 +1055,8 @@ int ssh_scp_read(ssh_scp scp, void *buffer, size_t size) } /** + * @deprecated Please use SFTP instead + * * @brief Get the name of the directory or file being pushed from the other * party. * @@ -1030,6 +1073,8 @@ const char *ssh_scp_request_get_filename(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Get the permissions of the directory or file being pushed from the * other party. * @@ -1044,7 +1089,10 @@ int ssh_scp_request_get_permissions(ssh_scp scp) return scp->request_mode; } -/** @brief Get the size of the file being pushed from the other party. +/** + * @deprecated Please use SFTP instead + * + * @brief Get the size of the file being pushed from the other party. * * @returns The numeric size of the file being read. * @warning The real size may not fit in a 32 bits field and may @@ -1059,7 +1107,10 @@ size_t ssh_scp_request_get_size(ssh_scp scp) return (size_t)scp->filelen; } -/** @brief Get the size of the file being pushed from the other party. +/** + * @deprecated Please use SFTP instead + * + * @brief Get the size of the file being pushed from the other party. * * @returns The numeric size of the file being read. */ @@ -1072,6 +1123,8 @@ uint64_t ssh_scp_request_get_size64(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Convert a scp text mode to an integer. * * @param[in] mode The mode to convert, e.g. "0644". @@ -1085,6 +1138,8 @@ int ssh_scp_integer_mode(const char *mode) } /** + * @deprecated Please use SFTP instead + * * @brief Convert a unix mode into a scp string. * * @param[in] mode The mode to convert, e.g. 420 or 0644. @@ -1100,6 +1155,8 @@ char *ssh_scp_string_mode(int mode) } /** + * @deprecated Please use SFTP instead + * * @brief Get the warning string from a scp handle. * * @param[in] scp The scp handle. diff --git a/src/server.c b/src/server.c index 841a1c42..28c3c015 100644 --- a/src/server.c +++ b/src/server.c @@ -92,7 +92,11 @@ int server_set_kex(ssh_session session) size_t len; int ok; - ZERO_STRUCTP(server); + /* Skip if already set, for example for the rekey or when we do the guessing + * it could have been already used to make some protocol decisions. */ + if (server->methods[0] != NULL) { + return SSH_OK; + } ok = ssh_get_random(server->cookie, 16, 0); if (!ok) { @@ -113,15 +117,6 @@ int server_set_kex(ssh_session session) ",%s", session->srv.ecdsa_key->type_c); } #endif -#ifdef HAVE_DSA - if (session->srv.dsa_key != NULL) { - len = strlen(hostkeys); - keytype = ssh_key_type(session->srv.dsa_key); - - snprintf(hostkeys + len, sizeof(hostkeys) - len, - ",%s", ssh_key_type_to_char(keytype)); - } -#endif if (session->srv.rsa_key != NULL) { /* We support also the SHA2 variants */ len = strlen(hostkeys); @@ -160,7 +155,8 @@ int server_set_kex(ssh_session session) rc = ssh_options_set_algo(session, SSH_HOSTKEYS, - kept); + kept, + &session->opts.wanted_methods[SSH_HOSTKEYS]); SAFE_FREE(kept); if (rc < 0) { return -1; @@ -191,7 +187,13 @@ int server_set_kex(ssh_session session) } } - return 0; + /* Do not append the extensions during rekey */ + if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) { + return SSH_OK; + } + + rc = ssh_kex_append_extensions(session, server); + return rc; } int ssh_server_init_kex(ssh_session session) { @@ -281,9 +283,6 @@ ssh_get_key_params(ssh_session session, int rc; switch(session->srv.hostkey) { - case SSH_KEYTYPE_DSS: - *privkey = session->srv.dsa_key; - break; case SSH_KEYTYPE_RSA: *privkey = session->srv.rsa_key; break; @@ -335,117 +334,127 @@ ssh_get_key_params(ssh_session session, * @brief A function to be called each time a step has been done in the * connection. */ -static void ssh_server_connection_callback(ssh_session session){ +static void ssh_server_connection_callback(ssh_session session) +{ int rc; - switch(session->session_state){ - case SSH_SESSION_STATE_NONE: - case SSH_SESSION_STATE_CONNECTING: - case SSH_SESSION_STATE_SOCKET_CONNECTED: - break; - case SSH_SESSION_STATE_BANNER_RECEIVED: - if (session->clientbanner == NULL) { + switch (session->session_state) { + case SSH_SESSION_STATE_NONE: + case SSH_SESSION_STATE_CONNECTING: + case SSH_SESSION_STATE_SOCKET_CONNECTED: + break; + case SSH_SESSION_STATE_BANNER_RECEIVED: + if (session->clientbanner == NULL) { + goto error; + } + set_status(session, 0.4f); + SSH_LOG(SSH_LOG_DEBUG, + "SSH client banner: %s", session->clientbanner); + + /* Here we analyze the different protocols the server allows. */ + rc = ssh_analyze_banner(session, 1); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "No version of SSH protocol usable (banner: %s)", + session->clientbanner); + goto error; + } + + /* from now, the packet layer is handling incoming packets */ + ssh_packet_register_socket_callback(session, session->socket); + + ssh_packet_set_default_callbacks(session); + set_status(session, 0.5f); + session->session_state = SSH_SESSION_STATE_INITIAL_KEX; + rc = ssh_send_kex(session); + if (rc < 0) { + goto error; + } + break; + case SSH_SESSION_STATE_INITIAL_KEX: + /* TODO: This state should disappear in favor of get_key handle */ + break; + case SSH_SESSION_STATE_KEXINIT_RECEIVED: + set_status(session, 0.6f); + if ((session->flags & SSH_SESSION_FLAG_KEXINIT_SENT) == 0) { + rc = server_set_kex(session); + if (rc == SSH_ERROR) { goto error; } - set_status(session, 0.4f); - SSH_LOG(SSH_LOG_PROTOCOL, - "SSH client banner: %s", session->clientbanner); - - /* Here we analyze the different protocols the server allows. */ - rc = ssh_analyze_banner(session, 1); + /* We are in a rekeying, so we need to send the server kex */ + rc = ssh_send_kex(session); if (rc < 0) { - ssh_set_error(session, SSH_FATAL, - "No version of SSH protocol usable (banner: %s)", - session->clientbanner); goto error; } + } + ssh_list_kex(&session->next_crypto->client_kex); // log client kex + rc = ssh_kex_select_methods(session); + if (rc < 0) { + goto error; + } + rc = crypt_set_algorithms_server(session); + if (rc == SSH_ERROR) { + goto error; + } + set_status(session, 0.8f); + session->session_state = SSH_SESSION_STATE_DH; + break; + case SSH_SESSION_STATE_DH: + if (session->dh_handshake_state == DH_STATE_FINISHED) { - /* from now, the packet layer is handling incoming packets */ - session->socket_callbacks.data=ssh_packet_socket_callback; - ssh_packet_register_socket_callback(session, session->socket); - - ssh_packet_set_default_callbacks(session); - set_status(session, 0.5f); - session->session_state=SSH_SESSION_STATE_INITIAL_KEX; - if (ssh_send_kex(session, 1) < 0) { - goto error; - } - break; - case SSH_SESSION_STATE_INITIAL_KEX: - /* TODO: This state should disappear in favor of get_key handle */ - break; - case SSH_SESSION_STATE_KEXINIT_RECEIVED: - set_status(session,0.6f); - if(session->next_crypto->server_kex.methods[0]==NULL){ - if(server_set_kex(session) == SSH_ERROR) - goto error; - /* We are in a rekeying, so we need to send the server kex */ - if(ssh_send_kex(session, 1) < 0) - goto error; - } - ssh_list_kex(&session->next_crypto->client_kex); // log client kex - if (ssh_kex_select_methods(session) < 0) { + rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); + if (rc != SSH_OK) { goto error; } - if (crypt_set_algorithms_server(session) == SSH_ERROR) - goto error; - set_status(session,0.8f); - session->session_state=SSH_SESSION_STATE_DH; - break; - case SSH_SESSION_STATE_DH: - if(session->dh_handshake_state==DH_STATE_FINISHED){ - - rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); - if (rc != SSH_OK) { - goto error; - } + /* + * If the client supports extension negotiation, we will send + * our supported extensions now. This is the first message after + * sending NEWKEYS message and after turning on crypto. + */ + if (session->extensions & SSH_EXT_NEGOTIATION && + session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { /* - * If the client supports extension negotiation, we will send - * our supported extensions now. This is the first message after - * sending NEWKEYS message and after turning on crypto. + * Only send an SSH_MSG_EXT_INFO message the first time the + * client undergoes NEWKEYS. It is unexpected for this message + * to be sent upon rekey, and may cause clients to log error + * messages. + * + * The session_state can not be used for this purpose because it + * is re-set to SSH_SESSION_STATE_KEXINIT_RECEIVED during rekey. + * So, use the connected flag which transitions from non-zero + * below. + * + * See also: + * - https://bugzilla.mindrot.org/show_bug.cgi?id=2929 */ - if (session->extensions & SSH_EXT_NEGOTIATION && - session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { - - /* - * Only send an SSH_MSG_EXT_INFO message the first time the client - * undergoes NEWKEYS. It is unexpected for this message to be sent - * upon rekey, and may cause clients to log error messages. - * - * The session_state can not be used for this purpose because it is - * re-set to SSH_SESSION_STATE_KEXINIT_RECEIVED during rekey. So, - * use the connected flag which transitions from non-zero below. - * - * See also: - * - https://bugzilla.mindrot.org/show_bug.cgi?id=2929 - */ - if (session->connected == 0) { - ssh_server_send_extensions(session); - } + if (session->connected == 0) { + ssh_server_send_extensions(session); } + } - set_status(session,1.0f); - session->connected = 1; - session->session_state=SSH_SESSION_STATE_AUTHENTICATING; - if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) - session->session_state = SSH_SESSION_STATE_AUTHENTICATED; + set_status(session, 1.0f); + session->connected = 1; + session->session_state = SSH_SESSION_STATE_AUTHENTICATING; + if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) + session->session_state = SSH_SESSION_STATE_AUTHENTICATED; - } - break; - case SSH_SESSION_STATE_AUTHENTICATING: - break; - case SSH_SESSION_STATE_ERROR: - goto error; - default: - ssh_set_error(session,SSH_FATAL,"Invalid state %d",session->session_state); + } + break; + case SSH_SESSION_STATE_AUTHENTICATING: + break; + case SSH_SESSION_STATE_ERROR: + goto error; + default: + ssh_set_error(session, SSH_FATAL, "Invalid state %d", + session->session_state); } return; error: ssh_socket_close(session->socket); session->alive = 0; - session->session_state=SSH_SESSION_STATE_ERROR; + session->session_state = SSH_SESSION_STATE_ERROR; } /** @@ -459,16 +468,17 @@ error: * @param user is a pointer to session * @returns Number of bytes processed, or zero if the banner is not complete. */ -static int callback_receive_banner(const void *data, size_t len, void *user) { - char *buffer = (char *) data; - ssh_session session = (ssh_session) user; +static size_t callback_receive_banner(const void *data, size_t len, void *user) +{ + char *buffer = (char *)data; + ssh_session session = (ssh_session)user; char *str = NULL; size_t i; - int ret=0; + size_t processed = 0; for (i = 0; i < len; i++) { #ifdef WITH_PCAP - if(session->pcap_ctx && buffer[i] == '\n') { + if (session->pcap_ctx && buffer[i] == '\n') { ssh_pcap_context_write(session->pcap_ctx, SSH_PCAP_DIR_IN, buffer, @@ -477,33 +487,34 @@ static int callback_receive_banner(const void *data, size_t len, void *user) { } #endif if (buffer[i] == '\r') { - buffer[i]='\0'; + buffer[i] = '\0'; } if (buffer[i] == '\n') { - buffer[i]='\0'; + buffer[i] = '\0'; str = strdup(buffer); /* number of bytes read */ - ret = i + 1; + processed = i + 1; session->clientbanner = str; session->session_state = SSH_SESSION_STATE_BANNER_RECEIVED; SSH_LOG(SSH_LOG_PACKET, "Received banner: %s", str); session->ssh_connection_callback(session); - return ret; + return processed; } - if(i > 127) { + if (i > 127) { /* Too big banner */ session->session_state = SSH_SESSION_STATE_ERROR; - ssh_set_error(session, SSH_FATAL, "Receiving banner: too large banner"); + ssh_set_error(session, SSH_FATAL, + "Receiving banner: too large banner"); return 0; } } - return ret; + return processed; } /* returns 0 until the key exchange is not finished */ @@ -524,11 +535,40 @@ void ssh_set_auth_methods(ssh_session session, int auth_methods) session->auth.supported_methods = (uint32_t)auth_methods & 0x3fU; } +int ssh_send_issue_banner(ssh_session session, const ssh_string banner) +{ + int rc = SSH_ERROR; + + if (session == NULL) { + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sending a server issue banner"); + + rc = ssh_buffer_pack(session->out_buffer, + "bSs", + SSH2_MSG_USERAUTH_BANNER, + banner, + ""); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + rc = ssh_packet_send(session); + return rc; +} + /* Do the banner and key exchange */ -int ssh_handle_key_exchange(ssh_session session) { +int ssh_handle_key_exchange(ssh_session session) +{ int rc; - if (session->session_state != SSH_SESSION_STATE_NONE) - goto pending; + + if (session->session_state != SSH_SESSION_STATE_NONE) { + goto pending; + } + rc = ssh_send_banner(session, 1); if (rc < 0) { return SSH_ERROR; @@ -539,27 +579,28 @@ int ssh_handle_key_exchange(ssh_session session) { session->ssh_connection_callback = ssh_server_connection_callback; session->session_state = SSH_SESSION_STATE_SOCKET_CONNECTED; ssh_socket_set_callbacks(session->socket,&session->socket_callbacks); - session->socket_callbacks.data=callback_receive_banner; - session->socket_callbacks.exception=ssh_socket_exception_callback; - session->socket_callbacks.userdata=session; + session->socket_callbacks.data = callback_receive_banner; + session->socket_callbacks.exception = ssh_socket_exception_callback; + session->socket_callbacks.userdata = session; rc = server_set_kex(session); if (rc < 0) { return SSH_ERROR; } - pending: +pending: rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, - ssh_server_kex_termination,session); + ssh_server_kex_termination,session); SSH_LOG(SSH_LOG_PACKET, "ssh_handle_key_exchange: current state : %d", - session->session_state); - if (rc != SSH_OK) - return rc; + session->session_state); + if (rc != SSH_OK) { + return rc; + } if (session->session_state == SSH_SESSION_STATE_ERROR || session->session_state == SSH_SESSION_STATE_DISCONNECTED) { - return SSH_ERROR; + return SSH_ERROR; } - return SSH_OK; + return SSH_OK; } /* messages */ @@ -648,7 +689,7 @@ static int ssh_message_channel_request_reply_default(ssh_message msg) { channel = msg->channel_request.channel->remote_channel; SSH_LOG(SSH_LOG_PACKET, - "Sending a default channel_request denied to channel %d", channel); + "Sending a default channel_request denied to channel %" PRIu32, channel); rc = ssh_buffer_pack(msg->session->out_buffer, "bd", @@ -672,6 +713,13 @@ static int ssh_message_service_request_reply_default(ssh_message msg) { return ssh_message_service_reply_success(msg); } +/** + * @brief Sends SERVICE_ACCEPT to the client + * + * @param msg The message to reply to + * + * @returns SSH_OK when success otherwise SSH_ERROR + */ int ssh_message_service_reply_success(ssh_message msg) { ssh_session session; int rc; @@ -696,6 +744,15 @@ int ssh_message_service_reply_success(ssh_message msg) { return rc; } +/** + * @brief Send a global request success message + * + * @param msg The message + * + * @param bound_port The remote bind port + * + * @returns SSH_OK on success, otherwise SSH_ERROR + */ int ssh_message_global_request_reply_success(ssh_message msg, uint16_t bound_port) { int rc; @@ -707,7 +764,7 @@ int ssh_message_global_request_reply_success(ssh_message msg, uint16_t bound_por goto error; } - if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD + if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD && msg->global_request.bind_port == 0) { rc = ssh_buffer_pack(msg->session->out_buffer, "d", bound_port); if (rc != SSH_OK) { @@ -719,7 +776,7 @@ int ssh_message_global_request_reply_success(ssh_message msg, uint16_t bound_por return ssh_packet_send(msg->session); } - if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD + if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD && msg->global_request.bind_port == 0) { SSH_LOG(SSH_LOG_PACKET, "The client doesn't want to know the remote port!"); @@ -774,6 +831,13 @@ int ssh_message_reply_default(ssh_message msg) { return -1; } +/** + * @brief Gets the service name from the service request message + * + * @param msg The service request message + * + * @returns the service name from the message + */ const char *ssh_message_service_service(ssh_message msg){ if (msg == NULL) { return NULL; @@ -805,7 +869,6 @@ ssh_key ssh_message_auth_pubkey(ssh_message msg) { return msg->auth_request.pubkey; } -/* Get the publickey of an auth request */ ssh_public_key ssh_message_auth_publickey(ssh_message msg){ if (msg == NULL) { return NULL; @@ -821,6 +884,13 @@ enum ssh_publickey_state_e ssh_message_auth_publickey_state(ssh_message msg){ return msg->auth_request.signature_state; } +/** + * @brief Check if the message is a keyboard-interactive response + * + * @param msg The message to check + * + * @returns 1 if the message is a response, otherwise 0 + */ int ssh_message_auth_kbdint_is_response(ssh_message msg) { if (msg == NULL) { return -1; @@ -830,6 +900,17 @@ int ssh_message_auth_kbdint_is_response(ssh_message msg) { } /* FIXME: methods should be unsigned */ +/** + * @brief Sets the supported authentication methods to a message + * + * @param msg The message + * + * @param methods Methods to set to the message. + * The supported methods are listed in ssh_set_auth_methods + * @see ssh_set_auth_methods + * + * @returns 0 on success, otherwise -1 + */ int ssh_message_auth_set_methods(ssh_message msg, int methods) { if (msg == NULL || msg->session == NULL) { return -1; @@ -884,9 +965,8 @@ int ssh_message_auth_interactive_request(ssh_message msg, const char *name, /* fill in the kbdint structure */ if (msg->session->kbdint == NULL) { - SSH_LOG(SSH_LOG_PROTOCOL, "Warning: Got a " - "keyboard-interactive response but it " - "seems we didn't send the request."); + SSH_LOG(SSH_LOG_DEBUG, "Warning: Got a keyboard-interactive response " + "but it seems we didn't send the request."); msg->session->kbdint = ssh_kbdint_new(); if (msg->session->kbdint == NULL) { @@ -950,6 +1030,17 @@ int ssh_message_auth_interactive_request(ssh_message msg, const char *name, return rc; } +/** + * @brief Sends SSH2_MSG_USERAUTH_SUCCESS or SSH2_MSG_USERAUTH_FAILURE message + * depending on the success of the authentication method + * + * @param session The session to reply to + * + * @param partial Denotes if the authentication process was partially completed + * (unsuccessful) + * + * @returns SSH_OK on success, otherwise SSH_ERROR + */ int ssh_auth_reply_success(ssh_session session, int partial) { struct ssh_crypto_struct *crypto = NULL; @@ -981,13 +1072,13 @@ int ssh_auth_reply_success(ssh_session session, int partial) crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); if (crypto != NULL && crypto->delayed_compress_out) { - SSH_LOG(SSH_LOG_PROTOCOL, "Enabling delayed compression OUT"); + SSH_LOG(SSH_LOG_DEBUG, "Enabling delayed compression OUT"); crypto->do_compress_out = 1; } crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); if (crypto != NULL && crypto->delayed_compress_in) { - SSH_LOG(SSH_LOG_PROTOCOL, "Enabling delayed compression IN"); + SSH_LOG(SSH_LOG_DEBUG, "Enabling delayed compression IN"); crypto->do_compress_in = 1; } return r; @@ -999,7 +1090,17 @@ int ssh_message_auth_reply_success(ssh_message msg, int partial) { return ssh_auth_reply_success(msg->session, partial); } -/* Answer OK to a pubkey auth request */ +/** + * @brief Answer SSH2_MSG_USERAUTH_PK_OK to a pubkey authentication request + * + * @param msg The message + * + * @param algo The algorithm of the accepted public key + * + * @param pubkey The accepted public key + * + * @returns SSH_OK on success, otherwise SSH_ERROR + */ int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pubkey) { int rc; if (msg == NULL) { @@ -1020,12 +1121,19 @@ int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pu return rc; } +/** + * @brief Answer SSH2_MSG_USERAUTH_PK_OK to a pubkey authentication request + * + * @param msg The message + * + * @returns SSH_OK on success, otherwise SSH_ERROR + */ int ssh_message_auth_reply_pk_ok_simple(ssh_message msg) { ssh_string algo; ssh_string pubkey_blob = NULL; int ret; - algo = ssh_string_from_char(msg->auth_request.pubkey->type_c); + algo = ssh_string_from_char(msg->auth_request.sigtype); if (algo == NULL) { return SSH_ERROR; } @@ -1170,6 +1278,13 @@ int ssh_execute_message_callbacks(ssh_session session){ return SSH_OK; } +/** + * @brief Sends a keepalive message to the session + * + * @param session The session to send the message to + * + * @returns SSH_OK + */ int ssh_send_keepalive(ssh_session session) { /* Client denies the request, so the error code is not meaningful */ diff --git a/src/session.c b/src/session.c index 3199096a..279352b6 100644 --- a/src/session.c +++ b/src/session.c @@ -43,7 +43,7 @@ #define FIRST_CHANNEL 42 // why not ? it helps to find bugs. /** - * @defgroup libssh_session The SSH session functions. + * @defgroup libssh_session The SSH session functions * @ingroup libssh * * Functions that manage a session. @@ -97,36 +97,53 @@ ssh_session ssh_new(void) ssh_set_blocking(session, 1); session->maxchannel = FIRST_CHANNEL; -#ifndef _WIN32 session->agent = ssh_agent_new(session); if (session->agent == NULL) { goto err; } -#endif /* _WIN32 */ /* OPTIONS */ session->opts.StrictHostKeyChecking = 1; - session->opts.port = 0; + session->opts.port = 22; session->opts.fd = -1; session->opts.compressionlevel = 7; session->opts.nodelay = 0; + session->opts.identities_only = false; + session->opts.control_master = SSH_CONTROL_MASTER_NO; session->opts.flags = SSH_OPT_FLAG_PASSWORD_AUTH | SSH_OPT_FLAG_PUBKEY_AUTH | SSH_OPT_FLAG_KBDINT_AUTH | SSH_OPT_FLAG_GSSAPI_AUTH; + session->opts.exp_flags = 0; + session->opts.identity = ssh_list_new(); if (session->opts.identity == NULL) { goto err; } + session->opts.identity_non_exp = ssh_list_new(); + if (session->opts.identity_non_exp == NULL) { + goto err; + } + + session->opts.certificate = ssh_list_new(); + if (session->opts.certificate == NULL) { + goto err; + } + session->opts.certificate_non_exp = ssh_list_new(); + if (session->opts.certificate_non_exp == NULL) { + goto err; + } + /* the default certificates are loaded automatically from the default + * identities later */ id = strdup("%d/id_ed25519"); if (id == NULL) { goto err; } - rc = ssh_list_append(session->opts.identity, id); + rc = ssh_list_append(session->opts.identity_non_exp, id); if (rc == SSH_ERROR) { goto err; } @@ -136,7 +153,7 @@ ssh_session ssh_new(void) if (id == NULL) { goto err; } - rc = ssh_list_append(session->opts.identity, id); + rc = ssh_list_append(session->opts.identity_non_exp, id); if (rc == SSH_ERROR) { goto err; } @@ -146,22 +163,11 @@ ssh_session ssh_new(void) if (id == NULL) { goto err; } - rc = ssh_list_append(session->opts.identity, id); + rc = ssh_list_append(session->opts.identity_non_exp, id); if (rc == SSH_ERROR) { goto err; } -#ifdef HAVE_DSA - id = strdup("%d/id_dsa"); - if (id == NULL) { - goto err; - } - rc = ssh_list_append(session->opts.identity, id); - if (rc == SSH_ERROR) { - goto err; - } -#endif - /* Explicitly initialize states */ session->session_state = SSH_SESSION_STATE_NONE; session->pending_call_state = SSH_PENDING_CALL_NONE; @@ -242,12 +248,8 @@ void ssh_free(ssh_session session) crypto_free(session->current_crypto); crypto_free(session->next_crypto); -#ifndef _WIN32 ssh_agent_free(session->agent); -#endif /* _WIN32 */ - ssh_key_free(session->srv.dsa_key); - session->srv.dsa_key = NULL; ssh_key_free(session->srv.rsa_key); session->srv.rsa_key = NULL; ssh_key_free(session->srv.ecdsa_key); @@ -286,24 +288,59 @@ void ssh_free(ssh_session session) ssh_list_free(session->opts.identity); } + if (session->opts.identity_non_exp) { + char *id; + + for (id = ssh_list_pop_head(char *, session->opts.identity_non_exp); + id != NULL; + id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) { + SAFE_FREE(id); + } + ssh_list_free(session->opts.identity_non_exp); + } + + if (session->opts.certificate) { + char *cert = NULL; + + for (cert = ssh_list_pop_head(char *, session->opts.certificate); + cert != NULL; + cert = ssh_list_pop_head(char *, session->opts.certificate)) { + SAFE_FREE(cert); + } + ssh_list_free(session->opts.certificate); + } + + if (session->opts.certificate_non_exp) { + char *cert = NULL; + + for (cert = ssh_list_pop_head(char *, session->opts.certificate_non_exp); + cert != NULL; + cert = ssh_list_pop_head(char *, session->opts.certificate_non_exp)) { + SAFE_FREE(cert); + } + ssh_list_free(session->opts.certificate_non_exp); + } + while ((b = ssh_list_pop_head(struct ssh_buffer_struct *, session->out_queue)) != NULL) { SSH_BUFFER_FREE(b); } ssh_list_free(session->out_queue); -#ifndef _WIN32 - ssh_agent_state_free (session->agent_state); -#endif + ssh_agent_state_free(session->agent_state); session->agent_state = NULL; SAFE_FREE(session->auth.auto_state); SAFE_FREE(session->serverbanner); SAFE_FREE(session->clientbanner); SAFE_FREE(session->banner); + SAFE_FREE(session->disconnect_message); + SAFE_FREE(session->peer_discon_msg); + SAFE_FREE(session->opts.agent_socket); SAFE_FREE(session->opts.bindaddr); SAFE_FREE(session->opts.custombanner); + SAFE_FREE(session->opts.moduli_file); SAFE_FREE(session->opts.username); SAFE_FREE(session->opts.host); SAFE_FREE(session->opts.sshdir); @@ -313,6 +350,7 @@ void ssh_free(ssh_session session) SAFE_FREE(session->opts.gss_server_identity); SAFE_FREE(session->opts.gss_client_identity); SAFE_FREE(session->opts.pubkey_accepted_types); + SAFE_FREE(session->opts.control_path); for (i = 0; i < SSH_KEX_METHODS; i++) { if (session->opts.wanted_methods[i]) { @@ -458,20 +496,36 @@ const char* ssh_get_hmac_out(ssh_session session) { } /** + * @internal + * @brief Close the connection socket if it is a socket created by us. + * Does not close the sockets provided by the user through options API. + */ +void +ssh_session_socket_close(ssh_session session) +{ + if (session->opts.fd == SSH_INVALID_SOCKET) { + ssh_socket_close(session->socket); + } + session->alive = 0; + session->session_state = SSH_SESSION_STATE_ERROR; +} + +/** * @brief Disconnect impolitely from a remote host by closing the socket. * * Suitable if you forked and want to destroy this session. * * @param[in] session The SSH session to disconnect. */ -void ssh_silent_disconnect(ssh_session session) { - if (session == NULL) { - return; - } +void +ssh_silent_disconnect(ssh_session session) +{ + if (session == NULL) { + return; + } - ssh_socket_close(session->socket); - session->alive = 0; - ssh_disconnect(session); + ssh_session_socket_close(session); + ssh_disconnect(session); } /** @@ -632,9 +686,10 @@ void ssh_set_fd_except(ssh_session session) { * * @return SSH_OK on success, SSH_ERROR otherwise. */ -int ssh_handle_packets(ssh_session session, int timeout) { - ssh_poll_handle spoll; - ssh_poll_ctx ctx; +int ssh_handle_packets(ssh_session session, int timeout) +{ + ssh_poll_handle spoll = NULL; + ssh_poll_ctx ctx = NULL; int tm = timeout; int rc; @@ -643,20 +698,29 @@ int ssh_handle_packets(ssh_session session, int timeout) { } spoll = ssh_socket_get_poll_handle(session->socket); + if (spoll == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } ssh_poll_add_events(spoll, POLLIN); ctx = ssh_poll_get_ctx(spoll); - if (!ctx) { + if (ctx == NULL) { ctx = ssh_poll_get_default_ctx(session); + if (ctx == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } ssh_poll_ctx_add(ctx, spoll); } if (timeout == SSH_TIMEOUT_USER) { - if (ssh_is_blocking(session)) - tm = ssh_make_milliseconds(session->opts.timeout, - session->opts.timeout_usec); - else - tm = 0; + if (ssh_is_blocking(session)) { + tm = ssh_make_milliseconds(session->opts.timeout, + session->opts.timeout_usec); + } else { + tm = 0; + } } rc = ssh_poll_ctx_dopoll(ctx, tm); if (rc == SSH_ERROR) { @@ -679,7 +743,7 @@ int ssh_handle_packets(ssh_session session, int timeout) { * @param[in] timeout Set an upper limit on the time for which this function * will block, in milliseconds. Specifying * SSH_TIMEOUT_INFINITE (-1) means an infinite timeout. - * Specifying SSH_TIMEOUT_USER means to use the timeout + * Specifying SSH_TIMEOUT_USER means using the timeout * specified in options. 0 means poll will return * immediately. * SSH_TIMEOUT_DEFAULT uses the session timeout if set or @@ -693,13 +757,13 @@ int ssh_handle_packets(ssh_session session, int timeout) { * SSH_ERROR otherwise. */ int ssh_handle_packets_termination(ssh_session session, - long timeout, + int timeout, ssh_termination_function fct, void *user) { struct ssh_timestamp ts; - long timeout_ms = SSH_TIMEOUT_INFINITE; - long tm; + int timeout_ms = SSH_TIMEOUT_INFINITE; + int tm; int ret = SSH_OK; /* If a timeout has been provided, use it */ @@ -818,11 +882,11 @@ const char *ssh_get_disconnect_message(ssh_session session) { if (session->session_state != SSH_SESSION_STATE_DISCONNECTED) { ssh_set_error(session, SSH_REQUEST_DENIED, "Connection not closed yet"); - } else if(!session->discon_msg) { + } else if(!session->peer_discon_msg) { ssh_set_error(session, SSH_FATAL, "Connection correctly closed but no disconnect message"); } else { - return session->discon_msg; + return session->peer_discon_msg; } return NULL; @@ -856,7 +920,9 @@ void ssh_socket_exception_callback(int code, int errno_code, void *user){ if (errno_code == 0 && code == SSH_SOCKET_EXCEPTION_EOF) { ssh_set_error(session, SSH_FATAL, "Socket error: disconnected"); } else { - ssh_set_error(session, SSH_FATAL, "Socket error: %s", strerror(errno_code)); + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(session, SSH_FATAL, "Socket error: %s", + ssh_strerror(errno_code, err_msg, SSH_ERRNO_MSG_MAX)); } session->ssh_connection_callback(session); @@ -933,7 +999,7 @@ error: /** * @brief Set the session data counters. * - * This functions sets the counter structures to be used to calculate data + * This function sets the counter structures to be used to calculate data * which comes in and goes out through the session at different levels. * * @code @@ -976,8 +1042,8 @@ int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) { ssh_key pubkey = NULL; ssh_string pubkey_blob = NULL; - MD5CTX ctx; - unsigned char *h; + MD5CTX ctx = NULL; + unsigned char *h = NULL; int rc; if (session == NULL || hash == NULL) { @@ -997,40 +1063,50 @@ int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) *hash = NULL; if (session->current_crypto == NULL || session->current_crypto->server_pubkey == NULL) { - ssh_set_error(session,SSH_FATAL,"No current cryptographic context"); + ssh_set_error(session, SSH_FATAL, "No current cryptographic context"); + return SSH_ERROR; + } + + rc = ssh_get_server_publickey(session, &pubkey); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); + ssh_key_free(pubkey); + if (rc != SSH_OK) { return SSH_ERROR; } h = calloc(MD5_DIGEST_LEN, sizeof(unsigned char)); if (h == NULL) { + SSH_STRING_FREE(pubkey_blob); return SSH_ERROR; } ctx = md5_init(); if (ctx == NULL) { + SSH_STRING_FREE(pubkey_blob); SAFE_FREE(h); return SSH_ERROR; } - rc = ssh_get_server_publickey(session, &pubkey); + rc = md5_update(ctx, + ssh_string_data(pubkey_blob), + ssh_string_len(pubkey_blob)); if (rc != SSH_OK) { - md5_final(h, ctx); + SSH_STRING_FREE(pubkey_blob); + md5_ctx_free(ctx); SAFE_FREE(h); - return SSH_ERROR; + return rc; } - - rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); - ssh_key_free(pubkey); + SSH_STRING_FREE(pubkey_blob); + rc = md5_final(h, ctx); if (rc != SSH_OK) { - md5_final(h, ctx); SAFE_FREE(h); - return SSH_ERROR; + return rc; } - md5_update(ctx, ssh_string_data(pubkey_blob), ssh_string_len(pubkey_blob)); - SSH_STRING_FREE(pubkey_blob); - md5_final(h, ctx); - *hash = h; return MD5_DIGEST_LEN; @@ -1039,14 +1115,15 @@ int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) /** * @brief Deallocate the hash obtained by ssh_get_pubkey_hash. * - * This is required under Microsoft platform as this library might use a + * This is required under Microsoft platform as this library might use a * different C library than your software, hence a different heap. * * @param[in] hash The buffer to deallocate. * * @see ssh_get_pubkey_hash() */ -void ssh_clean_pubkey_hash(unsigned char **hash) { +void ssh_clean_pubkey_hash(unsigned char **hash) +{ SAFE_FREE(*hash); } @@ -1056,9 +1133,9 @@ void ssh_clean_pubkey_hash(unsigned char **hash) { * @param[in] session The session to get the key from. * * @param[out] key A pointer to store the allocated key. You need to free - * the key. + * the key using ssh_key_free(). * - * @return SSH_OK on success, SSH_ERROR on errror. + * @return SSH_OK on success, SSH_ERROR on error. * * @see ssh_key_free() */ @@ -1100,15 +1177,15 @@ int ssh_get_publickey(ssh_session session, ssh_key *key) * * @param[in] type The type of the hash you want. * - * @param[in] hash A pointer to store the allocated buffer. It can be + * @param[out] hash A pointer to store the allocated buffer. It can be * freed using ssh_clean_pubkey_hash(). * * @param[in] hlen The length of the hash. * - * @return 0 on success, -1 if an error occured. + * @return 0 on success, -1 if an error occurred. * * @warning It is very important that you verify at some moment that the hash - * matches a known server. If you don't do it, cryptography wont help + * matches a known server. If you don't do it, cryptography won't help * you at making things secure. * OpenSSH uses SHA256 to print public key digests. * @@ -1123,7 +1200,7 @@ int ssh_get_publickey_hash(const ssh_key key, size_t *hlen) { ssh_string blob; - unsigned char *h; + unsigned char *h = NULL; int rc; rc = ssh_pki_export_pubkey_blob(key, &blob); @@ -1149,8 +1226,17 @@ int ssh_get_publickey_hash(const ssh_key key, goto out; } - sha1_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); - sha1_final(h, ctx); + rc = sha1_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); + if (rc != SSH_OK) { + free(h); + sha1_ctx_free(ctx); + goto out; + } + rc = sha1_final(h, ctx); + if (rc != SSH_OK) { + free(h); + goto out; + } *hlen = SHA_DIGEST_LEN; } @@ -1172,8 +1258,17 @@ int ssh_get_publickey_hash(const ssh_key key, goto out; } - sha256_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); - sha256_final(h, ctx); + rc = sha256_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); + if (rc != SSH_OK) { + free(h); + sha256_ctx_free(ctx); + goto out; + } + rc = sha256_final(h, ctx); + if (rc != SSH_OK) { + free(h); + goto out; + } *hlen = SHA256_DIGEST_LEN; } @@ -1184,8 +1279,8 @@ int ssh_get_publickey_hash(const ssh_key key, /* In FIPS mode, we cannot use MD5 */ if (ssh_fips_mode()) { - SSH_LOG(SSH_LOG_WARN, "In FIPS mode MD5 is not allowed." - "Try using SSH_PUBLICKEY_HASH_SHA256"); + SSH_LOG(SSH_LOG_TRACE, "In FIPS mode MD5 is not allowed." + "Try using SSH_PUBLICKEY_HASH_SHA256"); rc = SSH_ERROR; goto out; } @@ -1203,8 +1298,17 @@ int ssh_get_publickey_hash(const ssh_key key, goto out; } - md5_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); - md5_final(h, ctx); + rc = md5_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); + if (rc != SSH_OK) { + free(h); + md5_ctx_free(ctx); + goto out; + } + rc = md5_final(h, ctx); + if (rc != SSH_OK) { + free(h); + goto out; + } *hlen = MD5_DIGEST_LEN; } @@ -32,6 +32,9 @@ #include <fcntl.h> #include <stdio.h> #include <stdint.h> +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif /* HAVE_SYS_TIME_H */ #include <sys/types.h> #include <sys/stat.h> #include <limits.h> @@ -53,22 +56,12 @@ #ifdef WITH_SFTP -/* Buffer size maximum is 256M */ -#define SFTP_PACKET_SIZE_MAX 0x10000000 -#define SFTP_BUFFER_SIZE_MAX 16384 - struct sftp_ext_struct { uint32_t count; char **name; char **data; }; -/* functions */ -static int sftp_enqueue(sftp_session session, sftp_message msg); -static void sftp_message_free(sftp_message msg); -static void sftp_set_error(sftp_session sftp, int errnum); -static void status_msg_free(sftp_status_message status); - static sftp_ext sftp_ext_new(void) { sftp_ext ext; @@ -173,131 +166,129 @@ error: return NULL; } -sftp_session sftp_new_channel(ssh_session session, ssh_channel channel){ - sftp_session sftp; +sftp_session +sftp_new_channel(ssh_session session, ssh_channel channel) +{ + sftp_session sftp = NULL; - if (session == NULL) { - return NULL; - } + if (session == NULL) { + return NULL; + } - sftp = calloc(1, sizeof(struct sftp_session_struct)); - if (sftp == NULL) { - ssh_set_error_oom(session); + sftp = calloc(1, sizeof(struct sftp_session_struct)); + if (sftp == NULL) { + ssh_set_error_oom(session); + return NULL; + } - return NULL; - } + sftp->ext = sftp_ext_new(); + if (sftp->ext == NULL) { + ssh_set_error_oom(session); + goto error; + } - sftp->ext = sftp_ext_new(); - if (sftp->ext == NULL) { - ssh_set_error_oom(session); - SAFE_FREE(sftp); + sftp->read_packet = calloc(1, sizeof(struct sftp_packet_struct)); + if (sftp->read_packet == NULL) { + ssh_set_error_oom(session); + goto error; + } - return NULL; - } + sftp->read_packet->payload = ssh_buffer_new(); + if (sftp->read_packet->payload == NULL) { + ssh_set_error_oom(session); + goto error; + } - sftp->session = session; - sftp->channel = channel; + sftp->session = session; + sftp->channel = channel; - return sftp; + return sftp; + +error: + if (sftp->ext != NULL) { + sftp_ext_free(sftp->ext); + } + if (sftp->read_packet != NULL) { + if (sftp->read_packet->payload != NULL) { + SSH_BUFFER_FREE(sftp->read_packet->payload); + } + SAFE_FREE(sftp->read_packet); + } + SAFE_FREE(sftp); + return NULL; } #ifdef WITH_SERVER -sftp_session sftp_server_new(ssh_session session, ssh_channel chan){ - sftp_session sftp = NULL; +sftp_session +sftp_server_new(ssh_session session, ssh_channel chan) +{ + sftp_session sftp = NULL; - sftp = calloc(1, sizeof(struct sftp_session_struct)); - if (sftp == NULL) { - ssh_set_error_oom(session); - return NULL; - } + sftp = calloc(1, sizeof(struct sftp_session_struct)); + if (sftp == NULL) { + ssh_set_error_oom(session); + return NULL; + } - sftp->read_packet = calloc(1, sizeof(struct sftp_packet_struct)); - if (sftp->read_packet == NULL) { - goto error; - } + sftp->read_packet = calloc(1, sizeof(struct sftp_packet_struct)); + if (sftp->read_packet == NULL) { + goto error; + } - sftp->read_packet->payload = ssh_buffer_new(); - if (sftp->read_packet->payload == NULL) { - goto error; - } + sftp->read_packet->payload = ssh_buffer_new(); + if (sftp->read_packet->payload == NULL) { + goto error; + } - sftp->session = session; - sftp->channel = chan; + sftp->session = session; + sftp->channel = chan; - return sftp; + return sftp; error: - ssh_set_error_oom(session); - if (sftp->read_packet != NULL) { - if (sftp->read_packet->payload != NULL) { - SSH_BUFFER_FREE(sftp->read_packet->payload); + ssh_set_error_oom(session); + if (sftp->read_packet != NULL) { + if (sftp->read_packet->payload != NULL) { + SSH_BUFFER_FREE(sftp->read_packet->payload); + } + SAFE_FREE(sftp->read_packet); } - SAFE_FREE(sftp->read_packet); - } - SAFE_FREE(sftp); - return NULL; + SAFE_FREE(sftp); + return NULL; } -int sftp_server_init(sftp_session sftp){ - ssh_session session = sftp->session; - sftp_packet packet = NULL; - ssh_buffer reply = NULL; - uint32_t version; - int rc; - - packet = sftp_packet_read(sftp); - if (packet == NULL) { - return -1; - } - - if (packet->type != SSH_FXP_INIT) { - ssh_set_error(session, SSH_FATAL, - "Packet read of type %d instead of SSH_FXP_INIT", - packet->type); - - return -1; - } - - SSH_LOG(SSH_LOG_PACKET, "Received SSH_FXP_INIT"); - - ssh_buffer_get_u32(packet->payload, &version); - version = ntohl(version); - SSH_LOG(SSH_LOG_PACKET, "Client version: %d", version); - sftp->client_version = (int)version; - - reply = ssh_buffer_new(); - if (reply == NULL) { - ssh_set_error_oom(session); - return -1; - } +/* @deprecated in favor of sftp_server_new() and callbacks based sftp server */ +int sftp_server_init(sftp_session sftp) +{ + ssh_session session = sftp->session; + sftp_client_message msg = NULL; + int rc; - rc = ssh_buffer_pack(reply, "dssss", - LIBSFTP_VERSION, - "posix-rename@openssh.com", - "1", - "hardlink@openssh.com", - "1"); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - SSH_BUFFER_FREE(reply); - return -1; - } + /* handles setting the sftp->client_version */ + msg = sftp_get_client_message(sftp); + if (msg == NULL) { + return -1; + } - if (sftp_packet_write(sftp, SSH_FXP_VERSION, reply) < 0) { - SSH_BUFFER_FREE(reply); - return -1; - } - SSH_BUFFER_FREE(reply); + if (msg->type != SSH_FXP_INIT) { + ssh_set_error(session, + SSH_FATAL, + "Packet read of type %d instead of SSH_FXP_INIT", + msg->type); + return -1; + } - SSH_LOG(SSH_LOG_PROTOCOL, "Server version sent"); + SSH_LOG(SSH_LOG_PACKET, "Received SSH_FXP_INIT"); - if (version > LIBSFTP_VERSION) { - sftp->version = LIBSFTP_VERSION; - } else { - sftp->version = (int)version; - } + rc = sftp_reply_version(msg); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to process the SSH_FXP_INIT message"); + return -1; + } - return 0; + return 0; } void sftp_server_free(sftp_session sftp) @@ -325,6 +316,7 @@ void sftp_server_free(sftp_session sftp) SAFE_FREE(sftp); } + #endif /* WITH_SERVER */ void sftp_free(sftp_session sftp) @@ -355,176 +347,62 @@ void sftp_free(sftp_session sftp) SAFE_FREE(sftp->read_packet); sftp_ext_free(sftp->ext); + sftp_limits_free(sftp->limits); SAFE_FREE(sftp); } -ssize_t sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload) -{ - uint8_t header[5] = {0}; - uint32_t payload_size; - ssize_t size; - int rc; - - /* Add size of type */ - payload_size = ssh_buffer_get_len(payload) + sizeof(uint8_t); - PUSH_BE_U32(header, 0, payload_size); - PUSH_BE_U8(header, 4, type); - - rc = ssh_buffer_prepend_data(payload, header, sizeof(header)); - if (rc < 0) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - - size = ssh_channel_write(sftp->channel, - ssh_buffer_get(payload), - ssh_buffer_get_len(payload)); - if (size < 0) { - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - - if ((uint32_t)size != ssh_buffer_get_len(payload)) { - SSH_LOG(SSH_LOG_PACKET, - "Had to write %d bytes, wrote only %zd", - ssh_buffer_get_len(payload), - size); - } - - return size; -} - -sftp_packet sftp_packet_read(sftp_session sftp) +/* @internal + * Process the incoming data and copy them from the SSH packet buffer to the + * SFTP packet buffer. + * @returns number of decoded bytes. + */ +int +sftp_decode_channel_data_to_packet(sftp_session sftp, void *data, uint32_t len) { - uint8_t buffer[SFTP_BUFFER_SIZE_MAX]; sftp_packet packet = sftp->read_packet; - uint32_t size; int nread; - bool is_eof; - int rc; + int payload_len; + unsigned int data_offset; + int to_read, rc; - packet->sftp = sftp; - - /* - * If the packet has a payload, then just reinit the buffer, otherwise - * allocate a new one. - */ - if (packet->payload != NULL) { - rc = ssh_buffer_reinit(packet->payload); - if (rc != 0) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } - } else { - packet->payload = ssh_buffer_new(); - if (packet->payload == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } + if (packet->sftp == NULL) { + packet->sftp = sftp; } - nread = 0; - do { - int s; - - // read from channel until 4 bytes have been read or an error occurs - s = ssh_channel_read(sftp->channel, buffer + nread, 4 - nread, 0); - if (s < 0) { - goto error; - } else if (s == 0) { - is_eof = ssh_channel_is_eof(sftp->channel); - if (is_eof) { - ssh_set_error(sftp->session, - SSH_FATAL, - "Received EOF while reading sftp packet size"); - sftp_set_error(sftp, SSH_FX_EOF); - goto error; - } - } else { - nread += s; - } - } while (nread < 4); - - size = PULL_BE_U32(buffer, 0); - if (size == 0 || size > SFTP_PACKET_SIZE_MAX) { - ssh_set_error(sftp->session, SSH_FATAL, "Invalid sftp packet size!"); - sftp_set_error(sftp, SSH_FX_FAILURE); - goto error; + data_offset = sizeof(uint32_t) + sizeof(uint8_t); + /* not enough bytes to read */ + if (len < data_offset) { + return SSH_ERROR; } - do { - nread = ssh_channel_read(sftp->channel, buffer, 1, 0); - if (nread < 0) { - goto error; - } else if (nread == 0) { - is_eof = ssh_channel_is_eof(sftp->channel); - if (is_eof) { - ssh_set_error(sftp->session, - SSH_FATAL, - "Received EOF while reading sftp packet type"); - sftp_set_error(sftp, SSH_FX_EOF); - goto error; - } - } - } while (nread < 1); - - packet->type = buffer[0]; + payload_len = PULL_BE_U32(data, 0); + packet->type = PULL_BE_U8(data, 4); - /* Remove the packet type size */ - size -= sizeof(uint8_t); - - nread = ssh_buffer_allocate_size(packet->payload, size); - if (nread < 0) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - goto error; + /* We should check the legality of payload length */ + if (payload_len + sizeof(uint32_t) > len || payload_len < 0) { + return SSH_ERROR; } - while (size > 0 && size < SFTP_PACKET_SIZE_MAX) { - nread = ssh_channel_read(sftp->channel, - buffer, - sizeof(buffer) > size ? size : sizeof(buffer), - 0); - if (nread < 0) { - /* TODO: check if there are cases where an error needs to be set here */ - goto error; - } - - if (nread > 0) { - rc = ssh_buffer_add_data(packet->payload, buffer, nread); - if (rc != 0) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - goto error; - } - } else { /* nread == 0 */ - /* Retry the reading unless the remote was closed */ - is_eof = ssh_channel_is_eof(sftp->channel); - if (is_eof) { - ssh_set_error(sftp->session, - SSH_REQUEST_DENIED, - "Received EOF while reading sftp packet"); - sftp_set_error(sftp, SSH_FX_EOF); - goto error; - } - } - size -= nread; + to_read = payload_len - sizeof(uint8_t); + rc = ssh_buffer_add_data(packet->payload, + (void*)((uint8_t *)data + data_offset), + to_read); + if (rc != 0) { + return SSH_ERROR; } + nread = ssh_buffer_get_len(packet->payload); - return packet; -error: - ssh_buffer_reinit(packet->payload); - return NULL; -} + /* We should check if we copied the whole data */ + if (nread != to_read) { + return SSH_ERROR; + } -static void sftp_set_error(sftp_session sftp, int errnum) { - if (sftp != NULL) { - sftp->errnum = errnum; - } + /* + * We should return how many bytes we decoded, including packet length header + * and the payload length. + */ + return payload_len + sizeof(uint32_t); } /* Get the last sftp error */ @@ -536,199 +414,155 @@ int sftp_get_error(sftp_session sftp) { return sftp->errnum; } -static void sftp_message_free(sftp_message msg) -{ - if (msg == NULL) { - return; - } - - SSH_BUFFER_FREE(msg->payload); - SAFE_FREE(msg); -} +static sftp_limits_t sftp_limits_use_extension(sftp_session sftp); +static sftp_limits_t sftp_limits_use_default(sftp_session sftp); -static sftp_message sftp_get_message(sftp_packet packet) +/* Initialize the sftp session with the server. */ +int sftp_init(sftp_session sftp) { - sftp_session sftp = packet->sftp; - sftp_message msg = NULL; + sftp_packet packet = NULL; + ssh_buffer buffer = NULL; + char *ext_name = NULL; + char *ext_data = NULL; + uint32_t version; int rc; - switch(packet->type) { - case SSH_FXP_STATUS: - case SSH_FXP_HANDLE: - case SSH_FXP_DATA: - case SSH_FXP_ATTRS: - case SSH_FXP_NAME: - case SSH_FXP_EXTENDED_REPLY: - break; - default: - ssh_set_error(packet->sftp->session, - SSH_FATAL, - "Unknown packet type %d", - packet->type); - sftp_set_error(packet->sftp, SSH_FX_FAILURE); - return NULL; - } - - msg = calloc(1, sizeof(struct sftp_message_struct)); - if (msg == NULL) { + buffer = ssh_buffer_new(); + if (buffer == NULL) { ssh_set_error_oom(sftp->session); - sftp_set_error(packet->sftp, SSH_FX_FAILURE); - return NULL; + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; } - msg->sftp = packet->sftp; - msg->packet_type = packet->type; - - /* Move the payload from the packet to the message */ - msg->payload = packet->payload; - packet->payload = NULL; - - rc = ssh_buffer_unpack(msg->payload, "d", &msg->id); + rc = ssh_buffer_pack(buffer, "d", LIBSFTP_VERSION); if (rc != SSH_OK) { - ssh_set_error(packet->sftp->session, SSH_FATAL, - "Invalid packet %d: no ID", packet->type); - sftp_message_free(msg); - sftp_set_error(packet->sftp, SSH_FX_FAILURE); - return NULL; + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; } - SSH_LOG(SSH_LOG_PACKET, - "Packet with id %d type %d", - msg->id, - msg->packet_type); - - return msg; -} + rc = sftp_packet_write(sftp, SSH_FXP_INIT, buffer); + if (rc == SSH_ERROR) { + SSH_BUFFER_FREE(buffer); + return -1; + } -static int sftp_read_and_dispatch(sftp_session sftp) -{ - sftp_packet packet = NULL; - sftp_message msg = NULL; + SSH_BUFFER_FREE(buffer); packet = sftp_packet_read(sftp); if (packet == NULL) { - /* something nasty happened reading the packet */ return -1; } - msg = sftp_get_message(packet); - if (msg == NULL) { + if (packet->type != SSH_FXP_VERSION) { + ssh_set_error(sftp->session, SSH_FATAL, + "Received a %d messages instead of SSH_FXP_VERSION", + packet->type); return -1; } - if (sftp_enqueue(sftp, msg) < 0) { - sftp_message_free(msg); + /* TODO: are we sure there are 4 bytes ready? */ + rc = ssh_buffer_unpack(packet->payload, "d", &version); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Unable to unpack SSH_FXP_VERSION packet"); + sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } - return 0; -} + SSH_LOG(SSH_LOG_DEBUG, + "SFTP server version %" PRIu32, + version); + rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); + while (rc == SSH_OK) { + uint32_t count = sftp->ext->count; + char **tmp; -void sftp_packet_free(sftp_packet packet) -{ - if (packet == NULL) { - return; - } + rc = ssh_buffer_unpack(packet->payload, "s", &ext_data); + if (rc == SSH_ERROR) { + break; + } - SSH_BUFFER_FREE(packet->payload); - free(packet); -} + SSH_LOG(SSH_LOG_DEBUG, + "SFTP server extension: %s, version: %s", + ext_name, ext_data); -/* Initialize the sftp session with the server. */ -int sftp_init(sftp_session sftp) { - sftp_packet packet = NULL; - ssh_buffer buffer = NULL; - char *ext_name = NULL; - char *ext_data = NULL; - uint32_t version; - int rc; + count++; + tmp = realloc(sftp->ext->name, count * sizeof(char *)); + if (tmp == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(ext_name); + SAFE_FREE(ext_data); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } - buffer = ssh_buffer_new(); - if (buffer == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } + tmp[count - 1] = ext_name; + sftp->ext->name = tmp; - rc = ssh_buffer_pack(buffer, "d", LIBSFTP_VERSION); - if (rc != SSH_OK) { - ssh_set_error_oom(sftp->session); - SSH_BUFFER_FREE(buffer); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - if (sftp_packet_write(sftp, SSH_FXP_INIT, buffer) < 0) { - SSH_BUFFER_FREE(buffer); - return -1; - } - SSH_BUFFER_FREE(buffer); + tmp = realloc(sftp->ext->data, count * sizeof(char *)); + if (tmp == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(ext_name); + SAFE_FREE(ext_data); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } - packet = sftp_packet_read(sftp); - if (packet == NULL) { - return -1; - } + tmp[count - 1] = ext_data; + sftp->ext->data = tmp; - if (packet->type != SSH_FXP_VERSION) { - ssh_set_error(sftp->session, SSH_FATAL, - "Received a %d messages instead of SSH_FXP_VERSION", packet->type); - return -1; - } + sftp->ext->count = count; - /* TODO: are we sure there are 4 bytes ready? */ - rc = ssh_buffer_unpack(packet->payload, "d", &version); - if (rc != SSH_OK){ - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - SSH_LOG(SSH_LOG_PROTOCOL, - "SFTP server version %d", - version); - rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); - while (rc == SSH_OK) { - uint32_t count = sftp->ext->count; - char **tmp; - - rc = ssh_buffer_unpack(packet->payload, "s", &ext_data); - if (rc == SSH_ERROR) { - break; + rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); } - SSH_LOG(SSH_LOG_PROTOCOL, - "SFTP server extension: %s, version: %s", - ext_name, ext_data); + sftp->version = sftp->server_version = (int)version; - count++; - tmp = realloc(sftp->ext->name, count * sizeof(char *)); - if (tmp == NULL) { - ssh_set_error_oom(sftp->session); - SAFE_FREE(ext_name); - SAFE_FREE(ext_data); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - tmp[count - 1] = ext_name; - sftp->ext->name = tmp; + /* Set the limits */ + rc = sftp_extension_supported(sftp, "limits@openssh.com", "1"); + if (rc == 1) { + /* Get the ssh and sftp errors */ + const char *static_ssh_err_msg = ssh_get_error(sftp->session); + int ssh_err_code = ssh_get_error_code(sftp->session); + int sftp_err_code = sftp_get_error(sftp); + char *ssh_err_msg = strdup(static_ssh_err_msg); + if (ssh_err_msg == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } - tmp = realloc(sftp->ext->data, count * sizeof(char *)); - if (tmp == NULL) { - ssh_set_error_oom(sftp->session); - SAFE_FREE(ext_name); - SAFE_FREE(ext_data); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; + sftp->limits = sftp_limits_use_extension(sftp); + if (sftp->limits == NULL) { + /* fallback and use the default limits on failure */ + SSH_LOG(SSH_LOG_TRACE, + "Failed to get the limits from a server claiming to " + "support the limits@openssh.com extension, falling back " + "and using the default limits"); + + /* Restore the sftp and ssh errors to their previous state */ + ssh_set_error(sftp->session, ssh_err_code, "%s", ssh_err_msg); + sftp_set_error(sftp, sftp_err_code); + SAFE_FREE(ssh_err_msg); + + sftp->limits = sftp_limits_use_default(sftp); + if (sftp->limits == NULL) { + return -1; + } + } else { + SAFE_FREE(ssh_err_msg); + } + } else { + sftp->limits = sftp_limits_use_default(sftp); + if (sftp->limits == NULL) { + return -1; + } } - tmp[count - 1] = ext_data; - sftp->ext->data = tmp; - - sftp->ext->count = count; - - rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); - } - - sftp->version = sftp->server_version = (int)version; - - return 0; + return 0; } unsigned int sftp_extensions_get_count(sftp_session sftp) { @@ -794,166 +628,6 @@ int sftp_extension_supported(sftp_session sftp, const char *name, return 0; } -static sftp_request_queue request_queue_new(sftp_message msg) { - sftp_request_queue queue = NULL; - - queue = calloc(1, sizeof(struct sftp_request_queue_struct)); - if (queue == NULL) { - ssh_set_error_oom(msg->sftp->session); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - - queue->message = msg; - - return queue; -} - -static void request_queue_free(sftp_request_queue queue) { - if (queue == NULL) { - return; - } - - ZERO_STRUCTP(queue); - SAFE_FREE(queue); -} - -static int sftp_enqueue(sftp_session sftp, sftp_message msg) { - sftp_request_queue queue = NULL; - sftp_request_queue ptr; - - queue = request_queue_new(msg); - if (queue == NULL) { - return -1; - } - - SSH_LOG(SSH_LOG_PACKET, - "Queued msg id %d type %d", - msg->id, msg->packet_type); - - if(sftp->queue == NULL) { - sftp->queue = queue; - } else { - ptr = sftp->queue; - while(ptr->next) { - ptr=ptr->next; /* find end of linked list */ - } - ptr->next = queue; /* add it on bottom */ - } - - return 0; -} - -/* - * Pulls of a message from the queue based on the ID. - * Returns NULL if no message has been found. - */ -static sftp_message sftp_dequeue(sftp_session sftp, uint32_t id){ - sftp_request_queue prev = NULL; - sftp_request_queue queue; - sftp_message msg; - - if(sftp->queue == NULL) { - return NULL; - } - - queue = sftp->queue; - while (queue) { - if(queue->message->id == id) { - /* remove from queue */ - if (prev == NULL) { - sftp->queue = queue->next; - } else { - prev->next = queue->next; - } - msg = queue->message; - request_queue_free(queue); - SSH_LOG(SSH_LOG_PACKET, - "Dequeued msg id %d type %d", - msg->id, - msg->packet_type); - return msg; - } - prev = queue; - queue = queue->next; - } - - return NULL; -} - -/* - * Assigns a new SFTP ID for new requests and assures there is no collision - * between them. - * Returns a new ID ready to use in a request - */ -static inline uint32_t sftp_get_new_id(sftp_session session) { - return ++session->id_counter; -} - -static sftp_status_message parse_status_msg(sftp_message msg){ - sftp_status_message status; - int rc; - - if (msg->packet_type != SSH_FXP_STATUS) { - ssh_set_error(msg->sftp->session, SSH_FATAL, - "Not a ssh_fxp_status message passed in!"); - sftp_set_error(msg->sftp, SSH_FX_BAD_MESSAGE); - return NULL; - } - - status = calloc(1, sizeof(struct sftp_status_message_struct)); - if (status == NULL) { - ssh_set_error_oom(msg->sftp->session); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - - status->id = msg->id; - rc = ssh_buffer_unpack(msg->payload, "d", - &status->status); - if (rc != SSH_OK){ - SAFE_FREE(status); - ssh_set_error(msg->sftp->session, SSH_FATAL, - "Invalid SSH_FXP_STATUS message"); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - rc = ssh_buffer_unpack(msg->payload, "ss", - &status->errormsg, - &status->langmsg); - - if(rc != SSH_OK && msg->sftp->version >=3){ - /* These are mandatory from version 3 */ - SAFE_FREE(status); - ssh_set_error(msg->sftp->session, SSH_FATAL, - "Invalid SSH_FXP_STATUS message"); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - if (status->errormsg == NULL) - status->errormsg = strdup("No error message in packet"); - if (status->langmsg == NULL) - status->langmsg = strdup(""); - if (status->errormsg == NULL || status->langmsg == NULL) { - ssh_set_error_oom(msg->sftp->session); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - status_msg_free(status); - return NULL; - } - - return status; -} - -static void status_msg_free(sftp_status_message status){ - if (status == NULL) { - return; - } - - SAFE_FREE(status->errormsg); - SAFE_FREE(status->langmsg); - SAFE_FREE(status); -} - static sftp_file parse_handle_msg(sftp_message msg){ sftp_file file; @@ -993,7 +667,7 @@ sftp_dir sftp_opendir(sftp_session sftp, const char *path) sftp_file file = NULL; sftp_dir dir = NULL; sftp_status_message status; - ssh_buffer payload; + ssh_buffer payload = NULL; uint32_t id; int rc; @@ -1044,7 +718,7 @@ sftp_dir sftp_opendir(sftp_session sftp, const char *path) } sftp_set_error(sftp, status->status); ssh_set_error(sftp->session, SSH_REQUEST_DENIED, - "SFTP server: %s", status->errormsg); + "SFTP server: %s", status->errormsg); status_msg_free(status); return NULL; case SSH_FXP_HANDLE: @@ -1071,452 +745,14 @@ sftp_dir sftp_opendir(sftp_session sftp, const char *path) return dir; default: ssh_set_error(sftp->session, SSH_FATAL, - "Received message %d during opendir!", msg->packet_type); + "Received message %d during opendir!", + msg->packet_type); sftp_message_free(msg); } return NULL; } -/* - * Parse the attributes from a payload from some messages. It is coded on - * baselines from the protocol version 4. - * This code is more or less dead but maybe we need it in future. - */ -static sftp_attributes sftp_parse_attr_4(sftp_session sftp, ssh_buffer buf, - int expectnames) { - sftp_attributes attr; - ssh_string owner = NULL; - ssh_string group = NULL; - uint32_t flags = 0; - int ok = 0; - - /* unused member variable */ - (void) expectnames; - - attr = calloc(1, sizeof(struct sftp_attributes_struct)); - if (attr == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } - - /* This isn't really a loop, but it is like a try..catch.. */ - do { - if (ssh_buffer_get_u32(buf, &flags) != 4) { - break; - } - - flags = ntohl(flags); - attr->flags = flags; - - if (flags & SSH_FILEXFER_ATTR_SIZE) { - if (ssh_buffer_get_u64(buf, &attr->size) != 8) { - break; - } - attr->size = ntohll(attr->size); - } - - if (flags & SSH_FILEXFER_ATTR_OWNERGROUP) { - owner = ssh_buffer_get_ssh_string(buf); - if (owner == NULL) { - break; - } - attr->owner = ssh_string_to_char(owner); - SSH_STRING_FREE(owner); - if (attr->owner == NULL) { - break; - } - - group = ssh_buffer_get_ssh_string(buf); - if (group == NULL) { - break; - } - attr->group = ssh_string_to_char(group); - SSH_STRING_FREE(group); - if (attr->group == NULL) { - break; - } - } - - if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { - if (ssh_buffer_get_u32(buf, &attr->permissions) != 4) { - break; - } - attr->permissions = ntohl(attr->permissions); - - /* FIXME on windows! */ - switch (attr->permissions & SSH_S_IFMT) { - case SSH_S_IFSOCK: - case SSH_S_IFBLK: - case SSH_S_IFCHR: - case SSH_S_IFIFO: - attr->type = SSH_FILEXFER_TYPE_SPECIAL; - break; - case SSH_S_IFLNK: - attr->type = SSH_FILEXFER_TYPE_SYMLINK; - break; - case SSH_S_IFREG: - attr->type = SSH_FILEXFER_TYPE_REGULAR; - break; - case SSH_S_IFDIR: - attr->type = SSH_FILEXFER_TYPE_DIRECTORY; - break; - default: - attr->type = SSH_FILEXFER_TYPE_UNKNOWN; - break; - } - } - - if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) { - if (ssh_buffer_get_u64(buf, &attr->atime64) != 8) { - break; - } - attr->atime64 = ntohll(attr->atime64); - - if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { - if (ssh_buffer_get_u32(buf, &attr->atime_nseconds) != 4) { - break; - } - attr->atime_nseconds = ntohl(attr->atime_nseconds); - } - } - - if (flags & SSH_FILEXFER_ATTR_CREATETIME) { - if (ssh_buffer_get_u64(buf, &attr->createtime) != 8) { - break; - } - attr->createtime = ntohll(attr->createtime); - - if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { - if (ssh_buffer_get_u32(buf, &attr->createtime_nseconds) != 4) { - break; - } - attr->createtime_nseconds = ntohl(attr->createtime_nseconds); - } - } - - if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) { - if (ssh_buffer_get_u64(buf, &attr->mtime64) != 8) { - break; - } - attr->mtime64 = ntohll(attr->mtime64); - - if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { - if (ssh_buffer_get_u32(buf, &attr->mtime_nseconds) != 4) { - break; - } - attr->mtime_nseconds = ntohl(attr->mtime_nseconds); - } - } - - if (flags & SSH_FILEXFER_ATTR_ACL) { - if ((attr->acl = ssh_buffer_get_ssh_string(buf)) == NULL) { - break; - } - } - - if (flags & SSH_FILEXFER_ATTR_EXTENDED) { - if (ssh_buffer_get_u32(buf,&attr->extended_count) != 4) { - break; - } - attr->extended_count = ntohl(attr->extended_count); - - while(attr->extended_count && - (attr->extended_type = ssh_buffer_get_ssh_string(buf)) && - (attr->extended_data = ssh_buffer_get_ssh_string(buf))){ - attr->extended_count--; - } - - if (attr->extended_count) { - break; - } - } - ok = 1; - } while (0); - - if (ok == 0) { - /* break issued somewhere */ - SSH_STRING_FREE(attr->acl); - SSH_STRING_FREE(attr->extended_type); - SSH_STRING_FREE(attr->extended_data); - SAFE_FREE(attr->owner); - SAFE_FREE(attr->group); - SAFE_FREE(attr); - - ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); - - return NULL; - } - - return attr; -} - -enum sftp_longname_field_e { - SFTP_LONGNAME_PERM = 0, - SFTP_LONGNAME_FIXME, - SFTP_LONGNAME_OWNER, - SFTP_LONGNAME_GROUP, - SFTP_LONGNAME_SIZE, - SFTP_LONGNAME_DATE, - SFTP_LONGNAME_TIME, - SFTP_LONGNAME_NAME, -}; - -static char *sftp_parse_longname(const char *longname, - enum sftp_longname_field_e longname_field) { - const char *p, *q; - size_t len, field = 0; - - p = longname; - /* Find the beginning of the field which is specified by sftp_longname_field_e. */ - while(field != longname_field) { - if(isspace(*p)) { - field++; - p++; - while(*p && isspace(*p)) { - p++; - } - } else { - p++; - } - } - - q = p; - while (! isspace(*q)) { - q++; - } - - len = q - p; - - return strndup(p, len); -} - -/* sftp version 0-3 code. It is different from the v4 */ -/* maybe a paste of the draft is better than the code */ -/* - uint32 flags - uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE - uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID - uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID - uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS - uint32 atime present only if flag SSH_FILEXFER_ACMODTIME - uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME - uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED - string extended_type - string extended_data - ... more extended data (extended_type - extended_data pairs), - so that number of pairs equals extended_count */ -static sftp_attributes sftp_parse_attr_3(sftp_session sftp, ssh_buffer buf, - int expectname) { - sftp_attributes attr; - int rc; - - attr = calloc(1, sizeof(struct sftp_attributes_struct)); - if (attr == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } - - if (expectname) { - rc = ssh_buffer_unpack(buf, "ss", - &attr->name, - &attr->longname); - if (rc != SSH_OK){ - goto error; - } - SSH_LOG(SSH_LOG_PROTOCOL, "Name: %s", attr->name); - - /* Set owner and group if we talk to openssh and have the longname */ - if (ssh_get_openssh_version(sftp->session)) { - attr->owner = sftp_parse_longname(attr->longname, SFTP_LONGNAME_OWNER); - if (attr->owner == NULL) { - goto error; - } - - attr->group = sftp_parse_longname(attr->longname, SFTP_LONGNAME_GROUP); - if (attr->group == NULL) { - goto error; - } - } - } - - rc = ssh_buffer_unpack(buf, "d", &attr->flags); - if (rc != SSH_OK){ - goto error; - } - SSH_LOG(SSH_LOG_PROTOCOL, - "Flags: %.8"PRIx32"\n", (uint32_t) attr->flags); - - if (attr->flags & SSH_FILEXFER_ATTR_SIZE) { - rc = ssh_buffer_unpack(buf, "q", &attr->size); - if(rc != SSH_OK) { - goto error; - } - SSH_LOG(SSH_LOG_PROTOCOL, - "Size: %"PRIu64"\n", - (uint64_t) attr->size); - } - - if (attr->flags & SSH_FILEXFER_ATTR_UIDGID) { - rc = ssh_buffer_unpack(buf, "dd", - &attr->uid, - &attr->gid); - if (rc != SSH_OK){ - goto error; - } - } - - if (attr->flags & SSH_FILEXFER_ATTR_PERMISSIONS) { - rc = ssh_buffer_unpack(buf, "d", &attr->permissions); - if (rc != SSH_OK){ - goto error; - } - - switch (attr->permissions & SSH_S_IFMT) { - case SSH_S_IFSOCK: - case SSH_S_IFBLK: - case SSH_S_IFCHR: - case SSH_S_IFIFO: - attr->type = SSH_FILEXFER_TYPE_SPECIAL; - break; - case SSH_S_IFLNK: - attr->type = SSH_FILEXFER_TYPE_SYMLINK; - break; - case SSH_S_IFREG: - attr->type = SSH_FILEXFER_TYPE_REGULAR; - break; - case SSH_S_IFDIR: - attr->type = SSH_FILEXFER_TYPE_DIRECTORY; - break; - default: - attr->type = SSH_FILEXFER_TYPE_UNKNOWN; - break; - } - } - - if (attr->flags & SSH_FILEXFER_ATTR_ACMODTIME) { - rc = ssh_buffer_unpack(buf, "dd", - &attr->atime, - &attr->mtime); - if (rc != SSH_OK){ - goto error; - } - } - - if (attr->flags & SSH_FILEXFER_ATTR_EXTENDED) { - rc = ssh_buffer_unpack(buf, "d", &attr->extended_count); - if (rc != SSH_OK){ - goto error; - } - - if (attr->extended_count > 0){ - rc = ssh_buffer_unpack(buf, "ss", - &attr->extended_type, - &attr->extended_data); - if (rc != SSH_OK){ - goto error; - } - attr->extended_count--; - } - /* just ignore the remaining extensions */ - - while (attr->extended_count > 0){ - ssh_string tmp1,tmp2; - rc = ssh_buffer_unpack(buf, "SS", &tmp1, &tmp2); - if (rc != SSH_OK){ - goto error; - } - SAFE_FREE(tmp1); - SAFE_FREE(tmp2); - attr->extended_count--; - } - } - - return attr; - - error: - SSH_STRING_FREE(attr->extended_type); - SSH_STRING_FREE(attr->extended_data); - SAFE_FREE(attr->name); - SAFE_FREE(attr->longname); - SAFE_FREE(attr->owner); - SAFE_FREE(attr->group); - SAFE_FREE(attr); - ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); - sftp_set_error(sftp, SSH_FX_FAILURE); - - return NULL; -} - -int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr) -{ - uint32_t flags = (attr ? attr->flags : 0); - int rc; - - flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID | - SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); - - rc = ssh_buffer_pack(buffer, "d", flags); - if (rc != SSH_OK) { - return -1; - } - - if (attr != NULL) { - if (flags & SSH_FILEXFER_ATTR_SIZE) { - rc = ssh_buffer_pack(buffer, "q", attr->size); - if (rc != SSH_OK) { - return -1; - } - } - - if (flags & SSH_FILEXFER_ATTR_UIDGID) { - rc = ssh_buffer_pack(buffer, "dd", attr->uid, attr->gid); - if (rc != SSH_OK) { - return -1; - } - } - - if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { - rc = ssh_buffer_pack(buffer, "d", attr->permissions); - if (rc != SSH_OK) { - return -1; - } - } - - if (flags & SSH_FILEXFER_ATTR_ACMODTIME) { - rc = ssh_buffer_pack(buffer, "dd", attr->atime, attr->mtime); - if (rc != SSH_OK) { - return -1; - } - } - } - return 0; -} - - -sftp_attributes sftp_parse_attr(sftp_session session, - ssh_buffer buf, - int expectname) -{ - switch(session->version) { - case 4: - return sftp_parse_attr_4(session, buf, expectname); - case 3: - case 2: - case 1: - case 0: - return sftp_parse_attr_3(session, buf, expectname); - default: - ssh_set_error(session->session, SSH_FATAL, - "Version %d unsupported by client", session->server_version); - return NULL; - } - - return NULL; -} - /* Get the version of the SFTP protocol supported by the server */ int sftp_server_version(sftp_session sftp) { return sftp->server_version; @@ -1560,7 +796,7 @@ sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) } SSH_LOG(SSH_LOG_PACKET, - "Sent a ssh_fxp_readdir with id %d", id); + "Sent a ssh_fxp_readdir with id %" PRIu32, id); while (msg == NULL) { if (sftp_read_and_dispatch(sftp) < 0) { @@ -1588,7 +824,7 @@ sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) } ssh_set_error(sftp->session, SSH_FATAL, - "Unknown error status: %d", status->status); + "Unknown error status: %" PRIu32, status->status); status_msg_free(status); return NULL; @@ -1617,7 +853,7 @@ sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) return NULL; } - SSH_LOG(SSH_LOG_PROTOCOL, "Count is %d", dir->count); + SSH_LOG(SSH_LOG_DEBUG, "Count is %" PRIu32, dir->count); attr = sftp_parse_attr(sftp, dir->buffer, 1); if (attr == NULL) { @@ -1734,6 +970,10 @@ static int sftp_handle_close(sftp_session sftp, ssh_string handle) int sftp_close(sftp_file file){ int err = SSH_NO_ERROR; + if (file == NULL) { + return err; + } + SAFE_FREE(file->name); if (file->handle){ err = sftp_handle_close(file->sftp,file->handle); @@ -1771,7 +1011,7 @@ sftp_file sftp_open(sftp_session sftp, sftp_status_message status; struct sftp_attributes_struct attr; sftp_file handle; - ssh_buffer buffer; + ssh_buffer buffer = NULL; sftp_attributes stat_data; uint32_t sftp_flags = 0; uint32_t id; @@ -1803,7 +1043,8 @@ sftp_file sftp_open(sftp_session sftp, if ((flags & O_APPEND) == O_APPEND) { sftp_flags |= SSH_FXF_APPEND; } - SSH_LOG(SSH_LOG_PACKET,"Opening file %s with sftp flags %x",file,sftp_flags); + SSH_LOG(SSH_LOG_PACKET, "Opening file %s with sftp flags %" PRIx32, + file, sftp_flags); id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, @@ -1841,62 +1082,66 @@ sftp_file sftp_open(sftp_session sftp, } switch (msg->packet_type) { - case SSH_FXP_STATUS: - status = parse_status_msg(msg); - sftp_message_free(msg); - if (status == NULL) { - return NULL; - } - sftp_set_error(sftp, status->status); - ssh_set_error(sftp->session, SSH_REQUEST_DENIED, - "SFTP server: %s", status->errormsg); - status_msg_free(status); + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return NULL; + case SSH_FXP_HANDLE: + handle = parse_handle_msg(msg); + if (handle == NULL) { return NULL; - case SSH_FXP_HANDLE: - handle = parse_handle_msg(msg); - if (handle == NULL) { + } + sftp_message_free(msg); + if ((flags & O_APPEND) == O_APPEND) { + stat_data = sftp_stat(sftp, file); + if (stat_data == NULL) { + sftp_close(handle); return NULL; } - sftp_message_free(msg); - if ((flags & O_APPEND) == O_APPEND) { - stat_data = sftp_stat(sftp, file); - if (stat_data == NULL) { - sftp_close(handle); - return NULL; - } - if ((stat_data->flags & SSH_FILEXFER_ATTR_SIZE) != SSH_FILEXFER_ATTR_SIZE) { - ssh_set_error(sftp->session, - SSH_FATAL, - "Cannot open in append mode. Unknown file size."); - sftp_close(handle); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } - - handle->offset = stat_data->size; + if ((stat_data->flags & SSH_FILEXFER_ATTR_SIZE) != SSH_FILEXFER_ATTR_SIZE) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Cannot open in append mode. Unknown file size."); + sftp_attributes_free(stat_data); + sftp_close(handle); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; } - return handle; - default: - ssh_set_error(sftp->session, SSH_FATAL, - "Received message %d during open!", msg->packet_type); - sftp_message_free(msg); - sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + + handle->offset = stat_data->size; + sftp_attributes_free(stat_data); + } + return handle; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during open!", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } return NULL; } -void sftp_file_set_nonblocking(sftp_file handle){ - handle->nonblocking=1; +void sftp_file_set_nonblocking(sftp_file handle) +{ + handle->nonblocking = 1; } -void sftp_file_set_blocking(sftp_file handle){ - handle->nonblocking=0; +void sftp_file_set_blocking(sftp_file handle) +{ + handle->nonblocking = 0; } /* Read from a file using an opened sftp file handle. */ ssize_t sftp_read(sftp_file handle, void *buf, size_t count) { - sftp_session sftp = handle->sftp; + sftp_session sftp; sftp_message msg = NULL; sftp_status_message status; ssh_string datastring; @@ -1905,10 +1150,27 @@ ssize_t sftp_read(sftp_file handle, void *buf, size_t count) { uint32_t id; int rc; + if (handle == NULL) { + return -1; + } + sftp = handle->sftp; + if (handle->eof) { return 0; } + /* + * limit the reads to the maximum specified in Section 3 of + * https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02 + * or to the values provided by the limits@openssh.com extension. + * + * TODO: We should iterate over the blocks rather than writing less than + * requested to provide less surprises to the calling applications. + */ + if (count > sftp->limits->max_read_length) { + count = sftp->limits->max_read_length; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -2104,7 +1366,7 @@ int sftp_async_read(sftp_file file, void *data, uint32_t size, uint32_t id){ if (ssh_string_len(datastring) > size) { ssh_set_error(sftp->session, SSH_FATAL, "Received a too big DATA packet from sftp server: " - "%zu and asked for %u", + "%zu and asked for %" PRIu32, ssh_string_len(datastring), size); SSH_STRING_FREE(datastring); return SSH_ERROR; @@ -2126,7 +1388,7 @@ int sftp_async_read(sftp_file file, void *data, uint32_t size, uint32_t id){ } ssize_t sftp_write(sftp_file file, const void *buf, size_t count) { - sftp_session sftp = file->sftp; + sftp_session sftp; sftp_message msg = NULL; sftp_status_message status; ssh_buffer buffer; @@ -2135,6 +1397,11 @@ ssize_t sftp_write(sftp_file file, const void *buf, size_t count) { size_t packetlen; int rc; + if (file == NULL) { + return -1; + } + sftp = file->sftp; + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -2144,6 +1411,18 @@ ssize_t sftp_write(sftp_file file, const void *buf, size_t count) { id = sftp_get_new_id(file->sftp); + /* + * limit the writes to the maximum specified in Section 3 of + * https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02 + * or to the values provided by the limits@openssh.com extension. + * + * TODO: We should iterate over the blocks rather than writing less than + * requested to provide less surprises to the calling applications. + */ + if (count > sftp->limits->max_write_length) { + count = sftp->limits->max_write_length; + } + rc = ssh_buffer_pack(buffer, "dSqdP", id, @@ -2157,8 +1436,8 @@ ssize_t sftp_write(sftp_file file, const void *buf, size_t count) { sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } - packetlen=ssh_buffer_get_len(buffer); len = sftp_packet_write(file->sftp, SSH_FXP_WRITE, buffer); + packetlen=ssh_buffer_get_len(buffer); SSH_BUFFER_FREE(buffer); if (len < 0) { return -1; @@ -2493,85 +1772,115 @@ int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode) } /* code written by nick */ -int sftp_rename(sftp_session sftp, const char *original, const char *newname) { - sftp_status_message status = NULL; - sftp_message msg = NULL; - ssh_buffer buffer; - uint32_t id; - int rc; +int sftp_rename(sftp_session sftp, const char *original, const char *newname) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + const char *extension_name = "posix-rename@openssh.com"; + int request_type; + int rc; - buffer = ssh_buffer_new(); - if (buffer == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } - id = sftp_get_new_id(sftp); + id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, - "dss", - id, - original, - newname); - if (rc != SSH_OK) { - ssh_set_error_oom(sftp->session); - SSH_BUFFER_FREE(buffer); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } + /* + * posix-rename@openssh.com extension will be used + * if it is supported by sftp + */ + if (sftp_extension_supported(sftp, + extension_name, + "1")) { + rc = ssh_buffer_pack(buffer, + "dsss", + id, + extension_name, + original, + newname); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } - if (sftp->version >= 4){ - /* POSIX rename atomically replaces newpath, we should do the same - * only available on >=v4 */ - ssh_buffer_add_u32(buffer, SSH_FXF_RENAME_OVERWRITE); - } + request_type = SSH_FXP_EXTENDED; + } else { + rc = ssh_buffer_pack(buffer, + "dss", + id, + original, + newname); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } - if (sftp_packet_write(sftp, SSH_FXP_RENAME, buffer) < 0) { - SSH_BUFFER_FREE(buffer); - return -1; - } - SSH_BUFFER_FREE(buffer); + if (sftp->version >= 4) { + /* + * POSIX rename atomically replaces newpath, + * we should do the same only available on >=v4 + */ + ssh_buffer_add_u32(buffer, SSH_FXF_RENAME_OVERWRITE); + } - while (msg == NULL) { - if (sftp_read_and_dispatch(sftp) < 0) { - return -1; + request_type = SSH_FXP_RENAME; } - msg = sftp_dequeue(sftp, id); - } - /* By specification, this command only returns SSH_FXP_STATUS */ - if (msg->packet_type == SSH_FXP_STATUS) { - status = parse_status_msg(msg); - sftp_message_free(msg); - if (status == NULL) { - return -1; + rc = sftp_packet_write(sftp, request_type, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; } - sftp_set_error(sftp, status->status); - switch (status->status) { - case SSH_FX_OK: + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * Status should be SSH_FX_OK if the command was successful, + * if it didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); status_msg_free(status); - return 0; - default: - break; + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to rename", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } - /* - * Status should be SSH_FX_OK if the command was successful, if it didn't, - * then there was an error - */ - ssh_set_error(sftp->session, SSH_REQUEST_DENIED, - "SFTP server: %s", status->errormsg); - status_msg_free(status); - return -1; - } else { - ssh_set_error(sftp->session, SSH_FATAL, - "Received message %d when attempting to rename", - msg->packet_type); - sftp_message_free(msg); - sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); - } - return -1; + return -1; } /* Code written by Nick */ @@ -2699,7 +2008,8 @@ int sftp_utimes(sftp_session sftp, const char *file, return sftp_setstat(sftp, file, &attr); } -int sftp_symlink(sftp_session sftp, const char *target, const char *dest) { +int sftp_symlink(sftp_session sftp, const char *target, const char *dest) +{ sftp_status_message status = NULL; sftp_message msg = NULL; ssh_buffer buffer; @@ -2723,7 +2033,10 @@ int sftp_symlink(sftp_session sftp, const char *target, const char *dest) { id = sftp_get_new_id(sftp); - /* TODO check for version number if they ever fix it. */ + /* The OpenSSH sftp server has order of the arguments reversed, see the + * section "4.1 sftp: Reversal of arguments to SSH_FXP_SYMLINK' in + * https://github.com/openssh/openssh-portable/blob/master/PROTOCOL + * for more information */ if (ssh_get_openssh_version(sftp->session)) { rc = ssh_buffer_pack(buffer, "dss", @@ -2883,6 +2196,94 @@ char *sftp_readlink(sftp_session sftp, const char *path) return NULL; } +int sftp_hardlink(sftp_session sftp, const char *oldpath, const char *newpath) +{ + ssh_buffer buffer = NULL; + uint32_t id; + const char *extension_name = "hardlink@openssh.com"; + sftp_status_message status = NULL; + sftp_message msg = NULL; + int rc; + + if (sftp == NULL) { + return -1; + } + + if (oldpath == NULL || newpath == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "dsss", + id, + extension_name, + oldpath, + newpath); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * Status should be SSH_FX_OK if the command was successful, + * if it didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to create hardlink", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + static sftp_statvfs_t sftp_parse_statvfs(sftp_session sftp, ssh_buffer buf) { sftp_statvfs_t statvfs; int rc; @@ -3183,6 +2584,183 @@ void sftp_statvfs_free(sftp_statvfs_t statvfs) { SAFE_FREE(statvfs); } +static sftp_limits_t sftp_limits_new(void) +{ + return calloc(1, sizeof(struct sftp_limits_struct)); +} + +static sftp_limits_t sftp_parse_limits(sftp_session sftp, ssh_buffer buf) +{ + sftp_limits_t limits = NULL; + int rc; + + limits = sftp_limits_new(); + if (limits == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_unpack(buf, "qqqq", + &limits->max_packet_length, /** maximum number of bytes in a single sftp packet */ + &limits->max_read_length, /** maximum length in a SSH_FXP_READ packet */ + &limits->max_write_length, /** maximum length in a SSH_FXP_WRITE packet */ + &limits->max_open_handles /** maximum number of active handles allowed by server */ + ); + if (rc != SSH_OK) { + SAFE_FREE(limits); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid limits structure"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return limits; +} + +static sftp_limits_t sftp_limits_use_extension(sftp_session sftp) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) + return NULL; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "ds", + id, + "limits@openssh.com"); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + sftp_limits_t limits = sftp_parse_limits(sftp, msg->payload); + sftp_message_free(msg); + if (limits == NULL) { + return NULL; + } + + return limits; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d when attempting to get limits", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +static sftp_limits_t sftp_limits_use_default(sftp_session sftp) +{ + sftp_limits_t limits = NULL; + + if (sftp == NULL) { + return NULL; + } + + limits = sftp_limits_new(); + if (limits == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + limits->max_packet_length = 34000; + limits->max_read_length = 32768; + limits->max_write_length = 32768; + + /* + * For max-open-handles field openssh says : + * If the server doesn't enforce a specific limit, then the field may + * be set to 0. This implies the server relies on the OS to enforce + * limits (e.g. available memory or file handles), and such limits + * might be dynamic. The client SHOULD take care to not try to exceed + * reasonable limits. + */ + limits->max_open_handles = 0; + + return limits; +} + +sftp_limits_t sftp_limits(sftp_session sftp) +{ + sftp_limits_t limits = NULL; + + if (sftp == NULL) { + return NULL; + } + + if (sftp->limits == NULL) { + ssh_set_error(sftp, SSH_FATAL, + "Uninitialized sftp session, " + "sftp_init() was not called or failed"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + limits = sftp_limits_new(); + if (limits == NULL) { + ssh_set_error_oom(sftp); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + memcpy(limits, sftp->limits, sizeof(struct sftp_limits_struct)); + return limits; +} + +void sftp_limits_free(sftp_limits_t limits) +{ + if (limits == NULL) { + return; + } + + SAFE_FREE(limits); +} + /* another code written by Nick */ char *sftp_canonicalize_path(sftp_session sftp, const char *path) { @@ -3428,4 +3006,98 @@ sftp_attributes sftp_fstat(sftp_file file) return NULL; } +char *sftp_expand_path(sftp_session sftp, const char *path) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; + + if (sftp == NULL) { + return NULL; + } + + if (path == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "NULL received as an argument instead of the path to expand"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "dss", + id, + "expand-path@openssh.com", + path); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + while (msg == NULL) { + rc = sftp_read_and_dispatch(sftp); + if (rc < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_NAME) { + uint32_t ignored = 0; + char *cname = NULL; + + rc = ssh_buffer_unpack(msg->payload, + "ds", + &ignored, + &cname); + sftp_message_free(msg); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to parse expanded path"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return cname; + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to expand path", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + #endif /* WITH_SFTP */ diff --git a/src/sftp_aio.c b/src/sftp_aio.c new file mode 100644 index 00000000..c1c54561 --- /dev/null +++ b/src/sftp_aio.c @@ -0,0 +1,500 @@ +/* + * sftp_aio.c - Secure FTP functions for asynchronous i/o + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2008 by Aris Adamantiadis + * Copyright (c) 2008-2018 by Andreas Schneider <asn@cryptomilk.org> + * Copyright (c) 2023 by Eshan Kelkar <eshankelkar@galorithm.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" + +#include "libssh/sftp.h" +#include "libssh/sftp_priv.h" +#include "libssh/buffer.h" +#include "libssh/session.h" + +#ifdef WITH_SFTP + +struct sftp_aio_struct { + sftp_file file; + uint32_t id; + size_t len; +}; + +static sftp_aio sftp_aio_new(void) +{ + sftp_aio aio = NULL; + aio = calloc(1, sizeof(struct sftp_aio_struct)); + return aio; +} + +void sftp_aio_free(sftp_aio aio) +{ + SAFE_FREE(aio); +} + +ssize_t sftp_aio_begin_read(sftp_file file, size_t len, sftp_aio *aio) +{ + sftp_session sftp = NULL; + ssh_buffer buffer = NULL; + sftp_aio aio_handle = NULL; + uint32_t id; + int rc; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + return SSH_ERROR; + } + + sftp = file->sftp; + if (len == 0) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, 0 passed as the number of " + "bytes to read"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + /* Apply a cap on the length a user is allowed to read */ + if (len > sftp->limits->max_read_length) { + len = sftp->limits->max_read_length; + } + + if (aio == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed instead of a pointer to " + "a location to store an sftp aio handle"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "dSqd", + id, + file->handle, + file->offset, + len); + + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle = sftp_aio_new(); + if (aio_handle == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle->file = file; + aio_handle->id = id; + aio_handle->len = len; + + rc = sftp_packet_write(sftp, SSH_FXP_READ, buffer); + SSH_BUFFER_FREE(buffer); + if (rc == SSH_ERROR) { + SFTP_AIO_FREE(aio_handle); + return SSH_ERROR; + } + + /* Assume we read len bytes from the file */ + file->offset += len; + *aio = aio_handle; + return len; +} + +ssize_t sftp_aio_wait_read(sftp_aio *aio, + void *buf, + size_t buf_size) +{ + sftp_file file = NULL; + size_t bytes_requested; + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status = NULL; + uint32_t string_len, host_len; + int rc, err; + + /* + * This function releases the memory of the structure + * that (*aio) points to in all cases except when the + * return value is SSH_AGAIN. + * + * If the return value is SSH_AGAIN, the user should call this + * function again to get the response for the request corresponding + * to the structure that (*aio) points to, hence we don't release the + * structure's memory when SSH_AGAIN is returned. + */ + + if (aio == NULL || *aio == NULL) { + return SSH_ERROR; + } + + file = (*aio)->file; + bytes_requested = (*aio)->len; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + sftp = file->sftp; + if (bytes_requested == 0) { + /* should never happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid sftp aio, len for requested i/o is 0"); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + if (buf == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed " + "instead of a buffer's address"); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + if (buf_size < bytes_requested) { + ssh_set_error(sftp->session, SSH_FATAL, + "Buffer size (%zu, passed by the caller) is " + "smaller than the number of bytes requested " + "to read (%zu, as per the supplied sftp aio)", + buf_size, bytes_requested); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + /* handle an existing request */ + while (msg == NULL) { + if (file->nonblocking) { + if (ssh_channel_poll(sftp->channel, 0) == 0) { + /* we cannot block */ + return SSH_AGAIN; + } + } + + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + msg = sftp_dequeue(sftp, (*aio)->id); + } + + /* + * Release memory for the structure that (*aio) points to + * as all further points of return are for success or + * failure. + */ + SFTP_AIO_FREE(*aio); + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return SSH_ERROR; + } + + sftp_set_error(sftp, status->status); + if (status->status != SSH_FX_EOF) { + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server : %s", status->errormsg); + err = SSH_ERROR; + } else { + file->eof = 1; + /* Update the offset correctly */ + file->offset = file->offset - bytes_requested; + err = SSH_OK; + } + + status_msg_free(status); + return err; + + case SSH_FXP_DATA: + rc = ssh_buffer_get_u32(msg->payload, &string_len); + if (rc == 0) { + /* Insufficient data in the buffer */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received invalid DATA packet from sftp server"); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + sftp_message_free(msg); + return SSH_ERROR; + } + + host_len = ntohl(string_len); + if (host_len > buf_size) { + /* + * This should never happen, as according to the + * SFTP protocol the server reads bytes less than + * or equal to the number of bytes requested to read. + * + * And we have checked before that the buffer size is + * greater than or equal to the number of bytes requested + * to read, hence code of this if block should never + * get executed. + */ + ssh_set_error(sftp->session, SSH_FATAL, + "DATA packet (%u bytes) received from sftp server " + "cannot fit into the supplied buffer (%zu bytes)", + host_len, buf_size); + sftp_set_error(sftp, SSH_FX_FAILURE); + sftp_message_free(msg); + return SSH_ERROR; + } + + string_len = ssh_buffer_get_data(msg->payload, buf, host_len); + if (string_len != host_len) { + /* should never happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received invalid DATA packet from sftp server"); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + sftp_message_free(msg); + return SSH_ERROR; + } + + /* Update the offset with the correct value */ + file->offset = file->offset - (bytes_requested - string_len); + sftp_message_free(msg); + return string_len; + + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during read!", msg->packet_type); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + sftp_message_free(msg); + return SSH_ERROR; + } + + return SSH_ERROR; /* not reached */ +} + +ssize_t sftp_aio_begin_write(sftp_file file, + const void *buf, + size_t len, + sftp_aio *aio) +{ + sftp_session sftp = NULL; + ssh_buffer buffer = NULL; + sftp_aio aio_handle = NULL; + uint32_t id; + int rc; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + return SSH_ERROR; + } + + sftp = file->sftp; + if (buf == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed instead " + "of a buffer's address"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + if (len == 0) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, 0 passed as the number " + "of bytes to write"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + /* Apply a cap on the length a user is allowed to write */ + if (len > sftp->limits->max_write_length) { + len = sftp->limits->max_write_length; + } + + if (aio == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed instead of a pointer to " + "a location to store an sftp aio handle"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + id = sftp_get_new_id(sftp); + rc = ssh_buffer_pack(buffer, + "dSqdP", + id, + file->handle, + file->offset, + len, /* len of datastring */ + len, buf); + + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle = sftp_aio_new(); + if (aio_handle == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle->file = file; + aio_handle->id = id; + aio_handle->len = len; + + rc = sftp_packet_write(sftp, SSH_FXP_WRITE, buffer); + SSH_BUFFER_FREE(buffer); + if (rc == SSH_ERROR) { + SFTP_AIO_FREE(aio_handle); + return SSH_ERROR; + } + + /* Assume we wrote len bytes to the file */ + file->offset += len; + *aio = aio_handle; + return len; +} + +ssize_t sftp_aio_wait_write(sftp_aio *aio) +{ + sftp_file file = NULL; + size_t bytes_requested; + + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status = NULL; + + /* + * This function releases the memory of the structure + * that (*aio) points to in all cases except when the + * return value is SSH_AGAIN. + * + * If the return value is SSH_AGAIN, the user should call this + * function again to get the response for the request corresponding + * to the structure that (*aio) points to, hence we don't release the + * structure's memory when SSH_AGAIN is returned. + */ + + if (aio == NULL || *aio == NULL) { + return SSH_ERROR; + } + + file = (*aio)->file; + bytes_requested = (*aio)->len; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + sftp = file->sftp; + if (bytes_requested == 0) { + /* This should never happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid sftp aio, len for requested i/o is 0"); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + while (msg == NULL) { + if (file->nonblocking) { + if (ssh_channel_poll(sftp->channel, 0) == 0) { + /* we cannot block */ + return SSH_AGAIN; + } + } + + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + msg = sftp_dequeue(sftp, (*aio)->id); + } + + /* + * Release memory for the structure that (*aio) points to + * as all further points of return are for success or + * failure. + */ + SFTP_AIO_FREE(*aio); + + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return SSH_ERROR; + } + + sftp_set_error(sftp, status->status); + if (status->status == SSH_FX_OK) { + status_msg_free(status); + return bytes_requested; + } + + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return SSH_ERROR; + } + + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during write!", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + return SSH_ERROR; +} + +#endif /* WITH_SFTP */ diff --git a/src/sftp_common.c b/src/sftp_common.c new file mode 100644 index 00000000..005fa9da --- /dev/null +++ b/src/sftp_common.c @@ -0,0 +1,913 @@ +/* + * sftp_common.c - Secure FTP functions which are private and are used + * internally by other sftp api functions spread across + * various source files. + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2008 by Aris Adamantiadis + * Copyright (c) 2008-2018 by Andreas Schneider <asn@cryptomilk.org> + * + * 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" + +#include <ctype.h> + +#include "libssh/sftp.h" +#include "libssh/sftp_priv.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/bytearray.h" + +#ifdef WITH_SFTP + +/* Buffer size maximum is 256M */ +#define SFTP_PACKET_SIZE_MAX 0x10000000 + +sftp_packet sftp_packet_read(sftp_session sftp) +{ + uint8_t tmpbuf[4]; + uint8_t *buffer = NULL; + sftp_packet packet = sftp->read_packet; + uint32_t size; + int nread; + bool is_eof; + int rc; + + packet->sftp = sftp; + + /* + * If the packet has a payload, then just reinit the buffer, otherwise + * allocate a new one. + */ + if (packet->payload != NULL) { + rc = ssh_buffer_reinit(packet->payload); + if (rc != 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + } else { + packet->payload = ssh_buffer_new(); + if (packet->payload == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + } + + nread = 0; + do { + int s; + + /* read from channel until 4 bytes have been read or an error occurs */ + s = ssh_channel_read(sftp->channel, tmpbuf + nread, 4 - nread, 0); + if (s < 0) { + goto error; + } else if (s == 0) { + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received EOF while reading sftp packet size"); + sftp_set_error(sftp, SSH_FX_EOF); + goto error; + } + } else { + nread += s; + } + } while (nread < 4); + + size = PULL_BE_U32(tmpbuf, 0); + if (size == 0 || size > SFTP_PACKET_SIZE_MAX) { + ssh_set_error(sftp->session, SSH_FATAL, "Invalid sftp packet size!"); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto error; + } + + do { + nread = ssh_channel_read(sftp->channel, tmpbuf, 1, 0); + if (nread < 0) { + goto error; + } else if (nread == 0) { + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received EOF while reading sftp packet type"); + sftp_set_error(sftp, SSH_FX_EOF); + goto error; + } + } + } while (nread < 1); + + packet->type = tmpbuf[0]; + + /* Remove the packet type size */ + size -= sizeof(uint8_t); + + /* Allocate the receive buffer from payload */ + buffer = ssh_buffer_allocate(packet->payload, size); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto error; + } + while (size > 0 && size < SFTP_PACKET_SIZE_MAX) { + nread = ssh_channel_read(sftp->channel, buffer, size, 0); + if (nread < 0) { + /* TODO: check if there are cases where an error needs to be set here */ + goto error; + } + + if (nread > 0) { + buffer += nread; + size -= nread; + } else { /* nread == 0 */ + /* Retry the reading unless the remote was closed */ + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "Received EOF while reading sftp packet"); + sftp_set_error(sftp, SSH_FX_EOF); + goto error; + } + } + } + + return packet; +error: + ssh_buffer_reinit(packet->payload); + return NULL; +} + +int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload) +{ + uint8_t header[5] = {0}; + uint32_t payload_size; + int size; + int rc; + + /* Add size of type */ + payload_size = ssh_buffer_get_len(payload) + sizeof(uint8_t); + PUSH_BE_U32(header, 0, payload_size); + PUSH_BE_U8(header, 4, type); + + rc = ssh_buffer_prepend_data(payload, header, sizeof(header)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + size = ssh_channel_write(sftp->channel, + ssh_buffer_get(payload), + ssh_buffer_get_len(payload)); + if (size < 0) { + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + if ((uint32_t)size != ssh_buffer_get_len(payload)) { + SSH_LOG(SSH_LOG_PACKET, + "Had to write %" PRIu32 " bytes, wrote only %d", + ssh_buffer_get_len(payload), + size); + } + + return size; +} + +void sftp_packet_free(sftp_packet packet) +{ + if (packet == NULL) { + return; + } + + SSH_BUFFER_FREE(packet->payload); + free(packet); +} + +int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr) +{ + uint32_t flags = (attr ? attr->flags : 0); + int rc; + + flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID | + SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); + + rc = ssh_buffer_pack(buffer, "d", flags); + if (rc != SSH_OK) { + return -1; + } + + if (attr != NULL) { + if (flags & SSH_FILEXFER_ATTR_SIZE) { + rc = ssh_buffer_pack(buffer, "q", attr->size); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_UIDGID) { + rc = ssh_buffer_pack(buffer, "dd", attr->uid, attr->gid); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + rc = ssh_buffer_pack(buffer, "d", attr->permissions); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_ACMODTIME) { + rc = ssh_buffer_pack(buffer, "dd", attr->atime, attr->mtime); + if (rc != SSH_OK) { + return -1; + } + } + } + + return 0; +} + +/* + * Parse the attributes from a payload from some messages. It is coded on + * baselines from the protocol version 4. + * This code is more or less dead but maybe we will need it in the future. + */ +static sftp_attributes sftp_parse_attr_4(sftp_session sftp, + ssh_buffer buf, + int expectnames) +{ + sftp_attributes attr = NULL; + ssh_string owner = NULL; + ssh_string group = NULL; + uint32_t flags = 0; + int ok = 0; + + /* unused member variable */ + (void) expectnames; + + attr = calloc(1, sizeof(struct sftp_attributes_struct)); + if (attr == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + /* This isn't really a loop, but it is like a try..catch.. */ + do { + if (ssh_buffer_get_u32(buf, &flags) != 4) { + break; + } + + flags = ntohl(flags); + attr->flags = flags; + + if (flags & SSH_FILEXFER_ATTR_SIZE) { + if (ssh_buffer_get_u64(buf, &attr->size) != 8) { + break; + } + attr->size = ntohll(attr->size); + } + + if (flags & SSH_FILEXFER_ATTR_OWNERGROUP) { + owner = ssh_buffer_get_ssh_string(buf); + if (owner == NULL) { + break; + } + attr->owner = ssh_string_to_char(owner); + SSH_STRING_FREE(owner); + if (attr->owner == NULL) { + break; + } + + group = ssh_buffer_get_ssh_string(buf); + if (group == NULL) { + break; + } + attr->group = ssh_string_to_char(group); + SSH_STRING_FREE(group); + if (attr->group == NULL) { + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + if (ssh_buffer_get_u32(buf, &attr->permissions) != 4) { + break; + } + attr->permissions = ntohl(attr->permissions); + + /* FIXME on windows! */ + switch (attr->permissions & SSH_S_IFMT) { + case SSH_S_IFSOCK: + case SSH_S_IFBLK: + case SSH_S_IFCHR: + case SSH_S_IFIFO: + attr->type = SSH_FILEXFER_TYPE_SPECIAL; + break; + case SSH_S_IFLNK: + attr->type = SSH_FILEXFER_TYPE_SYMLINK; + break; + case SSH_S_IFREG: + attr->type = SSH_FILEXFER_TYPE_REGULAR; + break; + case SSH_S_IFDIR: + attr->type = SSH_FILEXFER_TYPE_DIRECTORY; + break; + default: + attr->type = SSH_FILEXFER_TYPE_UNKNOWN; + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) { + if (ssh_buffer_get_u64(buf, &attr->atime64) != 8) { + break; + } + attr->atime64 = ntohll(attr->atime64); + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->atime_nseconds) != 4) { + break; + } + attr->atime_nseconds = ntohl(attr->atime_nseconds); + } + } + + if (flags & SSH_FILEXFER_ATTR_CREATETIME) { + if (ssh_buffer_get_u64(buf, &attr->createtime) != 8) { + break; + } + attr->createtime = ntohll(attr->createtime); + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->createtime_nseconds) != 4) { + break; + } + attr->createtime_nseconds = ntohl(attr->createtime_nseconds); + } + } + + if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) { + if (ssh_buffer_get_u64(buf, &attr->mtime64) != 8) { + break; + } + attr->mtime64 = ntohll(attr->mtime64); + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->mtime_nseconds) != 4) { + break; + } + attr->mtime_nseconds = ntohl(attr->mtime_nseconds); + } + } + + if (flags & SSH_FILEXFER_ATTR_ACL) { + if ((attr->acl = ssh_buffer_get_ssh_string(buf)) == NULL) { + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_EXTENDED) { + if (ssh_buffer_get_u32(buf,&attr->extended_count) != 4) { + break; + } + attr->extended_count = ntohl(attr->extended_count); + + while (attr->extended_count && + (attr->extended_type = ssh_buffer_get_ssh_string(buf)) && + (attr->extended_data = ssh_buffer_get_ssh_string(buf))) { + attr->extended_count--; + } + + if (attr->extended_count) { + break; + } + } + ok = 1; + } while (0); + + if (ok == 0) { + /* break issued somewhere */ + SSH_STRING_FREE(attr->acl); + SSH_STRING_FREE(attr->extended_type); + SSH_STRING_FREE(attr->extended_data); + SAFE_FREE(attr->owner); + SAFE_FREE(attr->group); + SAFE_FREE(attr); + + ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); + + return NULL; + } + + return attr; +} + +enum sftp_longname_field_e { + SFTP_LONGNAME_PERM = 0, + SFTP_LONGNAME_FIXME, + SFTP_LONGNAME_OWNER, + SFTP_LONGNAME_GROUP, + SFTP_LONGNAME_SIZE, + SFTP_LONGNAME_DATE, + SFTP_LONGNAME_TIME, + SFTP_LONGNAME_NAME, +}; + +static char * sftp_parse_longname(const char *longname, + enum sftp_longname_field_e longname_field) +{ + const char *p, *q; + size_t len, field = 0; + + p = longname; + /* + * Find the beginning of the field which is specified + * by sftp_longname_field_e. + */ + while (field != longname_field) { + if (isspace(*p)) { + field++; + p++; + while (*p && isspace(*p)) { + p++; + } + } else { + p++; + } + } + + q = p; + while (! isspace(*q)) { + q++; + } + + len = q - p; + + return strndup(p, len); +} + +/* sftp version 0-3 code. It is different from the v4 */ +/* maybe a paste of the draft is better than the code */ +/* + uint32 flags + uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE + uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID + uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID + uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS + uint32 atime present only if flag SSH_FILEXFER_ACMODTIME + uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME + uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED + string extended_type + string extended_data + ... more extended data (extended_type - extended_data pairs), + so that number of pairs equals extended_count */ +static sftp_attributes sftp_parse_attr_3(sftp_session sftp, + ssh_buffer buf, + int expectname) +{ + sftp_attributes attr; + int rc; + + attr = calloc(1, sizeof(struct sftp_attributes_struct)); + if (attr == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + if (expectname) { + rc = ssh_buffer_unpack(buf, "ss", + &attr->name, + &attr->longname); + if (rc != SSH_OK){ + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Name: %s", attr->name); + + /* Set owner and group if we talk to openssh and have the longname */ + if (ssh_get_openssh_version(sftp->session)) { + attr->owner = sftp_parse_longname(attr->longname, + SFTP_LONGNAME_OWNER); + if (attr->owner == NULL) { + goto error; + } + + attr->group = sftp_parse_longname(attr->longname, + SFTP_LONGNAME_GROUP); + if (attr->group == NULL) { + goto error; + } + } + } + + rc = ssh_buffer_unpack(buf, "d", &attr->flags); + if (rc != SSH_OK){ + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, + "Flags: %.8" PRIx32 "\n", attr->flags); + + if (attr->flags & SSH_FILEXFER_ATTR_SIZE) { + rc = ssh_buffer_unpack(buf, "q", &attr->size); + if(rc != SSH_OK) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, + "Size: %" PRIu64 "\n", + (uint64_t) attr->size); + } + + if (attr->flags & SSH_FILEXFER_ATTR_UIDGID) { + rc = ssh_buffer_unpack(buf, "dd", + &attr->uid, + &attr->gid); + if (rc != SSH_OK) { + goto error; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + rc = ssh_buffer_unpack(buf, "d", &attr->permissions); + if (rc != SSH_OK) { + goto error; + } + + switch (attr->permissions & SSH_S_IFMT) { + case SSH_S_IFSOCK: + case SSH_S_IFBLK: + case SSH_S_IFCHR: + case SSH_S_IFIFO: + attr->type = SSH_FILEXFER_TYPE_SPECIAL; + break; + case SSH_S_IFLNK: + attr->type = SSH_FILEXFER_TYPE_SYMLINK; + break; + case SSH_S_IFREG: + attr->type = SSH_FILEXFER_TYPE_REGULAR; + break; + case SSH_S_IFDIR: + attr->type = SSH_FILEXFER_TYPE_DIRECTORY; + break; + default: + attr->type = SSH_FILEXFER_TYPE_UNKNOWN; + break; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_ACMODTIME) { + rc = ssh_buffer_unpack(buf, "dd", + &attr->atime, + &attr->mtime); + if (rc != SSH_OK) { + goto error; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_EXTENDED) { + rc = ssh_buffer_unpack(buf, "d", &attr->extended_count); + if (rc != SSH_OK) { + goto error; + } + + if (attr->extended_count > 0) { + rc = ssh_buffer_unpack(buf, "ss", + &attr->extended_type, + &attr->extended_data); + if (rc != SSH_OK) { + goto error; + } + attr->extended_count--; + } + /* just ignore the remaining extensions */ + + while (attr->extended_count > 0) { + ssh_string tmp1,tmp2; + rc = ssh_buffer_unpack(buf, "SS", &tmp1, &tmp2); + if (rc != SSH_OK){ + goto error; + } + SAFE_FREE(tmp1); + SAFE_FREE(tmp2); + attr->extended_count--; + } + } + + return attr; + +error: + SSH_STRING_FREE(attr->extended_type); + SSH_STRING_FREE(attr->extended_data); + SAFE_FREE(attr->name); + SAFE_FREE(attr->longname); + SAFE_FREE(attr->owner); + SAFE_FREE(attr->group); + SAFE_FREE(attr); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); + sftp_set_error(sftp, SSH_FX_FAILURE); + + return NULL; +} + +sftp_attributes sftp_parse_attr(sftp_session session, + ssh_buffer buf, + int expectname) +{ + switch (session->version) { + case 4: + return sftp_parse_attr_4(session, buf, expectname); + case 3: + case 2: + case 1: + case 0: + return sftp_parse_attr_3(session, buf, expectname); + default: + ssh_set_error(session->session, SSH_FATAL, + "Version %d unsupported by client", + session->server_version); + return NULL; + } + + return NULL; +} + +void sftp_set_error(sftp_session sftp, int errnum) +{ + if (sftp != NULL) { + sftp->errnum = errnum; + } +} + +void sftp_message_free(sftp_message msg) +{ + if (msg == NULL) { + return; + } + + SSH_BUFFER_FREE(msg->payload); + SAFE_FREE(msg); +} + +static sftp_request_queue request_queue_new(sftp_message msg) +{ + sftp_request_queue queue = NULL; + + queue = calloc(1, sizeof(struct sftp_request_queue_struct)); + if (queue == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + queue->message = msg; + + return queue; +} + +static void request_queue_free(sftp_request_queue queue) +{ + if (queue == NULL) { + return; + } + + ZERO_STRUCTP(queue); + SAFE_FREE(queue); +} + +static int sftp_enqueue(sftp_session sftp, sftp_message msg) +{ + sftp_request_queue queue = NULL; + sftp_request_queue ptr; + + queue = request_queue_new(msg); + if (queue == NULL) { + return -1; + } + + SSH_LOG(SSH_LOG_PACKET, + "Queued msg id %" PRIu32 " type %d", + msg->id, msg->packet_type); + + if(sftp->queue == NULL) { + sftp->queue = queue; + } else { + ptr = sftp->queue; + while(ptr->next) { + ptr=ptr->next; /* find end of linked list */ + } + ptr->next = queue; /* add it on bottom */ + } + + return 0; +} + +/* + * Pulls a message from the queue based on the ID. + * Returns NULL if no message has been found. + */ +sftp_message sftp_dequeue(sftp_session sftp, uint32_t id) +{ + sftp_request_queue prev = NULL; + sftp_request_queue queue; + sftp_message msg; + + if(sftp->queue == NULL) { + return NULL; + } + + queue = sftp->queue; + while (queue) { + if (queue->message->id == id) { + /* remove from queue */ + if (prev == NULL) { + sftp->queue = queue->next; + } else { + prev->next = queue->next; + } + msg = queue->message; + request_queue_free(queue); + SSH_LOG(SSH_LOG_PACKET, + "Dequeued msg id %" PRIu32 " type %d", + msg->id, + msg->packet_type); + return msg; + } + prev = queue; + queue = queue->next; + } + + return NULL; +} + +static sftp_message sftp_get_message(sftp_packet packet) +{ + sftp_session sftp = packet->sftp; + sftp_message msg = NULL; + int rc; + + switch (packet->type) { + case SSH_FXP_STATUS: + case SSH_FXP_HANDLE: + case SSH_FXP_DATA: + case SSH_FXP_ATTRS: + case SSH_FXP_NAME: + case SSH_FXP_EXTENDED_REPLY: + break; + default: + ssh_set_error(packet->sftp->session, + SSH_FATAL, + "Unknown packet type %d", + packet->type); + sftp_set_error(packet->sftp, SSH_FX_FAILURE); + return NULL; + } + + msg = calloc(1, sizeof(struct sftp_message_struct)); + if (msg == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(packet->sftp, SSH_FX_FAILURE); + return NULL; + } + + msg->sftp = packet->sftp; + msg->packet_type = packet->type; + + /* Move the payload from the packet to the message */ + msg->payload = packet->payload; + packet->payload = NULL; + + rc = ssh_buffer_unpack(msg->payload, "d", &msg->id); + if (rc != SSH_OK) { + ssh_set_error(packet->sftp->session, SSH_FATAL, + "Invalid packet %d: no ID", packet->type); + sftp_message_free(msg); + sftp_set_error(packet->sftp, SSH_FX_FAILURE); + return NULL; + } + + SSH_LOG(SSH_LOG_PACKET, + "Packet with id %" PRIu32 " type %d", + msg->id, + msg->packet_type); + + return msg; +} + +int sftp_read_and_dispatch(sftp_session sftp) +{ + sftp_packet packet = NULL; + sftp_message msg = NULL; + + packet = sftp_packet_read(sftp); + if (packet == NULL) { + /* something nasty happened reading the packet */ + return -1; + } + + msg = sftp_get_message(packet); + if (msg == NULL) { + return -1; + } + + if (sftp_enqueue(sftp, msg) < 0) { + sftp_message_free(msg); + return -1; + } + + return 0; +} + +sftp_status_message parse_status_msg(sftp_message msg) +{ + sftp_status_message status = NULL; + int rc; + + if (msg->packet_type != SSH_FXP_STATUS) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Not a ssh_fxp_status message passed in!"); + sftp_set_error(msg->sftp, SSH_FX_BAD_MESSAGE); + return NULL; + } + + status = calloc(1, sizeof(struct sftp_status_message_struct)); + if (status == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + status->id = msg->id; + rc = ssh_buffer_unpack(msg->payload, "d", + &status->status); + if (rc != SSH_OK) { + SAFE_FREE(status); + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_STATUS message"); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_unpack(msg->payload, "ss", + &status->errormsg, + &status->langmsg); + + if (rc != SSH_OK && msg->sftp->version >= 3) { + /* These are mandatory from version 3 */ + SAFE_FREE(status); + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_STATUS message"); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + if (status->errormsg == NULL) + status->errormsg = strdup("No error message in packet"); + + if (status->langmsg == NULL) + status->langmsg = strdup(""); + + if (status->errormsg == NULL || status->langmsg == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + status_msg_free(status); + return NULL; + } + + return status; +} + +void status_msg_free(sftp_status_message status) +{ + if (status == NULL) { + return; + } + + SAFE_FREE(status->errormsg); + SAFE_FREE(status->langmsg); + SAFE_FREE(status); +} + +#endif /* WITH_SFTP */ diff --git a/src/sftpserver.c b/src/sftpserver.c index 9117f155..7d8070b1 100644 --- a/src/sftpserver.c +++ b/src/sftpserver.c @@ -3,7 +3,8 @@ * * This file is part of the SSH Library * - * Copyright (c) 2005 by Aris Adamantiadis + * Copyright (c) 2005 Aris Adamantiadis + * Copyright (c) 2022 Zeyu Sheng <shengzeyu19_98@163.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 @@ -23,16 +24,23 @@ #include "config.h" -#include <stdio.h> #ifndef _WIN32 #include <netinet/in.h> #include <arpa/inet.h> +#include <dirent.h> +#include <sys/statvfs.h> #endif +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/stat.h> +#include <time.h> #include "libssh/libssh.h" #include "libssh/sftp.h" #include "libssh/sftp_priv.h" +#include "libssh/sftpserver.h" #include "libssh/ssh2.h" #include "libssh/priv.h" #include "libssh/buffer.h" @@ -40,518 +48,1720 @@ #define SFTP_HANDLES 256 -sftp_client_message sftp_get_client_message(sftp_session sftp) { - ssh_session session = sftp->session; - sftp_packet packet; - sftp_client_message msg; - ssh_buffer payload; - int rc; +#define MAX_ENTRIES_NUM_IN_PACKET 50 +#define MAX_LONG_NAME_LEN 350 - msg = malloc(sizeof (struct sftp_client_message_struct)); - if (msg == NULL) { - ssh_set_error_oom(session); - return NULL; - } - ZERO_STRUCTP(msg); +static sftp_client_message +sftp_make_client_message(sftp_session sftp, sftp_packet packet) +{ + ssh_session session = sftp->session; + sftp_client_message msg = NULL; + ssh_buffer payload = NULL; + int rc; + int version; + + msg = calloc(1, sizeof(struct sftp_client_message_struct)); + if (msg == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + payload = packet->payload; + msg->type = packet->type; + msg->sftp = sftp; + + /* take a copy of the whole packet */ + msg->complete_message = ssh_buffer_new(); + if (msg->complete_message == NULL) { + ssh_set_error_oom(session); + goto error; + } - packet = sftp_packet_read(sftp); - if (packet == NULL) { - ssh_set_error_oom(session); + rc = ssh_buffer_add_data(msg->complete_message, + ssh_buffer_get(payload), + ssh_buffer_get_len(payload)); + if (rc < 0) { + goto error; + } + + if (msg->type != SSH_FXP_INIT) { + rc = ssh_buffer_get_u32(payload, &msg->id); + if (rc != sizeof(uint32_t)) { + goto error; + } + } + + switch (msg->type) { + case SSH_FXP_INIT: + rc = ssh_buffer_unpack(payload, + "d", + &version); + if (rc != SSH_OK) { + printf("unpack init failed!\n"); + goto error; + } + version = ntohl(version); + sftp->client_version = version; + break; + case SSH_FXP_CLOSE: + case SSH_FXP_READDIR: + msg->handle = ssh_buffer_get_ssh_string(payload); + if (msg->handle == NULL) { + goto error; + } + break; + case SSH_FXP_READ: + rc = ssh_buffer_unpack(payload, + "Sqd", + &msg->handle, + &msg->offset, + &msg->len); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_WRITE: + rc = ssh_buffer_unpack(payload, + "SqS", + &msg->handle, + &msg->offset, + &msg->data); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_REMOVE: + case SSH_FXP_RMDIR: + case SSH_FXP_OPENDIR: + case SSH_FXP_READLINK: + case SSH_FXP_REALPATH: + rc = ssh_buffer_unpack(payload, + "s", + &msg->filename); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_RENAME: + case SSH_FXP_SYMLINK: + rc = ssh_buffer_unpack(payload, + "sS", + &msg->filename, + &msg->data); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_MKDIR: + case SSH_FXP_SETSTAT: + rc = ssh_buffer_unpack(payload, + "s", + &msg->filename); + if (rc != SSH_OK) { + goto error; + } + msg->attr = sftp_parse_attr(sftp, payload, 0); + if (msg->attr == NULL) { + goto error; + } + break; + case SSH_FXP_FSETSTAT: + msg->handle = ssh_buffer_get_ssh_string(payload); + if (msg->handle == NULL) { + goto error; + } + msg->attr = sftp_parse_attr(sftp, payload, 0); + if (msg->attr == NULL) { + goto error; + } + break; + case SSH_FXP_LSTAT: + case SSH_FXP_STAT: + rc = ssh_buffer_unpack(payload, + "s", + &msg->filename); + if (rc != SSH_OK) { + goto error; + } + if (sftp->version > 3) { + ssh_buffer_unpack(payload, "d", &msg->flags); + } + break; + case SSH_FXP_OPEN: + rc = ssh_buffer_unpack(payload, + "sd", + &msg->filename, + &msg->flags); + if (rc != SSH_OK) { + goto error; + } + msg->attr = sftp_parse_attr(sftp, payload, 0); + if (msg->attr == NULL) { + goto error; + } + break; + case SSH_FXP_FSTAT: + rc = ssh_buffer_unpack(payload, + "S", + &msg->handle); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_EXTENDED: + rc = ssh_buffer_unpack(payload, + "s", + &msg->submessage); + if (rc != SSH_OK) { + goto error; + } + + if (strcmp(msg->submessage, "hardlink@openssh.com") == 0 || + strcmp(msg->submessage, "posix-rename@openssh.com") == 0) { + rc = ssh_buffer_unpack(payload, + "sS", + &msg->filename, + &msg->data); + if (rc != SSH_OK) { + goto error; + } + } else if (strcmp(msg->submessage, "statvfs@openssh.com") == 0 ){ + rc = ssh_buffer_unpack(payload, + "s", + &msg->filename); + if (rc != SSH_OK) { + goto error; + } + } + break; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received unhandled sftp message %d", msg->type); + goto error; + } + + return msg; + +error: sftp_client_message_free(msg); return NULL; - } - - payload = packet->payload; - msg->type = packet->type; - msg->sftp = sftp; +} - /* take a copy of the whole packet */ - msg->complete_message = ssh_buffer_new(); - if (msg->complete_message == NULL) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } +sftp_client_message sftp_get_client_message(sftp_session sftp) +{ + ssh_session session = sftp->session; + sftp_packet packet; - rc = ssh_buffer_add_data(msg->complete_message, - ssh_buffer_get(payload), - ssh_buffer_get_len(payload)); - if (rc < 0) { + packet = sftp_packet_read(sftp); + if (packet == NULL) { ssh_set_error_oom(session); - sftp_client_message_free(msg); return NULL; - } + } + return sftp_make_client_message(sftp, packet); +} - ssh_buffer_get_u32(payload, &msg->id); +/** + * @brief Get the client message from a sftp packet. + * + * @param sftp The sftp session handle. + * + * @return The pointer to the generated sftp client message. + */ +static sftp_client_message +sftp_get_client_message_from_packet(sftp_session sftp) +{ + sftp_packet packet = NULL; - switch(msg->type) { - case SSH_FXP_CLOSE: - case SSH_FXP_READDIR: - msg->handle = ssh_buffer_get_ssh_string(payload); - if (msg->handle == NULL) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_READ: - rc = ssh_buffer_unpack(payload, - "Sqd", - &msg->handle, - &msg->offset, - &msg->len); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_WRITE: - rc = ssh_buffer_unpack(payload, - "SqS", - &msg->handle, - &msg->offset, - &msg->data); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_REMOVE: - case SSH_FXP_RMDIR: - case SSH_FXP_OPENDIR: - case SSH_FXP_READLINK: - case SSH_FXP_REALPATH: - rc = ssh_buffer_unpack(payload, - "s", - &msg->filename); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_RENAME: - case SSH_FXP_SYMLINK: - rc = ssh_buffer_unpack(payload, - "sS", - &msg->filename, - &msg->data); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_MKDIR: - case SSH_FXP_SETSTAT: - rc = ssh_buffer_unpack(payload, - "s", - &msg->filename); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - msg->attr = sftp_parse_attr(sftp, payload, 0); - if (msg->attr == NULL) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_FSETSTAT: - msg->handle = ssh_buffer_get_ssh_string(payload); - if (msg->handle == NULL) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - msg->attr = sftp_parse_attr(sftp, payload, 0); - if (msg->attr == NULL) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_LSTAT: - case SSH_FXP_STAT: - rc = ssh_buffer_unpack(payload, - "s", - &msg->filename); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - if(sftp->version > 3) { - ssh_buffer_unpack(payload, "d", &msg->flags); - } - break; - case SSH_FXP_OPEN: - rc = ssh_buffer_unpack(payload, - "sd", - &msg->filename, - &msg->flags); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - msg->attr = sftp_parse_attr(sftp, payload, 0); - if (msg->attr == NULL) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_FSTAT: - rc = ssh_buffer_unpack(payload, - "S", - &msg->handle); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_EXTENDED: - rc = ssh_buffer_unpack(payload, - "s", - &msg->submessage); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); + packet = sftp->read_packet; + if (packet == NULL) { return NULL; - } - - if (strcmp(msg->submessage, "hardlink@openssh.com") == 0 || - strcmp(msg->submessage, "posix-rename@openssh.com") == 0) { - rc = ssh_buffer_unpack(payload, - "sS", - &msg->filename, - &msg->data); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - } - break; - default: - ssh_set_error(sftp->session, SSH_FATAL, - "Received unhandled sftp message %d", msg->type); - sftp_client_message_free(msg); - return NULL; - } - - return msg; + } + return sftp_make_client_message(sftp, packet); } -/* Send an sftp client message. Can be used in cas of proxying */ -int sftp_send_client_message(sftp_session sftp, sftp_client_message msg){ - return sftp_packet_write(sftp, msg->type, msg->complete_message); +/* Send an sftp client message. Can be used in case of proxying */ +int sftp_send_client_message(sftp_session sftp, sftp_client_message msg) +{ + return sftp_packet_write(sftp, msg->type, msg->complete_message); } -uint8_t sftp_client_message_get_type(sftp_client_message msg){ - return msg->type; +uint8_t sftp_client_message_get_type(sftp_client_message msg) +{ + return msg->type; } -const char *sftp_client_message_get_filename(sftp_client_message msg){ - return msg->filename; +const char *sftp_client_message_get_filename(sftp_client_message msg) +{ + return msg->filename; } -void sftp_client_message_set_filename(sftp_client_message msg, const char *newname){ - free(msg->filename); - msg->filename = strdup(newname); +void +sftp_client_message_set_filename(sftp_client_message msg, const char *newname) +{ + free(msg->filename); + msg->filename = strdup(newname); } -const char *sftp_client_message_get_data(sftp_client_message msg){ - if (msg->str_data == NULL) - msg->str_data = ssh_string_to_char(msg->data); - return msg->str_data; +const char *sftp_client_message_get_data(sftp_client_message msg) +{ + if (msg->str_data == NULL) + msg->str_data = ssh_string_to_char(msg->data); + return msg->str_data; } -uint32_t sftp_client_message_get_flags(sftp_client_message msg){ - return msg->flags; +uint32_t sftp_client_message_get_flags(sftp_client_message msg) +{ + return msg->flags; } -const char *sftp_client_message_get_submessage(sftp_client_message msg){ - return msg->submessage; +const char *sftp_client_message_get_submessage(sftp_client_message msg) +{ + return msg->submessage; } -void sftp_client_message_free(sftp_client_message msg) { - if (msg == NULL) { - return; - } +void sftp_client_message_free(sftp_client_message msg) +{ + if (msg == NULL) { + return; + } - SAFE_FREE(msg->filename); - SAFE_FREE(msg->submessage); - SSH_STRING_FREE(msg->data); - SSH_STRING_FREE(msg->handle); - sftp_attributes_free(msg->attr); - SSH_BUFFER_FREE(msg->complete_message); - SAFE_FREE(msg->str_data); - ZERO_STRUCTP(msg); - SAFE_FREE(msg); + SAFE_FREE(msg->filename); + SAFE_FREE(msg->submessage); + SSH_STRING_FREE(msg->data); + SSH_STRING_FREE(msg->handle); + sftp_attributes_free(msg->attr); + SSH_BUFFER_FREE(msg->complete_message); + SAFE_FREE(msg->str_data); + ZERO_STRUCTP(msg); + SAFE_FREE(msg); } -int sftp_reply_name(sftp_client_message msg, const char *name, - sftp_attributes attr) { - ssh_buffer out; - ssh_string file; +int +sftp_reply_name(sftp_client_message msg, const char *name, sftp_attributes attr) +{ + ssh_buffer out; + ssh_string file; - out = ssh_buffer_new(); - if (out == NULL) { - return -1; - } + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } - file = ssh_string_from_char(name); - if (file == NULL) { - SSH_BUFFER_FREE(out); - return -1; - } - - if (ssh_buffer_add_u32(out, msg->id) < 0 || - ssh_buffer_add_u32(out, htonl(1)) < 0 || - ssh_buffer_add_ssh_string(out, file) < 0 || - ssh_buffer_add_ssh_string(out, file) < 0 || /* The protocol is broken here between 3 & 4 */ - buffer_add_attributes(out, attr) < 0 || - sftp_packet_write(msg->sftp, SSH_FXP_NAME, out) < 0) { + file = ssh_string_from_char(name); + if (file == NULL) { + SSH_BUFFER_FREE(out); + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending name %s", ssh_string_get_char(file)); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u32(out, htonl(1)) < 0 || + ssh_buffer_add_ssh_string(out, file) < 0 || + ssh_buffer_add_ssh_string(out, file) < 0 || /* The protocol is broken here between 3 & 4 */ + buffer_add_attributes(out, attr) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_NAME, out) < 0) { + SSH_BUFFER_FREE(out); + SSH_STRING_FREE(file); + return -1; + } SSH_BUFFER_FREE(out); SSH_STRING_FREE(file); - return -1; - } - SSH_BUFFER_FREE(out); - SSH_STRING_FREE(file); - return 0; + return 0; } -int sftp_reply_handle(sftp_client_message msg, ssh_string handle){ - ssh_buffer out; +int sftp_reply_handle(sftp_client_message msg, ssh_string handle) +{ + ssh_buffer out; - out = ssh_buffer_new(); - if (out == NULL) { - return -1; - } + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } - if (ssh_buffer_add_u32(out, msg->id) < 0 || - ssh_buffer_add_ssh_string(out, handle) < 0 || - sftp_packet_write(msg->sftp, SSH_FXP_HANDLE, out) < 0) { + ssh_log_hexdump("Sending handle:", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_ssh_string(out, handle) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_HANDLE, out) < 0) { + SSH_BUFFER_FREE(out); + return -1; + } SSH_BUFFER_FREE(out); - return -1; - } - SSH_BUFFER_FREE(out); - return 0; + return 0; } -int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr) { - ssh_buffer out; +int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr) +{ + ssh_buffer out; - out = ssh_buffer_new(); - if (out == NULL) { - return -1; - } + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } - if (ssh_buffer_add_u32(out, msg->id) < 0 || - buffer_add_attributes(out, attr) < 0 || - sftp_packet_write(msg->sftp, SSH_FXP_ATTRS, out) < 0) { + SSH_LOG(SSH_LOG_PROTOCOL, "Sending attr"); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + buffer_add_attributes(out, attr) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_ATTRS, out) < 0) { + SSH_BUFFER_FREE(out); + return -1; + } SSH_BUFFER_FREE(out); - return -1; - } - SSH_BUFFER_FREE(out); - return 0; + return 0; } -int sftp_reply_names_add(sftp_client_message msg, const char *file, - const char *longname, sftp_attributes attr) { - ssh_string name; +int +sftp_reply_names_add(sftp_client_message msg, const char *file, + const char *longname, sftp_attributes attr) +{ + ssh_string name; - name = ssh_string_from_char(file); - if (name == NULL) { - return -1; - } + name = ssh_string_from_char(file); + if (name == NULL) { + return -1; + } - if (msg->attrbuf == NULL) { - msg->attrbuf = ssh_buffer_new(); if (msg->attrbuf == NULL) { - SSH_STRING_FREE(name); - return -1; + msg->attrbuf = ssh_buffer_new(); + if (msg->attrbuf == NULL) { + SSH_STRING_FREE(name); + return -1; + } } - } - if (ssh_buffer_add_ssh_string(msg->attrbuf, name) < 0) { - SSH_STRING_FREE(name); - return -1; - } + if (ssh_buffer_add_ssh_string(msg->attrbuf, name) < 0) { + SSH_STRING_FREE(name); + return -1; + } - SSH_STRING_FREE(name); - name = ssh_string_from_char(longname); - if (name == NULL) { - return -1; - } - if (ssh_buffer_add_ssh_string(msg->attrbuf,name) < 0 || - buffer_add_attributes(msg->attrbuf,attr) < 0) { SSH_STRING_FREE(name); - return -1; - } - SSH_STRING_FREE(name); - msg->attr_num++; + name = ssh_string_from_char(longname); + if (name == NULL) { + return -1; + } + if (ssh_buffer_add_ssh_string(msg->attrbuf, name) < 0 || + buffer_add_attributes(msg->attrbuf, attr) < 0) { + SSH_STRING_FREE(name); + return -1; + } + SSH_STRING_FREE(name); + msg->attr_num++; - return 0; + return 0; } -int sftp_reply_names(sftp_client_message msg) { - ssh_buffer out; +int sftp_reply_names(sftp_client_message msg) +{ + ssh_buffer out; - out = ssh_buffer_new(); - if (out == NULL) { - SSH_BUFFER_FREE(msg->attrbuf); - return -1; - } + out = ssh_buffer_new(); + if (out == NULL) { + SSH_BUFFER_FREE(msg->attrbuf); + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending %d names", msg->attr_num); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u32(out, htonl(msg->attr_num)) < 0 || + ssh_buffer_add_data(out, ssh_buffer_get(msg->attrbuf), + ssh_buffer_get_len(msg->attrbuf)) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_NAME, out) < 0) { + SSH_BUFFER_FREE(out); + SSH_BUFFER_FREE(msg->attrbuf); + return -1; + } - if (ssh_buffer_add_u32(out, msg->id) < 0 || - ssh_buffer_add_u32(out, htonl(msg->attr_num)) < 0 || - ssh_buffer_add_data(out, ssh_buffer_get(msg->attrbuf), - ssh_buffer_get_len(msg->attrbuf)) < 0 || - sftp_packet_write(msg->sftp, SSH_FXP_NAME, out) < 0) { SSH_BUFFER_FREE(out); SSH_BUFFER_FREE(msg->attrbuf); - return -1; - } - - SSH_BUFFER_FREE(out); - SSH_BUFFER_FREE(msg->attrbuf); - msg->attr_num = 0; - msg->attrbuf = NULL; + msg->attr_num = 0; + msg->attrbuf = NULL; - return 0; + return 0; } -int sftp_reply_status(sftp_client_message msg, uint32_t status, - const char *message) { - ssh_buffer out; - ssh_string s; +int +sftp_reply_status(sftp_client_message msg, uint32_t status, const char *message) +{ + ssh_buffer out; + ssh_string s; - out = ssh_buffer_new(); - if (out == NULL) { - return -1; - } + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } - s = ssh_string_from_char(message ? message : ""); - if (s == NULL) { - SSH_BUFFER_FREE(out); - return -1; - } + s = ssh_string_from_char(message ? message : ""); + if (s == NULL) { + SSH_BUFFER_FREE(out); + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending status %d, message: %s", status, + ssh_string_get_char(s)); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u32(out, htonl(status)) < 0 || + ssh_buffer_add_ssh_string(out, s) < 0 || + ssh_buffer_add_u32(out, 0) < 0 || /* language string */ + sftp_packet_write(msg->sftp, SSH_FXP_STATUS, out) < 0) { + SSH_BUFFER_FREE(out); + SSH_STRING_FREE(s); + return -1; + } - if (ssh_buffer_add_u32(out, msg->id) < 0 || - ssh_buffer_add_u32(out, htonl(status)) < 0 || - ssh_buffer_add_ssh_string(out, s) < 0 || - ssh_buffer_add_u32(out, 0) < 0 || /* language string */ - sftp_packet_write(msg->sftp, SSH_FXP_STATUS, out) < 0) { SSH_BUFFER_FREE(out); SSH_STRING_FREE(s); - return -1; - } - - SSH_BUFFER_FREE(out); - SSH_STRING_FREE(s); - return 0; + return 0; } -int sftp_reply_data(sftp_client_message msg, const void *data, int len) { - ssh_buffer out; +int sftp_reply_data(sftp_client_message msg, const void *data, int len) +{ + ssh_buffer out; - out = ssh_buffer_new(); - if (out == NULL) { - return -1; - } + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } - if (ssh_buffer_add_u32(out, msg->id) < 0 || - ssh_buffer_add_u32(out, ntohl(len)) < 0 || - ssh_buffer_add_data(out, data, len) < 0 || - sftp_packet_write(msg->sftp, SSH_FXP_DATA, out) < 0) { + SSH_LOG(SSH_LOG_PROTOCOL, "Sending data, length: %d", len); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u32(out, ntohl(len)) < 0 || + ssh_buffer_add_data(out, data, len) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_DATA, out) < 0) { + SSH_BUFFER_FREE(out); + return -1; + } SSH_BUFFER_FREE(out); - return -1; - } - SSH_BUFFER_FREE(out); - return 0; + return 0; +} + +/** + * @brief Handle the statvfs request, return information the mounted file system. + * + * @param msg The sftp client message. + * + * @param st The statvfs state of target file. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +static int +sftp_reply_statvfs(sftp_client_message msg, sftp_statvfs_t st) +{ + int ret = 0; + ssh_buffer out; + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending statvfs reply"); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_bsize)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_frsize)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_blocks)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_bfree)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_bavail)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_files)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_ffree)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_favail)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_fsid)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_flag)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_namemax)) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_EXTENDED_REPLY, out) < 0) { + ret = -1; + } + SSH_BUFFER_FREE(out); + + return ret; +} + +int sftp_reply_version(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_session session = sftp->session; + int version; + ssh_buffer reply; + int rc; + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending version packet"); + + version = sftp->client_version; + reply = ssh_buffer_new(); + if (reply == NULL) { + ssh_set_error_oom(session); + return -1; + } + + rc = ssh_buffer_pack(reply, "dssssss", + LIBSFTP_VERSION, + "posix-rename@openssh.com", + "1", + "hardlink@openssh.com", + "1", + "statvfs@openssh.com", + "2"); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(reply); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_VERSION, reply); + if (rc < 0) { + SSH_BUFFER_FREE(reply); + return -1; + } + SSH_BUFFER_FREE(reply); + + SSH_LOG(SSH_LOG_PROTOCOL, "Server version sent"); + + if (version > LIBSFTP_VERSION) { + sftp->version = LIBSFTP_VERSION; + } else { + sftp->version = version; + } + + return SSH_OK; } + /* * This function will return you a new handle to give the client. * the function accepts an info that can be retrieved later with * the handle. Care is given that a corrupted handle won't give a * valid info (or worse). */ -ssh_string sftp_handle_alloc(sftp_session sftp, void *info) { - ssh_string ret; - uint32_t val; - uint32_t i; +ssh_string sftp_handle_alloc(sftp_session sftp, void *info) +{ + ssh_string ret; + uint32_t val; + uint32_t i; - if (sftp->handles == NULL) { - sftp->handles = calloc(SFTP_HANDLES, sizeof(void *)); if (sftp->handles == NULL) { - return NULL; + sftp->handles = calloc(SFTP_HANDLES, sizeof(void *)); + if (sftp->handles == NULL) { + return NULL; + } } - } - for (i = 0; i < SFTP_HANDLES; i++) { - if (sftp->handles[i] == NULL) { - break; + for (i = 0; i < SFTP_HANDLES; i++) { + if (sftp->handles[i] == NULL) { + break; + } } - } - if (i == SFTP_HANDLES) { - return NULL; /* no handle available */ - } + if (i == SFTP_HANDLES) { + return NULL; /* no handle available */ + } - val = i; - ret = ssh_string_new(4); - if (ret == NULL) { - return NULL; - } + val = i; + ret = ssh_string_new(4); + if (ret == NULL) { + return NULL; + } - memcpy(ssh_string_data(ret), &val, sizeof(uint32_t)); - sftp->handles[i] = info; + memcpy(ssh_string_data(ret), &val, sizeof(uint32_t)); + sftp->handles[i] = info; - return ret; + return ret; } -void *sftp_handle(sftp_session sftp, ssh_string handle){ - uint32_t val; +void *sftp_handle(sftp_session sftp, ssh_string handle) +{ + uint32_t val; - if (sftp->handles == NULL) { - return NULL; - } + if (sftp->handles == NULL) { + return NULL; + } - if (ssh_string_len(handle) != sizeof(uint32_t)) { - return NULL; - } + if (ssh_string_len(handle) != sizeof(uint32_t)) { + return NULL; + } - memcpy(&val, ssh_string_data(handle), sizeof(uint32_t)); + memcpy(&val, ssh_string_data(handle), sizeof(uint32_t)); - if (val > SFTP_HANDLES) { - return NULL; - } + if (val > SFTP_HANDLES) { + return NULL; + } + + return sftp->handles[val]; +} + +void sftp_handle_remove(sftp_session sftp, void *handle) +{ + int i; + + for (i = 0; i < SFTP_HANDLES; i++) { + if (sftp->handles[i] == handle) { + sftp->handles[i] = NULL; + break; + } + } +} + +/* Default SFTP handlers */ + +static const char * +ssh_str_error(int u_errno) +{ + switch (u_errno) { + case SSH_FX_NO_SUCH_FILE: + return "No such file"; + case SSH_FX_PERMISSION_DENIED: + return "Permission denied"; + case SSH_FX_BAD_MESSAGE: + return "Bad message"; + case SSH_FX_OP_UNSUPPORTED: + return "Operation not supported"; + default: + return "Operation failed"; + } +} + +static int +unix_errno_to_ssh_stat(int u_errno) +{ + int ret = SSH_OK; + switch (u_errno) { + case 0: + break; + case ENOENT: + case ENOTDIR: + case EBADF: + case ELOOP: + ret = SSH_FX_NO_SUCH_FILE; + break; + case EPERM: + case EACCES: + case EFAULT: + ret = SSH_FX_PERMISSION_DENIED; + break; + case ENAMETOOLONG: + case EINVAL: + ret = SSH_FX_BAD_MESSAGE; + break; + case ENOSYS: + ret = SSH_FX_OP_UNSUPPORTED; + break; + default: + ret = SSH_FX_FAILURE; + break; + } + + return ret; +} + +static void +stat_to_filexfer_attrib(const struct stat *z_st, struct sftp_attributes_struct *z_attr) +{ + z_attr->flags = 0 | (uint32_t)SSH_FILEXFER_ATTR_SIZE; + z_attr->size = z_st->st_size; + + z_attr->flags |= (uint32_t)SSH_FILEXFER_ATTR_UIDGID; + z_attr->uid = z_st->st_uid; + z_attr->gid = z_st->st_gid; - return sftp->handles[val]; + z_attr->flags |= (uint32_t)SSH_FILEXFER_ATTR_PERMISSIONS; + z_attr->permissions = z_st->st_mode; + + z_attr->flags |= (uint32_t)SSH_FILEXFER_ATTR_ACMODTIME; + z_attr->atime = z_st->st_atime; + z_attr->mtime = z_st->st_mtime; +} + +static void +clear_filexfer_attrib(struct sftp_attributes_struct *z_attr) +{ + z_attr->flags = 0; + z_attr->size = 0; + z_attr->uid = 0; + z_attr->gid = 0; + z_attr->permissions = 0; + z_attr->atime = 0; + z_attr->mtime = 0; +} + +#ifndef _WIN32 +/* internal */ +enum sftp_handle_type +{ + SFTP_NULL_HANDLE, + SFTP_DIR_HANDLE, + SFTP_FILE_HANDLE +}; + +struct sftp_handle +{ + enum sftp_handle_type type; + int fd; + DIR *dirp; + char *name; +}; + +SSH_SFTP_CALLBACK(process_unsupposed); +SSH_SFTP_CALLBACK(process_open); +SSH_SFTP_CALLBACK(process_read); +SSH_SFTP_CALLBACK(process_write); +SSH_SFTP_CALLBACK(process_close); +SSH_SFTP_CALLBACK(process_opendir); +SSH_SFTP_CALLBACK(process_readdir); +SSH_SFTP_CALLBACK(process_rmdir); +SSH_SFTP_CALLBACK(process_realpath); +SSH_SFTP_CALLBACK(process_mkdir); +SSH_SFTP_CALLBACK(process_lstat); +SSH_SFTP_CALLBACK(process_stat); +SSH_SFTP_CALLBACK(process_readlink); +SSH_SFTP_CALLBACK(process_symlink); +SSH_SFTP_CALLBACK(process_remove); +SSH_SFTP_CALLBACK(process_extended_statvfs); + +const struct sftp_message_handler message_handlers[] = { + {"open", NULL, SSH_FXP_OPEN, process_open}, + {"close", NULL, SSH_FXP_CLOSE, process_close}, + {"read", NULL, SSH_FXP_READ, process_read}, + {"write", NULL, SSH_FXP_WRITE, process_write}, + {"lstat", NULL, SSH_FXP_LSTAT, process_lstat}, + {"fstat", NULL, SSH_FXP_FSTAT, process_unsupposed}, + {"setstat", NULL, SSH_FXP_SETSTAT, process_unsupposed}, + {"fsetstat", NULL, SSH_FXP_FSETSTAT, process_unsupposed}, + {"opendir", NULL, SSH_FXP_OPENDIR, process_opendir}, + {"readdir", NULL, SSH_FXP_READDIR, process_readdir}, + {"remove", NULL, SSH_FXP_REMOVE, process_remove}, + {"mkdir", NULL, SSH_FXP_MKDIR, process_mkdir}, + {"rmdir", NULL, SSH_FXP_RMDIR, process_rmdir}, + {"realpath", NULL, SSH_FXP_REALPATH, process_realpath}, + {"stat", NULL, SSH_FXP_STAT, process_stat}, + {"rename", NULL, SSH_FXP_RENAME, process_unsupposed}, + {"readlink", NULL, SSH_FXP_READLINK, process_readlink}, + {"symlink", NULL, SSH_FXP_SYMLINK, process_symlink}, + {"init", NULL, SSH_FXP_INIT, sftp_reply_version}, + {NULL, NULL, 0, NULL}, +}; + +const struct sftp_message_handler extended_handlers[] = { + /* here are some extended type handlers */ + {"statvfs", "statvfs@openssh.com", 0, process_extended_statvfs}, + {NULL, NULL, 0, NULL}, +}; + +static int +process_open(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + uint32_t msg_flag = sftp_client_message_get_flags(client_msg); + uint32_t mode = client_msg->attr->permissions; + ssh_string handle_s = NULL; + struct sftp_handle *h = NULL; + int file_flag; + int fd = -1; + int status; + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing open: filename %s, mode=0%o" PRIu32, + filename, mode); + + if (((msg_flag & (uint32_t)SSH_FXF_READ) == SSH_FXF_READ) && + ((msg_flag & (uint32_t)SSH_FXF_WRITE) == SSH_FXF_WRITE)) { + file_flag = O_RDWR; // file must exist + if ((msg_flag & (uint32_t)SSH_FXF_CREAT) == SSH_FXF_CREAT) + file_flag |= O_CREAT; + } else if ((msg_flag & (uint32_t)SSH_FXF_WRITE) == SSH_FXF_WRITE) { + file_flag = O_WRONLY; + if ((msg_flag & (uint32_t)SSH_FXF_APPEND) == SSH_FXF_APPEND) + file_flag |= O_APPEND; + if ((msg_flag & (uint32_t)SSH_FXF_CREAT) == SSH_FXF_CREAT) + file_flag |= O_CREAT; + } else if ((msg_flag & (uint32_t)SSH_FXF_READ) == SSH_FXF_READ) { + file_flag = O_RDONLY; + } else { + SSH_LOG(SSH_LOG_PROTOCOL, "undefined message flag: %" PRIu32, msg_flag); + sftp_reply_status(client_msg, SSH_FX_FAILURE, "Flag error"); + return SSH_ERROR; + } + + fd = open(filename, file_flag, mode); + if (fd == -1) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "error open file with error: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, "Write error"); + return SSH_ERROR; + } + + h = calloc(1, sizeof (struct sftp_handle)); + if (h == NULL) { + close(fd); + SSH_LOG(SSH_LOG_PROTOCOL, "failed to allocate a new handle"); + sftp_reply_status(client_msg, SSH_FX_FAILURE, + "Failed to allocate new handle"); + return SSH_ERROR; + } + h->fd = fd; + h->type = SFTP_FILE_HANDLE; + handle_s = sftp_handle_alloc(client_msg->sftp, h); + if (handle_s != NULL) { + sftp_reply_handle(client_msg, handle_s); + ssh_string_free(handle_s); + } else { + close(fd); + SSH_LOG(SSH_LOG_PROTOCOL, "Failed to allocate handle"); + sftp_reply_status(client_msg, SSH_FX_FAILURE, + "Failed to allocate handle"); + } + + return SSH_OK; +} + +static int +process_read(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_string handle = client_msg->handle; + struct sftp_handle *h = NULL; + ssize_t allreadn = 0; + int fd = -1; + char *buffer = NULL; + int rv; + + ssh_log_hexdump("Processing read: handle:", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + h = sftp_handle(sftp, handle); + if (h->type == SFTP_FILE_HANDLE) { + fd = h->fd; + } + + if (fd < 0) { + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "invalid fd (%d) received from handle", fd); + return SSH_ERROR; + } + rv = lseek(fd, client_msg->offset, SEEK_SET); + if (rv == -1) { + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, + "error seeking file fd: %d at offset: %" PRIu64, + fd, client_msg->offset); + return SSH_ERROR; + } + + buffer = malloc(client_msg->len); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "Failed to allocate memory for read data"); + return SSH_ERROR; + } + do { + ssize_t readn = read(fd, buffer + allreadn, client_msg->len - allreadn); + if (readn < 0) { + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "read file error!"); + free(buffer); + return SSH_ERROR; + } else if (readn == 0) { + /* no more data to read, EOF ? */ + break; + } + allreadn += readn; + } while (allreadn < (ssize_t)client_msg->len); + + if (allreadn > 0) { + sftp_reply_data(client_msg, buffer, allreadn); + } else { + sftp_reply_status(client_msg, SSH_FX_EOF, NULL); + } + + free(buffer); + return SSH_OK; +} + +static int +process_write(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_string handle = client_msg->handle; + struct sftp_handle *h = NULL; + ssize_t written = 0; + int fd = -1; + const char *msg_data = NULL; + uint32_t len; + int rv; + + ssh_log_hexdump("Processing write: handle", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + h = sftp_handle(sftp, handle); + if (h->type == SFTP_FILE_HANDLE) { + fd = h->fd; + } + if (fd < 0) { + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "write file fd error!"); + return SSH_ERROR; + } + + msg_data = ssh_string_get_char(client_msg->data); + len = ssh_string_len(client_msg->data); + + rv = lseek(fd, client_msg->offset, SEEK_SET); + if (rv == -1) { + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "error seeking file at offset: %" PRIu64, + client_msg->offset); + } + do { + rv = write(fd, msg_data + written, len - written); + if (rv < 0) { + sftp_reply_status(client_msg, SSH_FX_FAILURE, "Write error"); + SSH_LOG(SSH_LOG_PROTOCOL, "file write error!"); + return SSH_ERROR; + } + written += rv; + } while (written < (int)len); + + sftp_reply_status(client_msg, SSH_FX_OK, NULL); + + return SSH_OK; +} + +static int +process_close(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_string handle = client_msg->handle; + struct sftp_handle *h = NULL; + int ret; + + ssh_log_hexdump("Processing close: handle:", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + h = sftp_handle(sftp, handle); + if (h->type == SFTP_FILE_HANDLE) { + int fd = h->fd; + close(fd); + ret = SSH_OK; + } else if (h->type == SFTP_DIR_HANDLE) { + DIR *dir = h->dirp; + closedir(dir); + ret = SSH_OK; + } else { + ret = SSH_ERROR; + } + SAFE_FREE(h->name); + sftp_handle_remove(sftp, h); + SAFE_FREE(h); + + if (ret == SSH_OK) { + sftp_reply_status(client_msg, SSH_FX_OK, NULL); + } else { + SSH_LOG(SSH_LOG_PROTOCOL, "closing file failed"); + sftp_reply_status(client_msg, SSH_FX_BAD_MESSAGE, "Invalid handle"); + } + + return SSH_OK; +} + +static int +process_opendir(sftp_client_message client_msg) +{ + DIR *dir = NULL; + const char *dir_name = sftp_client_message_get_filename(client_msg); + ssh_string handle_s = NULL; + struct sftp_handle *h = NULL; + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing opendir %s", dir_name); + + dir = opendir(dir_name); + if (dir == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "No such directory"); + return SSH_ERROR; + } + + h = calloc(1, sizeof (struct sftp_handle)); + if (h == NULL) { + closedir(dir); + SSH_LOG(SSH_LOG_PROTOCOL, "failed to allocate a new handle"); + sftp_reply_status(client_msg, SSH_FX_FAILURE, + "Failed to allocate new handle"); + return SSH_ERROR; + } + h->dirp = dir; + h->name = strdup(dir_name); + h->type = SFTP_DIR_HANDLE; + handle_s = sftp_handle_alloc(client_msg->sftp, h); + + if (handle_s != NULL) { + sftp_reply_handle(client_msg, handle_s); + ssh_string_free(handle_s); + } else { + closedir(dir); + sftp_reply_status(client_msg, SSH_FX_FAILURE, "No handle available"); + } + + return SSH_OK; +} + +static int +readdir_long_name(char *z_file_name, struct stat *z_st, char *z_long_name) +{ + char tmpbuf[MAX_LONG_NAME_LEN]; + char time[50]; + char *ptr = z_long_name; + int mode = z_st->st_mode; + + *ptr = '\0'; + + switch (mode & S_IFMT) { + case S_IFDIR: + *ptr++ = 'd'; + break; + default: + *ptr++ = '-'; + break; + } + + /* user */ + if (mode & 0400) + *ptr++ = 'r'; + else + *ptr++ = '-'; + + if (mode & 0200) + *ptr++ = 'w'; + else + *ptr++ = '-'; + + if (mode & 0100) { + if (mode & S_ISUID) + *ptr++ = 's'; + else + *ptr++ = 'x'; + } else + *ptr++ = '-'; + + /* group */ + if (mode & 040) + *ptr++ = 'r'; + else + *ptr++ = '-'; + if (mode & 020) + *ptr++ = 'w'; + else + *ptr++ = '-'; + if (mode & 010) + *ptr++ = 'x'; + else + *ptr++ = '-'; + + /* other */ + if (mode & 04) + *ptr++ = 'r'; + else + *ptr++ = '-'; + if (mode & 02) + *ptr++ = 'w'; + else + *ptr++ = '-'; + if (mode & 01) + *ptr++ = 'x'; + else + *ptr++ = '-'; + + *ptr++ = ' '; + *ptr = '\0'; + + snprintf(tmpbuf, sizeof(tmpbuf), "%3d %d %d %d", (int)z_st->st_nlink, + (int)z_st->st_uid, (int)z_st->st_gid, (int)z_st->st_size); + strcat(z_long_name, tmpbuf); + + ctime_r(&z_st->st_mtime, time); + if ((ptr = strchr(time, '\n'))) { + *ptr = '\0'; + } + snprintf(tmpbuf, sizeof(tmpbuf), " %s %s", time + 4, z_file_name); + strcat(z_long_name, tmpbuf); + + return SSH_OK; +} + +static int +process_readdir(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_string handle = client_msg->handle; + struct sftp_handle *h = NULL; + int ret = SSH_OK; + int entries = 0; + struct dirent *dentry = NULL; + DIR *dir = NULL; + char long_path[PATH_MAX]; + int srclen; + const char *handle_name = NULL; + + ssh_log_hexdump("Processing readdir: handle", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + h = sftp_handle(sftp, client_msg->handle); + if (h->type == SFTP_DIR_HANDLE) { + dir = h->dirp; + handle_name = h->name; + } + if (dir == NULL) { + SSH_LOG(SSH_LOG_PROTOCOL, "got wrong handle from msg"); + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + return SSH_ERROR; + } + + if (handle_name == NULL) { + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + return SSH_ERROR; + } + + srclen = strlen(handle_name); + if (srclen + 2 >= PATH_MAX) { + SSH_LOG(SSH_LOG_PROTOCOL, "handle string length exceed max length!"); + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + return SSH_ERROR; + } + + for (int i = 0; i < MAX_ENTRIES_NUM_IN_PACKET; i++) { + dentry = readdir(dir); + + if (dentry != NULL) { + struct sftp_attributes_struct attr; + struct stat st; + char long_name[MAX_LONG_NAME_LEN]; + + if (strlen(dentry->d_name) + srclen + 1 >= PATH_MAX) { + SSH_LOG(SSH_LOG_PROTOCOL, + "handle string length exceed max length!"); + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + return SSH_ERROR; + } + snprintf(long_path, PATH_MAX, "%s/%s", handle_name, dentry->d_name); + + if (lstat(long_path, &st) == 0) { + stat_to_filexfer_attrib(&st, &attr); + } else { + clear_filexfer_attrib(&attr); + } + + if (readdir_long_name(dentry->d_name, &st, long_name) == 0) { + sftp_reply_names_add(client_msg, dentry->d_name, long_name, &attr); + } else { + printf("readdir long name error\n"); + } + + entries++; + } else { + break; + } + } + + if (entries > 0) { + ret = sftp_reply_names(client_msg); + } else { + sftp_reply_status(client_msg, SSH_FX_EOF, NULL); + } + + return ret; +} + +static int +process_mkdir(sftp_client_message client_msg) +{ + int ret = SSH_OK; + const char *filename = sftp_client_message_get_filename(client_msg); + uint32_t msg_flags = client_msg->attr->flags; + uint32_t permission = client_msg->attr->permissions; + uint32_t mode = (msg_flags & (uint32_t)SSH_FILEXFER_ATTR_PERMISSIONS) + ? permission & (uint32_t)07777 : 0777; + int status = SSH_FX_OK; + int rv; + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing mkdir %s, mode=0%o" PRIu32, + filename, mode); + + if (filename == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + rv = mkdir(filename, mode); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "failed to mkdir: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + ret = SSH_ERROR; + } + + sftp_reply_status(client_msg, status, NULL); + + return ret; +} + +static int +process_rmdir(sftp_client_message client_msg) +{ + int ret = SSH_OK; + const char *filename = sftp_client_message_get_filename(client_msg); + int status = SSH_FX_OK; + int rv; + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing rmdir %s", filename); + + if (filename == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + rv = rmdir(filename); + if (rv < 0) { + status = unix_errno_to_ssh_stat(errno); + ret = SSH_ERROR; + } + + sftp_reply_status(client_msg, status, NULL); + + return ret; } -void sftp_handle_remove(sftp_session sftp, void *handle) { - int i; +static int +process_realpath(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + char *path = NULL; - for (i = 0; i < SFTP_HANDLES; i++) { - if (sftp->handles[i] == handle) { - sftp->handles[i] = NULL; - break; + SSH_LOG(SSH_LOG_PROTOCOL, "Processing realpath %s", filename); + + if (filename[0] == '\0') { + path = realpath(".", NULL); + } else { + path = realpath(filename, NULL); + } + if (path == NULL) { + int saved_errno = errno; + int status = unix_errno_to_ssh_stat(saved_errno); + const char *err_msg = ssh_str_error(status); + + SSH_LOG(SSH_LOG_PROTOCOL, "realpath failed: %s", strerror(saved_errno)); + sftp_reply_status(client_msg, status, err_msg); + return SSH_ERROR; + } + sftp_reply_name(client_msg, path, NULL); + free(path); + return SSH_OK; +} + +static int +process_lstat(sftp_client_message client_msg) +{ + int ret = SSH_OK; + const char *filename = sftp_client_message_get_filename(client_msg); + struct sftp_attributes_struct attr; + struct stat st; + int status = SSH_FX_OK; + int rv; + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing lstat %s", filename); + + if (filename == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + rv = lstat(filename, &st); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "lstat failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + ret = SSH_ERROR; + } else { + stat_to_filexfer_attrib(&st, &attr); + sftp_reply_attr(client_msg, &attr); + } + + return ret; +} + +static int +process_stat(sftp_client_message client_msg) +{ + int ret = SSH_OK; + const char *filename = sftp_client_message_get_filename(client_msg); + struct sftp_attributes_struct attr; + struct stat st; + int status = SSH_FX_OK; + int rv; + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing stat %s", filename); + + if (filename == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + rv = stat(filename, &st); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "lstat failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + ret = SSH_ERROR; + } else { + stat_to_filexfer_attrib(&st, &attr); + sftp_reply_attr(client_msg, &attr); + } + + return ret; +} + +static int +process_readlink(sftp_client_message client_msg) +{ + int ret = SSH_OK; + const char *filename = sftp_client_message_get_filename(client_msg); + char buf[PATH_MAX]; + int len = -1; + const char *err_msg; + int status = SSH_FX_OK; + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing readlink %s", filename); + + if (filename == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + len = readlink(filename, buf, sizeof(buf) - 1); + if (len < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "readlink failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + err_msg = ssh_str_error(status); + sftp_reply_status(client_msg, status, err_msg); + ret = SSH_ERROR; + } else { + buf[len] = '\0'; + sftp_reply_name(client_msg, buf, NULL); + } + + return ret; +} + +/* Note, that this function is using reversed order of the arguments than the + * OpenSSH sftp server as they have the arguments switched. See + * section "4.1 sftp: Reversal of arguments to SSH_FXP_SYMLINK' in + * https://github.com/openssh/openssh-portable/blob/master/PROTOCOL + * for more information */ +static int +process_symlink(sftp_client_message client_msg) +{ + int ret = SSH_OK; + const char *destpath = sftp_client_message_get_filename(client_msg); + const char *srcpath = ssh_string_get_char(client_msg->data); + int status = SSH_FX_OK; + int rv; + + SSH_LOG(SSH_LOG_PROTOCOL, "processing symlink: src=%s dest=%s", + srcpath, destpath); + + if (srcpath == NULL || destpath == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + rv = symlink(srcpath, destpath); + if (rv < 0) { + int saved_errno = errno; + status = unix_errno_to_ssh_stat(saved_errno); + SSH_LOG(SSH_LOG_PROTOCOL, "symlink failed: %s", strerror(saved_errno)); + sftp_reply_status(client_msg, status, "Write error"); + ret = SSH_ERROR; + } else { + sftp_reply_status(client_msg, SSH_FX_OK, "write success"); + } + + return ret; +} + +static int +process_remove(sftp_client_message client_msg) +{ + int ret = SSH_OK; + const char *filename = sftp_client_message_get_filename(client_msg); + int rv; + int status = SSH_FX_OK; + + SSH_LOG(SSH_LOG_PROTOCOL, "processing remove: %s", filename); + + rv = unlink(filename); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "unlink failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + ret = SSH_ERROR; + } + + sftp_reply_status(client_msg, status, NULL); + + return ret; +} + +static int +process_unsupposed(sftp_client_message client_msg) +{ + sftp_reply_status(client_msg, SSH_FX_OP_UNSUPPORTED, + "Operation not supported"); + SSH_LOG(SSH_LOG_PROTOCOL, "Message type %d not implemented", + sftp_client_message_get_type(client_msg)); + return SSH_OK; +} + +static int +process_extended_statvfs(sftp_client_message client_msg) +{ + const char *path = sftp_client_message_get_filename(client_msg); + sftp_statvfs_t sftp_statvfs; + struct statvfs st; + uint64_t flag; + int status; + int rv; + + SSH_LOG(SSH_LOG_PROTOCOL, "processing extended statvfs: %s", path); + + rv = statvfs(path, &st); + if (rv != 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "statvfs failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return SSH_ERROR; + } + + sftp_statvfs = calloc(1, sizeof(struct sftp_statvfs_struct)); + if (sftp_statvfs == NULL) { + SSH_LOG(SSH_LOG_PROTOCOL, "Failed to allocate statvfs structure"); + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + return SSH_ERROR; + } + flag = (st.f_flag & ST_RDONLY) ? SSH_FXE_STATVFS_ST_RDONLY : 0; + flag |= (st.f_flag & ST_NOSUID) ? SSH_FXE_STATVFS_ST_NOSUID : 0; + + sftp_statvfs->f_bsize = st.f_bsize; + sftp_statvfs->f_frsize = st.f_frsize; + sftp_statvfs->f_blocks = st.f_blocks; + sftp_statvfs->f_bfree = st.f_bfree; + sftp_statvfs->f_bavail = st.f_bavail; + sftp_statvfs->f_files = st.f_files; + sftp_statvfs->f_ffree = st.f_ffree; + sftp_statvfs->f_favail = st.f_favail; + sftp_statvfs->f_fsid = st.f_fsid; + sftp_statvfs->f_flag = flag; + sftp_statvfs->f_namemax = st.f_namemax; + + rv = sftp_reply_statvfs(client_msg, sftp_statvfs); + free(sftp_statvfs); + if (rv == 0) { + return SSH_OK; + } + return SSH_ERROR; +} + +static int +process_extended(sftp_client_message sftp_msg) +{ + int status = SSH_ERROR; + const char *subtype = sftp_msg->submessage; + sftp_server_message_callback handler = NULL; + + SSH_LOG(SSH_LOG_PROTOCOL, "processing extended message: %s", subtype); + + for (int i = 0; extended_handlers[i].cb != NULL; i++) { + if (strcmp(subtype, extended_handlers[i].extended_name) == 0) { + handler = extended_handlers[i].cb; + break; + } + } + if (handler != NULL) { + status = handler(sftp_msg); + return status; + } + + sftp_reply_status(sftp_msg, SSH_FX_OP_UNSUPPORTED, + "Extended Operation not supported"); + SSH_LOG(SSH_LOG_PROTOCOL, "Extended Message type %s not implemented", + subtype); + return SSH_OK; +} + +static int +dispatch_sftp_request(sftp_client_message sftp_msg) +{ + int status = SSH_ERROR; + sftp_server_message_callback handler = NULL; + uint8_t type = sftp_client_message_get_type(sftp_msg); + + SSH_LOG(SSH_LOG_PROTOCOL, "processing request type: %u", type); + + for (int i = 0; message_handlers[i].cb != NULL; i++) { + if (type == message_handlers[i].type) { + handler = message_handlers[i].cb; + break; + } + } + + if (handler != NULL) { + status = handler(sftp_msg); + } else { + sftp_reply_status(sftp_msg, SSH_FX_OP_UNSUPPORTED, + "Operation not supported"); + SSH_LOG(SSH_LOG_PROTOCOL, "Message type %u not implemented", type); + return SSH_OK; + } + + return status; +} + +static int +process_client_message(sftp_client_message client_msg) +{ + int status = SSH_OK; + if (client_msg == NULL) { + return SSH_ERROR; + } + + switch (client_msg->type) { + case SSH_FXP_EXTENDED: + status = process_extended(client_msg); + break; + default: + status = dispatch_sftp_request(client_msg); + } + + if (status != SSH_OK) + SSH_LOG(SSH_LOG_PROTOCOL, + "error occurred during processing client message!"); + + return status; +} + +/** + * @brief Default subsystem request handler for SFTP subsystem + * + * @param[in] session The ssh session + * @param[in] channel The existing ssh channel + * @param[in] subsystem The subsystem name. Only "sftp" is handled + * @param[out] userdata The pointer to sftp_session which will get the + * resulting SFTP session + * + * @return SSH_OK when the SFTP server was successfully initialized, SSH_ERROR + * otherwise. + */ +int +sftp_channel_default_subsystem_request(ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata) +{ + if (strcmp(subsystem, "sftp") == 0) { + sftp_session *sftp = (sftp_session *)userdata; + + /* initialize sftp session and file handler */ + *sftp = sftp_server_new(session, channel); + if (*sftp == NULL) { + return SSH_ERROR; + } + + return SSH_OK; + } + return SSH_ERROR; +} + +/** + * @brief Default data callback for sftp server + * + * @param[in] session The ssh session + * @param[in] channel The ssh channel with SFTP session opened + * @param[in] data The data to be processed. + * @param[in] len The length of input data to be processed + * @param[in] is_stderr Unused channel flag for stderr flagging + * @param[in] userdata The pointer to sftp_session + * + * @return number of bytes processed, -1 when error occurs. + */ +int +sftp_channel_default_data_callback(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *data, + uint32_t len, + UNUSED_PARAM(int is_stderr), + void *userdata) +{ + sftp_session *sftpp = (sftp_session *)userdata; + sftp_session sftp = NULL; + sftp_client_message msg; + int decode_len; + int rc; + + if (sftpp == NULL) { + SSH_LOG(SSH_LOG_WARNING, "NULL userdata passed to callback"); + return -1; } - } + sftp = *sftpp; + + decode_len = sftp_decode_channel_data_to_packet(sftp, data, len); + if (decode_len == -1) + return -1; + + msg = sftp_get_client_message_from_packet(sftp); + rc = process_client_message(msg); + sftp_client_message_free(msg); + if (rc != SSH_OK) + SSH_LOG(SSH_LOG_PROTOCOL, "process sftp failed!"); + + return decode_len; } +#else +/* Not available on Windows for now */ +int +sftp_channel_default_data_callback(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(void *data), + UNUSED_PARAM(uint32_t len), + UNUSED_PARAM(int is_stderr), + UNUSED_PARAM(void *userdata)) +{ + return -1; +} + +int +sftp_channel_default_subsystem_request(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *subsystem), + UNUSED_PARAM(void *userdata)) +{ + return SSH_ERROR; +} +#endif diff --git a/src/socket.c b/src/socket.c index b3594311..9dc4cbdd 100644 --- a/src/socket.c +++ b/src/socket.c @@ -28,17 +28,15 @@ #ifdef _WIN32 #include <winsock2.h> #include <ws2tcpip.h> -#if _MSC_VER >= 1400 -#include <io.h> -#undef open -#define open _open -#undef close -#define close _close -#undef read -#define read _read -#undef write -#define write _write -#endif /* _MSC_VER */ +#ifndef UNIX_PATH_MAX + /* Inlining the key portions of afunix.h in Windows 10 SDK; + * that header isn't available in the mingw environment. */ +#define UNIX_PATH_MAX 108 +struct sockaddr_un { + ADDRESS_FAMILY sun_family; + char sun_path[UNIX_PATH_MAX]; +}; +#endif #else /* _WIN32 */ #include <fcntl.h> #include <sys/types.h> @@ -56,8 +54,6 @@ #include "libssh/session.h" /** - * @internal - * * @defgroup libssh_socket The SSH socket functions. * @ingroup libssh * @@ -214,6 +210,15 @@ void ssh_socket_set_callbacks(ssh_socket s, ssh_socket_callbacks callbacks) s->callbacks = callbacks; } +void ssh_socket_set_connected(ssh_socket s, struct ssh_poll_handle_struct *p) +{ + s->state = SSH_SOCKET_CONNECTED; + /* POLLOUT is the event to wait for in a nonblocking connect */ + if (p != NULL) { + ssh_poll_set_events(p, POLLIN | POLLOUT); + } +} + /** * @brief SSH poll callback. This callback will be used when an event * caught on the socket. @@ -221,7 +226,7 @@ void ssh_socket_set_callbacks(ssh_socket s, ssh_socket_callbacks callbacks) * @param p Poll object this callback belongs to. * @param fd The raw socket. * @param revents The current poll events on the socket. - * @param userdata Userdata to be passed to the callback function, + * @param v_s Userdata to be passed to the callback function, * in this case the socket object. * * @return 0 on success, < 0 when the poll object has been removed @@ -233,8 +238,8 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, void *v_s) { ssh_socket s = (ssh_socket)v_s; - char buffer[MAX_BUF_SIZE]; - ssize_t nread; + void *buffer = NULL; + ssize_t nread = 0; int rc; int err = 0; socklen_t errlen = sizeof(err); @@ -243,7 +248,8 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, if (!ssh_socket_is_open(s)) { return -1; } - SSH_LOG(SSH_LOG_TRACE, "Poll callback on socket %d (%s%s%s), out buffer %d",fd, + SSH_LOG(SSH_LOG_TRACE, + "Poll callback on socket %d (%s%s%s), out buffer %" PRIu32, fd, (revents & POLLIN) ? "POLLIN ":"", (revents & POLLOUT) ? "POLLOUT ":"", (revents & POLLERR) ? "POLLERR":"", @@ -275,8 +281,12 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, } if ((revents & POLLIN) && s->state == SSH_SOCKET_CONNECTED) { s->read_wontblock = 1; - nread = ssh_socket_unbuffered_read(s, buffer, sizeof(buffer)); + buffer = ssh_buffer_allocate(s->in_buffer, MAX_BUF_SIZE); + if (buffer) { + nread = ssh_socket_unbuffered_read(s, buffer, MAX_BUF_SIZE); + } if (nread < 0) { + ssh_buffer_pass_bytes_end(s->in_buffer, MAX_BUF_SIZE); if (p != NULL) { ssh_poll_remove_events(p, POLLIN); } @@ -288,6 +298,10 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, } return -2; } + + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(s->in_buffer, MAX_BUF_SIZE - nread); + if (nread == 0) { if (p != NULL) { ssh_poll_remove_events(p, POLLIN); @@ -304,18 +318,15 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, s->session->socket_counter->in_bytes += nread; } - /* Bufferize the data and then call the callback */ - rc = ssh_buffer_add_data(s->in_buffer, buffer, nread); - if (rc < 0) { - return -1; - } + /* Call the callback */ if (s->callbacks != NULL && s->callbacks->data != NULL) { + size_t processed; do { - nread = s->callbacks->data(ssh_buffer_get(s->in_buffer), - ssh_buffer_get_len(s->in_buffer), - s->callbacks->userdata); - ssh_buffer_pass_bytes(s->in_buffer, nread); - } while ((nread > 0) && (s->state == SSH_SOCKET_CONNECTED)); + processed = s->callbacks->data(ssh_buffer_get(s->in_buffer), + ssh_buffer_get_len(s->in_buffer), + s->callbacks->userdata); + ssh_buffer_pass_bytes(s->in_buffer, processed); + } while ((processed > 0) && (s->state == SSH_SOCKET_CONNECTED)); /* p may have been freed, so don't use it * anymore in this function */ @@ -332,10 +343,7 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, /* First, POLLOUT is a sign we may be connected */ if (s->state == SSH_SOCKET_CONNECTING) { SSH_LOG(SSH_LOG_PACKET, "Received POLLOUT in connecting state"); - s->state = SSH_SOCKET_CONNECTED; - if (p != NULL) { - ssh_poll_set_events(p, POLLOUT | POLLIN); - } + ssh_socket_set_connected(s, p); rc = ssh_socket_set_blocking(ssh_socket_get_fd(s)); if (rc < 0) { @@ -363,7 +371,7 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, ssh_socket_nonblocking_flush(s); } else if (s->callbacks != NULL && s->callbacks->controlflow != NULL) { /* Otherwise advertise the upper level that write can be done */ - SSH_LOG(SSH_LOG_TRACE,"sending control flow event"); + SSH_LOG(SSH_LOG_TRACE, "sending control flow event"); s->callbacks->controlflow(SSH_SOCKET_FLOW_WRITEWONTBLOCK, s->callbacks->userdata); } @@ -388,7 +396,7 @@ ssh_poll_handle ssh_socket_get_poll_handle(ssh_socket s) if (s->poll_handle) { return s->poll_handle; } - s->poll_handle = ssh_poll_new(s->fd,0,ssh_socket_pollcallback,s); + s->poll_handle = ssh_poll_new(s->fd, 0, ssh_socket_pollcallback, s); return s->poll_handle; } @@ -406,10 +414,10 @@ void ssh_socket_free(ssh_socket s) SAFE_FREE(s); } -#ifndef _WIN32 int ssh_socket_unix(ssh_socket s, const char *path) { struct sockaddr_un sunaddr; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; socket_t fd; sunaddr.sun_family = AF_UNIX; snprintf(sunaddr.sun_path, sizeof(sunaddr.sun_path), "%s", path); @@ -418,28 +426,30 @@ int ssh_socket_unix(ssh_socket s, const char *path) if (fd == SSH_INVALID_SOCKET) { ssh_set_error(s->session, SSH_FATAL, "Error from socket(AF_UNIX, SOCK_STREAM, 0): %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return -1; } +#ifndef _WIN32 if (fcntl(fd, F_SETFD, 1) == -1) { ssh_set_error(s->session, SSH_FATAL, "Error from fcntl(fd, F_SETFD, 1): %s", - strerror(errno)); - close(fd); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + CLOSE_SOCKET(fd); return -1; } +#endif if (connect(fd, (struct sockaddr *) &sunaddr, sizeof(sunaddr)) < 0) { - ssh_set_error(s->session, SSH_FATAL, "Error from connect(): %s", - strerror(errno)); - close(fd); + ssh_set_error(s->session, SSH_FATAL, "Error from connect(%s): %s", + path, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + CLOSE_SOCKET(fd); return -1; } ssh_socket_set_fd(s,fd); return 0; } -#endif /** \internal * \brief closes a socket @@ -473,12 +483,14 @@ void ssh_socket_close(ssh_socket s) kill(pid, SIGTERM); while (waitpid(pid, &status, 0) == -1) { if (errno != EINTR) { - SSH_LOG(SSH_LOG_WARN, "waitpid failed: %s", strerror(errno)); + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + SSH_LOG(SSH_LOG_TRACE, "waitpid failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return; } } if (!WIFEXITED(status)) { - SSH_LOG(SSH_LOG_WARN, "Proxy command exitted abnormally"); + SSH_LOG(SSH_LOG_TRACE, "Proxy command exited abnormally"); return; } SSH_LOG(SSH_LOG_TRACE, "Proxy command returned %d", WEXITSTATUS(status)); @@ -491,22 +503,28 @@ void ssh_socket_close(ssh_socket s) * @brief sets the file descriptor of the socket. * @param[out] s ssh_socket to update * @param[in] fd file descriptor to set - * @warning this function updates boths the input and output + * @warning this function updates both the input and output * file descriptors */ void ssh_socket_set_fd(ssh_socket s, socket_t fd) { + ssh_poll_handle h = NULL; + s->fd = fd; if (s->poll_handle) { ssh_poll_set_fd(s->poll_handle,fd); } else { s->state = SSH_SOCKET_CONNECTING; + h = ssh_socket_get_poll_handle(s); + if (h == NULL) { + return; + } /* POLLOUT is the event to wait for in a nonblocking connect */ - ssh_poll_set_events(ssh_socket_get_poll_handle(s), POLLOUT); + ssh_poll_set_events(h, POLLOUT); #ifdef _WIN32 - ssh_poll_add_events(ssh_socket_get_poll_handle(s), POLLWRNORM); + ssh_poll_add_events(h, POLLWRNORM); #endif } } @@ -540,9 +558,9 @@ static ssize_t ssh_socket_unbuffered_read(ssh_socket s, return -1; } if (s->fd_is_socket) { - rc = recv(s->fd,buffer, len, 0); + rc = recv(s->fd, buffer, len, 0); } else { - rc = read(s->fd,buffer, len); + rc = read(s->fd, buffer, len); } #ifdef _WIN32 s->last_errno = WSAGetLastError(); @@ -553,6 +571,8 @@ static ssize_t ssh_socket_unbuffered_read(ssh_socket s, if (rc < 0) { s->data_except = 1; + } else { + SSH_LOG(SSH_LOG_TRACE, "read %zd", rc); } return rc; @@ -590,12 +610,13 @@ static ssize_t ssh_socket_unbuffered_write(ssh_socket s, /* Reactive the POLLOUT detector in the poll multiplexer system */ if (s->poll_handle) { SSH_LOG(SSH_LOG_PACKET, "Enabling POLLOUT for socket"); - ssh_poll_set_events(s->poll_handle,ssh_poll_get_events(s->poll_handle) | POLLOUT); + ssh_poll_add_events(s->poll_handle, POLLOUT); } if (w < 0) { s->data_except = 1; } + SSH_LOG(SSH_LOG_TRACE, "wrote %zd", w); return w; } @@ -634,7 +655,7 @@ void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd) * \returns SSH_OK, or SSH_ERROR * \warning has no effect on socket before a flush */ -int ssh_socket_write(ssh_socket s, const void *buffer, int len) +int ssh_socket_write(ssh_socket s, const void *buffer, uint32_t len) { if (len > 0) { if (ssh_buffer_add_data(s->out_buffer, buffer, len) < 0) { @@ -664,11 +685,12 @@ int ssh_socket_nonblocking_flush(ssh_socket s) s->last_errno, s->callbacks->userdata); } else { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; ssh_set_error(session, SSH_FATAL, "Writing packet: error on socket (or connection " "closed): %s", - strerror(s->last_errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); } return SSH_ERROR; @@ -697,11 +719,12 @@ int ssh_socket_nonblocking_flush(ssh_socket s) s->last_errno, s->callbacks->userdata); } else { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; ssh_set_error(session, SSH_FATAL, "Writing packet: error on socket (or connection " "closed): %s", - strerror(s->last_errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); } return SSH_ERROR; @@ -716,6 +739,8 @@ int ssh_socket_nonblocking_flush(ssh_socket s) /* Is there some data pending? */ len = ssh_buffer_get_len(s->out_buffer); if (s->poll_handle && len > 0) { + SSH_LOG(SSH_LOG_TRACE, + "did not send all the data, queuing pollout event"); /* force the poll system to catch pollout events */ ssh_poll_add_events(s->poll_handle, POLLOUT); @@ -825,7 +850,7 @@ int ssh_socket_set_blocking(socket_t fd) /** * @internal * @brief Launches a socket connection - * If a the socket connected callback has been defined and + * If the socket connected callback has been defined and * a poll object exists, this call will be non blocking. * @param s socket to connect. * @param host hostname or ip address to connect to. @@ -833,8 +858,6 @@ int ssh_socket_set_blocking(socket_t fd) * @param bind_addr address to bind to, or NULL for default. * @returns SSH_OK socket is being connected. * @returns SSH_ERROR error while connecting to remote host. - * @bug It only tries connecting to one of the available AI's - * which is problematic for hosts having DNS fail-over. */ int ssh_socket_connect(ssh_socket s, const char *host, @@ -842,19 +865,19 @@ int ssh_socket_connect(ssh_socket s, const char *bind_addr) { socket_t fd; - + if (s->state != SSH_SOCKET_NONE) { ssh_set_error(s->session, SSH_FATAL, "ssh_socket_connect called on socket not unconnected"); return SSH_ERROR; } fd = ssh_connect_host_nonblocking(s->session, host, bind_addr, port); - SSH_LOG(SSH_LOG_PROTOCOL, "Nonblocking connection socket: %d", fd); + SSH_LOG(SSH_LOG_DEBUG, "Nonblocking connection socket: %d", fd); if (fd == SSH_INVALID_SOCKET) { return SSH_ERROR; } ssh_socket_set_fd(s,fd); - + return SSH_OK; } @@ -872,20 +895,29 @@ ssh_execute_command(const char *command, socket_t in, socket_t out) const char *shell = NULL; const char *args[] = {NULL/*shell*/, "-c", command, NULL}; int devnull; + int rc; /* Prepare /dev/null socket for the stderr redirection */ devnull = open("/dev/null", O_WRONLY); if (devnull == -1) { - SSH_LOG(SSH_LOG_WARNING, "Failed to open /dev/null"); + SSH_LOG(SSH_LOG_TRACE, "Failed to open /dev/null"); exit(1); } - /* By default, use the current users shell */ + /* + * By default, use the current users shell. This could fail with some + * shells like zsh or dash ... + */ shell = getenv("SHELL"); if (shell == NULL || shell[0] == '\0') { - /* Fall back to bash. There are issues with dash or - * whatever people tend to link to /bin/sh */ - shell = "/bin/bash"; + /* Fall back to the /bin/sh only if the bash is not available. But there are + * issues with dash or whatever people tend to link to /bin/sh */ + rc = access("/bin/bash", 0); + if (rc != 0) { + shell = "/bin/sh"; + } else { + shell = "/bin/bash"; + } } args[0] = shell; @@ -896,7 +928,13 @@ ssh_execute_command(const char *command, socket_t in, socket_t out) dup2(devnull, STDERR_FILENO); close(in); close(out); - execv(args[0], (char * const *)args); + rc = execv(args[0], (char * const *)args); + if (rc < 0) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + SSH_LOG(SSH_LOG_WARN, "Failed to execute command %s: %s", + command, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } exit(1); } @@ -914,6 +952,7 @@ int ssh_socket_connect_proxycommand(ssh_socket s, const char *command) { socket_t pair[2]; + ssh_poll_handle h = NULL; int pid; int rc; @@ -926,7 +965,7 @@ ssh_socket_connect_proxycommand(ssh_socket s, const char *command) return SSH_ERROR; } - SSH_LOG(SSH_LOG_PROTOCOL, "Executing proxycommand '%s'", command); + SSH_LOG(SSH_LOG_DEBUG, "Executing proxycommand '%s'", command); pid = fork(); if (pid == 0) { ssh_execute_command(command, pair[0], pair[0]); @@ -934,12 +973,14 @@ ssh_socket_connect_proxycommand(ssh_socket s, const char *command) } s->proxy_pid = pid; close(pair[0]); - SSH_LOG(SSH_LOG_PROTOCOL, "ProxyCommand connection pipe: [%d,%d]",pair[0],pair[1]); + SSH_LOG(SSH_LOG_DEBUG, "ProxyCommand connection pipe: [%d,%d]",pair[0],pair[1]); ssh_socket_set_fd(s, pair[1]); - s->state=SSH_SOCKET_CONNECTED; - s->fd_is_socket=0; - /* POLLOUT is the event to wait for in a nonblocking connect */ - ssh_poll_set_events(ssh_socket_get_poll_handle(s), POLLIN | POLLOUT); + s->fd_is_socket = 0; + h = ssh_socket_get_poll_handle(s); + if (h == NULL) { + return SSH_ERROR; + } + ssh_socket_set_connected(s, h); if (s->callbacks && s->callbacks->connected) { s->callbacks->connected(SSH_SOCKET_CONNECTED_OK, 0, s->callbacks->userdata); } diff --git a/src/string.c b/src/string.c index acd3cf48..44403487 100644 --- a/src/string.c +++ b/src/string.c @@ -103,7 +103,7 @@ int ssh_string_fill(struct ssh_string_struct *s, const void *data, size_t len) { * @return The newly allocated string, NULL on error with errno * set. * - * @note The nul byte is not copied nor counted in the ouput string. + * @note The null byte is not copied nor counted in the output string. */ struct ssh_string_struct *ssh_string_from_char(const char *what) { struct ssh_string_struct *ptr; @@ -129,7 +129,7 @@ struct ssh_string_struct *ssh_string_from_char(const char *what) { /** * @brief Return the size of a SSH string. * - * @param[in] s The the input SSH string. + * @param[in] s The input SSH string. * * @return The size of the content of the string, 0 on error. */ @@ -149,7 +149,7 @@ size_t ssh_string_len(struct ssh_string_struct *s) { } /** - * @brief Get the the string as a C nul-terminated string. + * @brief Get the string as a C null-terminated string. * * This is only available as long as the SSH string exists. * @@ -168,7 +168,7 @@ const char *ssh_string_get_char(struct ssh_string_struct *s) } /** - * @brief Convert a SSH string to a C nul-terminated string. + * @brief Convert a SSH string to a C null-terminated string. * * @param[in] s The SSH input string. * diff --git a/src/threads.c b/src/threads.c index 792b976d..7660b972 100644 --- a/src/threads.c +++ b/src/threads.c @@ -20,7 +20,7 @@ */ /** - * @defgroup libssh_threads The SSH threading functions. + * @defgroup libssh_threads The SSH threading functions * @ingroup libssh * * Threading with libssh diff --git a/src/threads/libcrypto.c b/src/threads/libcrypto.c index 5786948b..18951b6a 100644 --- a/src/threads/libcrypto.c +++ b/src/threads/libcrypto.c @@ -24,8 +24,6 @@ #include "libssh/threads.h" #include <libssh/callbacks.h> -#if (OPENSSL_VERSION_NUMBER >= 0x10100000) - int crypto_thread_init(struct ssh_threads_callbacks_struct *cb) { (void) cb; @@ -36,98 +34,3 @@ void crypto_thread_finalize(void) { return; } - -#else - -static struct ssh_threads_callbacks_struct *user_callbacks = NULL; - -static void **libcrypto_mutexes; - -void libcrypto_lock_callback(int mode, int i, const char *file, int line); - -void libcrypto_lock_callback(int mode, int i, const char *file, int line) -{ - (void)file; - (void)line; - - if (mode & CRYPTO_LOCK) { - user_callbacks->mutex_lock(&libcrypto_mutexes[i]); - } else { - user_callbacks->mutex_unlock(&libcrypto_mutexes[i]); - } -} - -#ifdef HAVE_OPENSSL_CRYPTO_THREADID_SET_CALLBACK -static void libcrypto_THREADID_callback(CRYPTO_THREADID *id) -{ - unsigned long thread_id = (*user_callbacks->thread_id)(); - - CRYPTO_THREADID_set_numeric(id, thread_id); -} -#endif /* HAVE_OPENSSL_CRYPTO_THREADID_SET_CALLBACK */ - -int crypto_thread_init(struct ssh_threads_callbacks_struct *cb) -{ - int n = CRYPTO_num_locks(); - int cmp; - int i; - - if (cb == NULL) { - return SSH_OK; - } - - if (user_callbacks != NULL) { - crypto_thread_finalize(); - } - - user_callbacks = cb; - - cmp = strcmp(user_callbacks->type, "threads_noop"); - if (cmp == 0) { - return SSH_OK; - } - - libcrypto_mutexes = calloc(n, sizeof(void *)); - if (libcrypto_mutexes == NULL) { - return SSH_ERROR; - } - - for (i = 0; i < n; ++i){ - user_callbacks->mutex_init(&libcrypto_mutexes[i]); - } - -#ifdef HAVE_OPENSSL_CRYPTO_THREADID_SET_CALLBACK - CRYPTO_THREADID_set_callback(libcrypto_THREADID_callback); -#else - CRYPTO_set_id_callback(user_callbacks->thread_id); -#endif - - CRYPTO_set_locking_callback(libcrypto_lock_callback); - - return SSH_OK; -} - -void crypto_thread_finalize(void) -{ - int n = CRYPTO_num_locks(); - int i; - - if (libcrypto_mutexes == NULL) { - return; - } - -#ifdef HAVE_OPENSSL_CRYPTO_THREADID_SET_CALLBACK - CRYPTO_THREADID_set_callback(NULL); -#else - CRYPTO_set_id_callback(NULL); -#endif - - CRYPTO_set_locking_callback(NULL); - - for (i = 0; i < n; ++i) { - user_callbacks->mutex_destroy(&libcrypto_mutexes[i]); - } - SAFE_FREE(libcrypto_mutexes); -} - -#endif diff --git a/src/threads/winlocks.c b/src/threads/winlocks.c index a1799531..da600418 100644 --- a/src/threads/winlocks.c +++ b/src/threads/winlocks.c @@ -26,6 +26,7 @@ #include <windows.h> #include <winbase.h> #include <errno.h> +#include <stdlib.h> static int ssh_winlock_mutex_init (void **priv) { diff --git a/src/token.c b/src/token.c index 0924d3bd..6ce2b64c 100644 --- a/src/token.c +++ b/src/token.c @@ -87,7 +87,7 @@ struct ssh_tokens_st *ssh_tokenize(const char *chain, char separator) return NULL; } - tokens->buffer= strdup(chain); + tokens->buffer = strdup(chain); if (tokens->buffer == NULL) { goto error; } @@ -376,6 +376,7 @@ char *ssh_append_without_duplicates(const char *list, { size_t concat_len = 0; char *ret = NULL, *concat = NULL; + int rc = 0; if (list != NULL) { concat_len = strlen(list); @@ -396,12 +397,144 @@ char *ssh_append_without_duplicates(const char *list, return NULL; } + rc = snprintf(concat, concat_len, "%s%s%s", + list == NULL ? "" : list, + list == NULL ? "" : ",", + appended_list == NULL ? "" : appended_list); + if (rc < 0) { + SAFE_FREE(concat); + return NULL; + } + + ret = ssh_remove_duplicates(concat); + + SAFE_FREE(concat); + + return ret; +} + +/** + * @internal + * + * @brief Given two strings containing lists of tokens, return a newly + * allocated string containing the elements of the first list without the + * elements of the second list. The order of the elements will be preserved. + * + * @param[in] list The first list + * @param[in] remove_list The list to be removed + * + * @return A newly allocated copy list containing elements of the + * list without the elements of remove_list; NULL in case of error. + */ +char *ssh_remove_all_matching(const char *list, + const char *remove_list) +{ + struct ssh_tokens_st *l_tok = NULL, *r_tok = NULL; + int i, j, cmp; + char *ret = NULL; + size_t len, pos = 0; + bool exclude; + + if (list == NULL) { + return NULL; + } + if (remove_list == NULL) { + return strdup (list); + } + + l_tok = ssh_tokenize(list, ','); + if (l_tok == NULL) { + goto out; + } + + r_tok = ssh_tokenize(remove_list, ','); + if (r_tok == NULL) { + goto out; + } + + ret = calloc(1, strlen(list) + 1); + if (ret == NULL) { + goto out; + } + + for (i = 0; l_tok->tokens[i]; i++) { + exclude = false; + for (j = 0; r_tok->tokens[j]; j++) { + cmp = strcmp(l_tok->tokens[i], r_tok->tokens[j]); + if (cmp == 0) { + exclude = true; + break; + } + } + if (exclude == false) { + if (pos != 0) { + ret[pos] = ','; + pos++; + } + + len = strlen(l_tok->tokens[i]); + memcpy(&ret[pos], l_tok->tokens[i], len); + pos += len; + } + } + + if (ret[0] == '\0') { + SAFE_FREE(ret); + } + +out: + ssh_tokens_free(l_tok); + ssh_tokens_free(r_tok); + return ret; +} + +/** + * @internal + * + * @brief Given two strings containing lists of tokens, return a newly + * allocated string containing all the elements of the first list prefixed at + * the beginning of the second list, without duplicates. + * + * @param[in] list The first list + * @param[in] prefixed_list The list to use as a prefix + * + * @return A newly allocated list containing all the elements + * of the list prefixed with the elements of the prefixed_list without + * duplicates; NULL in case of error. + */ +char *ssh_prefix_without_duplicates(const char *list, + const char *prefixed_list) +{ + size_t concat_len = 0; + char *ret = NULL, *concat = NULL; + int rc = 0; + if (list != NULL) { - strcpy(concat, list); - strncat(concat, ",", concat_len - strlen(concat) - 1); + concat_len = strlen(list); } - if (appended_list != NULL) { - strncat(concat, appended_list, concat_len - strlen(concat) - 1); + + if (prefixed_list != NULL) { + concat_len += strlen(prefixed_list); + } + + if (concat_len == 0) { + return NULL; + } + + /* Add room for ending '\0' and for middle ',' */ + concat_len += 2; + concat = calloc(concat_len, 1); + if (concat == NULL) { + return NULL; + } + + rc = snprintf(concat, concat_len, "%s%s%s", + prefixed_list == NULL ? "" : prefixed_list, + prefixed_list == NULL ? "" : ",", + list == NULL ? "" : list); + if (rc < 0) { + SAFE_FREE(concat); + return NULL; } ret = ssh_remove_duplicates(concat); diff --git a/src/ttyopts.c b/src/ttyopts.c new file mode 100644 index 00000000..f6557884 --- /dev/null +++ b/src/ttyopts.c @@ -0,0 +1,449 @@ +/* + * ttyopts.c - encoding of TTY modes. + * + * This file is part of the SSH Library + * + * Copyright (c) 2023 by Utimaco TS GmbH <oss_committee@utimaco.com> + * Author: Daniel Evers <daniel.evers@utimaco.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" + +#include <stdint.h> +#include <stdio.h> + +#include <libssh/priv.h> +#include <string.h> + +#ifdef HAVE_TERMIOS_H +#include <termios.h> +#endif + +/** Terminal mode opcodes */ +enum { + TTY_OP_END = 0, + TTY_OP_VINTR = 1, + TTY_OP_VQUIT = 2, + TTY_OP_VERASE = 3, + TTY_OP_VKILL = 4, + TTY_OP_VEOF = 5, + TTY_OP_VEOL = 6, + TTY_OP_VEOL2 = 7, + TTY_OP_VSTART = 8, + TTY_OP_VSTOP = 9, + TTY_OP_VSUSP = 10, + TTY_OP_VDSUSP = 11, + TTY_OP_VREPRINT = 12, + TTY_OP_VWERASE = 13, + TTY_OP_VLNEXT = 14, + TTY_OP_VFLUSH = 15, + TTY_OP_VSWTC = 16, + TTY_OP_VSTATUS = 17, + TTY_OP_VDISCARD = 18, + TTY_OP_IGNPAR = 30, + TTY_OP_PARMRK = 31, + TTY_OP_INPCK = 32, + TTY_OP_ISTRIP = 33, + TTY_OP_INLCR = 34, + TTY_OP_IGNCR = 35, + TTY_OP_ICRNL = 36, + TTY_OP_IUCLC = 37, + TTY_OP_IXON = 38, + TTY_OP_IXANY = 39, + TTY_OP_IXOFF = 40, + TTY_OP_IMAXBEL = 41, + TTY_OP_IUTF8 = 42, + TTY_OP_ISIG = 50, + TTY_OP_ICANON = 51, + TTY_OP_XCASE = 52, + TTY_OP_ECHO = 53, + TTY_OP_ECHOE = 54, + TTY_OP_ECHOK = 55, + TTY_OP_ECHONL = 56, + TTY_OP_NOFLSH = 57, + TTY_OP_TOSTOP = 58, + TTY_OP_IEXTEN = 59, + TTY_OP_ECHOCTL = 60, + TTY_OP_ECHOKE = 61, + TTY_OP_PENDIN = 62, + TTY_OP_OPOST = 70, + TTY_OP_OLCUC = 71, + TTY_OP_ONLCR = 72, + TTY_OP_OCRNL = 73, + TTY_OP_ONOCR = 74, + TTY_OP_ONLRET = 75, + TTY_OP_CS7 = 90, + TTY_OP_CS8 = 91, + TTY_OP_PARENB = 92, + TTY_OP_PARODD = 93, + TTY_OP_ISPEED = 128, + TTY_OP_OSPEED = 129, +}; + +/** + * Encodes a single SSH terminal mode option into the buffer. + * + * @param[in] attr The mode's opcode value. + * + * @param[in] value The mode's value. + * + * @param[out] buf Destination buffer to encode into. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes written to the buffer on success, -1 on + * error. + */ +static int +encode_termios_opt(unsigned char opcode, + uint32_t value, + unsigned char *buf, + size_t buflen) +{ + int offset = 0; + + /* always need 5 bytes */ + if (buflen < 5) { + return -1; + } + + /* 1 byte opcode */ + buf[offset++] = opcode; + + /* 4 bytes value (big endian) */ + value = htonl(value); + memcpy(buf + offset, &value, sizeof(value)); + offset += sizeof(value); + + return offset; +} + +#ifdef HAVE_TERMIOS_H +/** Converts a baudrate constant (Bxxxx) to a numeric value. */ +static int +baud2speed(int baudrate) +{ + switch (baudrate) { + default: + case B0: + return 0; + case B50: + return 50; + case B75: + return 75; + case B110: + return 110; + case B134: + return 134; + case B150: + return 150; + case B200: + return 200; + case B300: + return 300; + case B600: + return 600; + case B1200: + return 1200; + case B1800: + return 1800; + case B2400: + return 2400; + case B4800: + return 4800; + case B9600: + return 9600; + case B19200: + return 19200; + case B38400: + return 38400; + case B57600: + return 57600; + case B115200: + return 115200; + case B230400: + return 230400; + } +} + +/** + * Encodes all terminal options from the given \c termios structure + * into the buffer. + * + * @param[in] attr The terminal options to encode. + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +static int +encode_termios_opts(struct termios *attr, unsigned char *buf, size_t buflen) +{ + unsigned int offset = 0; + int rc; + +#define SSH_ENCODE_OPT(code, value) \ + rc = encode_termios_opt(code, value, buf + offset, buflen - offset); \ + if (rc < 0) { \ + return rc; \ + } else { \ + offset += rc; \ + } + +#define SSH_ENCODE_INPUT_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_iflag & opt) ? 1 : 0) + SSH_ENCODE_INPUT_OPT(IGNPAR) + SSH_ENCODE_INPUT_OPT(PARMRK) + SSH_ENCODE_INPUT_OPT(INPCK) + SSH_ENCODE_INPUT_OPT(ISTRIP) + SSH_ENCODE_INPUT_OPT(INLCR) + SSH_ENCODE_INPUT_OPT(IGNCR) + SSH_ENCODE_INPUT_OPT(ICRNL) +#ifdef IUCLC + SSH_ENCODE_INPUT_OPT(IUCLC) +#endif + SSH_ENCODE_INPUT_OPT(IXON) + SSH_ENCODE_INPUT_OPT(IXANY) + SSH_ENCODE_INPUT_OPT(IXOFF) + SSH_ENCODE_INPUT_OPT(IMAXBEL) +#ifdef IUTF8 + SSH_ENCODE_INPUT_OPT(IUTF8) +#endif +#undef SSH_ENCODE_INPUT_OPT + +#define SSH_ENCODE_OUTPUT_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_oflag & opt) ? 1 : 0) + SSH_ENCODE_OUTPUT_OPT(OPOST) +#ifdef OLCUC + SSH_ENCODE_OUTPUT_OPT(OLCUC) +#endif + SSH_ENCODE_OUTPUT_OPT(ONLCR) + SSH_ENCODE_OUTPUT_OPT(OCRNL) + SSH_ENCODE_OUTPUT_OPT(ONOCR) + SSH_ENCODE_OUTPUT_OPT(ONLRET) +#undef SSH_ENCODE_OUTPUT_OPT + +#define SSH_ENCODE_CONTROL_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_cflag & opt) ? 1 : 0) + SSH_ENCODE_CONTROL_OPT(CS7) + SSH_ENCODE_CONTROL_OPT(CS8) + SSH_ENCODE_CONTROL_OPT(PARENB) + SSH_ENCODE_CONTROL_OPT(PARODD) +#undef SSH_ENCODE_CONTROL_OPT + +#define SSH_ENCODE_LOCAL_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_lflag & opt) ? 1 : 0) + SSH_ENCODE_LOCAL_OPT(ISIG) + SSH_ENCODE_LOCAL_OPT(ICANON) +#ifdef XCASE + SSH_ENCODE_LOCAL_OPT(XCASE) +#endif + SSH_ENCODE_LOCAL_OPT(ECHO) + SSH_ENCODE_LOCAL_OPT(ECHOE) + SSH_ENCODE_LOCAL_OPT(ECHOK) + SSH_ENCODE_LOCAL_OPT(ECHONL) + SSH_ENCODE_LOCAL_OPT(NOFLSH) + SSH_ENCODE_LOCAL_OPT(TOSTOP) + SSH_ENCODE_LOCAL_OPT(IEXTEN) + SSH_ENCODE_LOCAL_OPT(ECHOCTL) + SSH_ENCODE_LOCAL_OPT(ECHOKE) + SSH_ENCODE_LOCAL_OPT(PENDIN) +#undef SSH_ENCODE_LOCAL_OPT + +#define SSH_ENCODE_CC_OPT(opt) SSH_ENCODE_OPT(TTY_OP_##opt, attr->c_cc[opt]) + SSH_ENCODE_CC_OPT(VINTR) + SSH_ENCODE_CC_OPT(VQUIT) + SSH_ENCODE_CC_OPT(VERASE) + SSH_ENCODE_CC_OPT(VKILL) + SSH_ENCODE_CC_OPT(VEOF) + SSH_ENCODE_CC_OPT(VEOL) + SSH_ENCODE_CC_OPT(VEOL2) + SSH_ENCODE_CC_OPT(VSTART) + SSH_ENCODE_CC_OPT(VSTOP) + SSH_ENCODE_CC_OPT(VSUSP) +#ifdef VDSUSP + SSH_ENCODE_CC_OPT(VDSUSP) +#endif + SSH_ENCODE_CC_OPT(VREPRINT) + SSH_ENCODE_CC_OPT(VWERASE) + SSH_ENCODE_CC_OPT(VLNEXT) +#ifdef VFLUSH + SSH_ENCODE_CC_OPT(VFLUSH) +#endif +#ifdef VSWTC + SSH_ENCODE_CC_OPT(VSWTC) +#endif +#ifdef VSTATUS + SSH_ENCODE_CC_OPT(VSTATUS) +#endif + SSH_ENCODE_CC_OPT(VDISCARD) +#undef SSH_ENCODE_CC_OPT + + SSH_ENCODE_OPT(TTY_OP_ISPEED, baud2speed(cfgetispeed(attr))) + SSH_ENCODE_OPT(TTY_OP_OSPEED, baud2speed(cfgetospeed(attr))) +#undef SSH_ENCODE_OPT + + /* end of options */ + if (buflen > offset) { + buf[offset++] = TTY_OP_END; + } else { + return -1; + } + + return (int)offset; +} +#endif + +/** + * Encodes a set of default options to ensure "sane" PTY behavior. + * This function intentionally doesn't use the \c termios structure + * to allow it to work on Windows as well. + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +static int +encode_default_opts(unsigned char *buf, size_t buflen) +{ + unsigned int offset = 0; + int rc; + +#define SSH_ENCODE_OPT(code, value) \ + rc = encode_termios_opt(code, value, buf + offset, buflen - offset); \ + if (rc < 0) { \ + return rc; \ + } else { \ + offset += rc; \ + } + + SSH_ENCODE_OPT(TTY_OP_VINTR, 003) + SSH_ENCODE_OPT(TTY_OP_VQUIT, 034) + SSH_ENCODE_OPT(TTY_OP_VERASE, 0177) + SSH_ENCODE_OPT(TTY_OP_VKILL, 025) + SSH_ENCODE_OPT(TTY_OP_VEOF, 0) + SSH_ENCODE_OPT(TTY_OP_VEOL, 0) + SSH_ENCODE_OPT(TTY_OP_VEOL2, 0) + SSH_ENCODE_OPT(TTY_OP_VSTART, 021) + SSH_ENCODE_OPT(TTY_OP_VSTOP, 023) + SSH_ENCODE_OPT(TTY_OP_VSUSP, 032) + SSH_ENCODE_OPT(TTY_OP_VDSUSP, 031) + SSH_ENCODE_OPT(TTY_OP_VREPRINT, 022) + SSH_ENCODE_OPT(TTY_OP_VWERASE, 027) + SSH_ENCODE_OPT(TTY_OP_VLNEXT, 026) + SSH_ENCODE_OPT(TTY_OP_VDISCARD, 017) + SSH_ENCODE_OPT(TTY_OP_IGNPAR, 0) + SSH_ENCODE_OPT(TTY_OP_PARMRK, 0) + SSH_ENCODE_OPT(TTY_OP_INPCK, 0) + SSH_ENCODE_OPT(TTY_OP_ISTRIP, 0) + SSH_ENCODE_OPT(TTY_OP_INLCR, 0) + SSH_ENCODE_OPT(TTY_OP_IGNCR, 0) + SSH_ENCODE_OPT(TTY_OP_ICRNL, 0) + SSH_ENCODE_OPT(TTY_OP_IUCLC, 0) + SSH_ENCODE_OPT(TTY_OP_IXON, 1) + SSH_ENCODE_OPT(TTY_OP_IXANY, 0) + SSH_ENCODE_OPT(TTY_OP_IXOFF, 0) + SSH_ENCODE_OPT(TTY_OP_IMAXBEL, 0) + SSH_ENCODE_OPT(TTY_OP_IUTF8, 1) + SSH_ENCODE_OPT(TTY_OP_ISIG, 1) + SSH_ENCODE_OPT(TTY_OP_ICANON, 1) + SSH_ENCODE_OPT(TTY_OP_XCASE, 0) + SSH_ENCODE_OPT(TTY_OP_ECHO, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOE, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOK, 1) + SSH_ENCODE_OPT(TTY_OP_ECHONL, 0) + SSH_ENCODE_OPT(TTY_OP_NOFLSH, 0) + SSH_ENCODE_OPT(TTY_OP_TOSTOP, 0) + SSH_ENCODE_OPT(TTY_OP_IEXTEN, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOCTL, 0) + SSH_ENCODE_OPT(TTY_OP_ECHOKE, 1) + SSH_ENCODE_OPT(TTY_OP_PENDIN, 0) + SSH_ENCODE_OPT(TTY_OP_OPOST, 1) + SSH_ENCODE_OPT(TTY_OP_OLCUC, 0) + SSH_ENCODE_OPT(TTY_OP_ONLCR, 0) + SSH_ENCODE_OPT(TTY_OP_OCRNL, 0) + SSH_ENCODE_OPT(TTY_OP_ONOCR, 0) + SSH_ENCODE_OPT(TTY_OP_ONLRET, 0) + SSH_ENCODE_OPT(TTY_OP_CS7, 1) + SSH_ENCODE_OPT(TTY_OP_CS8, 1) + SSH_ENCODE_OPT(TTY_OP_PARENB, 0) + SSH_ENCODE_OPT(TTY_OP_PARODD, 0) + SSH_ENCODE_OPT(TTY_OP_ISPEED, 38400); + SSH_ENCODE_OPT(TTY_OP_OSPEED, 38400); + +#undef SSH_ENCODE_OPT + + /* end of options */ + if (buflen > offset) { + buf[offset++] = TTY_OP_END; + } else { + return -1; + } + + return (int)offset; +} + +/** + * @ingroup libssh_misc + * + * @brief Encode the current TTY options as SSH modes. + * + * Call this function to determine the settings of the process' TTY and + * encode them as SSH Terminal Modes according to RFC 4254 section 8. + * + * If STDIN isn't connected to a TTY, this function fills the buffer with + * "sane" default modes. + * + * The encoded modes can be passed to \c ssh_channel_request_pty_size_modes . + * + * @code + * unsigned char modes_buf[SSH_TTY_MODES_MAX_BUFSIZE]; + * encode_current_tty_opts(modes_buf, sizeof(modes_buf)); + * @endcode + * + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +int +encode_current_tty_opts(unsigned char *buf, size_t buflen) +{ +#ifdef HAVE_TERMIOS_H + struct termios attr; + ZERO_STRUCT(attr); + + if (isatty(STDIN_FILENO)) { + /* get local terminal attributes */ + if (tcgetattr(STDIN_FILENO, &attr) < 0) { + perror("tcgetattr"); + return -1; + } + return encode_termios_opts(&attr, buf, buflen); + } +#endif + + /* use "sane" default attributes */ + return encode_default_opts(buf, buflen); +} diff --git a/src/wrapper.c b/src/wrapper.c index d53a61a3..bf949ea9 100644 --- a/src/wrapper.c +++ b/src/wrapper.c @@ -1,5 +1,5 @@ /* - * wrapper.c - wrapper for crytpo functions + * wrapper.c - wrapper for crypto functions * * This file is part of the SSH Library * @@ -25,7 +25,7 @@ * Why a wrapper? * * Let's say you want to port libssh from libcrypto of openssl to libfoo - * you are going to spend hours to remove every references to SHA1_Update() + * you are going to spend hours removing every reference to SHA1_Update() * to libfoo_sha1_update after the work is finished, you're going to have * only this file to modify it's not needed to say that your modifications * are welcome. @@ -108,7 +108,7 @@ const char *ssh_hmac_type_to_string(enum ssh_hmac_e hmac_type, bool etm) } /* it allocates a new cipher structure based on its offset into the global table */ -static struct ssh_cipher_struct *cipher_new(int offset) { +static struct ssh_cipher_struct *cipher_new(uint8_t offset) { struct ssh_cipher_struct *cipher = NULL; cipher = malloc(sizeof(struct ssh_cipher_struct)); @@ -150,15 +150,16 @@ static void cipher_free(struct ssh_cipher_struct *cipher) { SAFE_FREE(cipher); } -struct ssh_crypto_struct *crypto_new(void) { - struct ssh_crypto_struct *crypto; +struct ssh_crypto_struct *crypto_new(void) +{ + struct ssh_crypto_struct *crypto; - crypto = malloc(sizeof(struct ssh_crypto_struct)); - if (crypto == NULL) { - return NULL; - } - ZERO_STRUCTP(crypto); - return crypto; + crypto = malloc(sizeof(struct ssh_crypto_struct)); + if (crypto == NULL) { + return NULL; + } + ZERO_STRUCTP(crypto); + return crypto; } void crypto_free(struct ssh_crypto_struct *crypto) @@ -176,17 +177,22 @@ void crypto_free(struct ssh_crypto_struct *crypto) #ifdef HAVE_ECDH SAFE_FREE(crypto->ecdh_client_pubkey); SAFE_FREE(crypto->ecdh_server_pubkey); - if(crypto->ecdh_privkey != NULL){ + if (crypto->ecdh_privkey != NULL) { #ifdef HAVE_OPENSSL_ECC +#if OPENSSL_VERSION_NUMBER < 0x30000000L EC_KEY_free(crypto->ecdh_privkey); +#else + EVP_PKEY_free(crypto->ecdh_privkey); +#endif /* OPENSSL_VERSION_NUMBER */ #elif defined HAVE_GCRYPT_ECC gcry_sexp_release(crypto->ecdh_privkey); #endif crypto->ecdh_privkey = NULL; } #endif + SAFE_FREE(crypto->dh_server_signature); if (crypto->session_id != NULL) { - explicit_bzero(crypto->session_id, crypto->digest_len); + explicit_bzero(crypto->session_id, crypto->session_id_len); SAFE_FREE(crypto->session_id); } if (crypto->secret_hash != NULL) { @@ -233,12 +239,35 @@ void crypto_free(struct ssh_crypto_struct *crypto) SAFE_FREE(crypto); } +static void +compression_enable(ssh_session session, + enum ssh_crypto_direction_e direction, + bool delayed) +{ + /* The delayed compression is turned on AFTER authentication. This means + * that we need to turn it on immediately in case of rekeying */ + if (delayed && !(session->flags & SSH_SESSION_FLAG_AUTHENTICATED)) { + if (direction == SSH_DIRECTION_IN) { + session->next_crypto->delayed_compress_in = 1; + } else { /* SSH_DIRECTION_OUT */ + session->next_crypto->delayed_compress_out = 1; + } + } else { + if (direction == SSH_DIRECTION_IN) { + session->next_crypto->do_compress_in = 1; + } else { /* SSH_DIRECTION_OUT */ + session->next_crypto->do_compress_out = 1; + } + } +} + static int crypt_set_algorithms2(ssh_session session) { const char *wanted = NULL; + const char *method = NULL; struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab(); struct ssh_hmac_struct *ssh_hmactab=ssh_get_hmactab(); - size_t i = 0; + uint8_t i = 0; int cmp; /* @@ -358,22 +387,29 @@ static int crypt_set_algorithms2(ssh_session session) session->next_crypto->in_hmac = ssh_hmactab[i].hmac_type; session->next_crypto->in_hmac_etm = ssh_hmactab[i].etm; - /* compression */ - cmp = strcmp(session->next_crypto->kex_methods[SSH_COMP_C_S], "zlib"); + /* compression: client */ + method = session->next_crypto->kex_methods[SSH_COMP_C_S]; + cmp = strcmp(method, "zlib"); if (cmp == 0) { - session->next_crypto->do_compress_out = 1; + SSH_LOG(SSH_LOG_PACKET, "enabling C->S compression"); + compression_enable(session, SSH_DIRECTION_OUT, false); } - cmp = strcmp(session->next_crypto->kex_methods[SSH_COMP_S_C], "zlib"); + cmp = strcmp(method, "zlib@openssh.com"); if (cmp == 0) { - session->next_crypto->do_compress_in = 1; + SSH_LOG(SSH_LOG_PACKET, "enabling C->S delayed compression"); + compression_enable(session, SSH_DIRECTION_OUT, true); } - cmp = strcmp(session->next_crypto->kex_methods[SSH_COMP_C_S], "zlib@openssh.com"); + + method = session->next_crypto->kex_methods[SSH_COMP_S_C]; + cmp = strcmp(method, "zlib"); if (cmp == 0) { - session->next_crypto->delayed_compress_out = 1; + SSH_LOG(SSH_LOG_PACKET, "enabling S->C compression"); + compression_enable(session, SSH_DIRECTION_IN, false); } - cmp = strcmp(session->next_crypto->kex_methods[SSH_COMP_S_C], "zlib@openssh.com"); + cmp = strcmp(method, "zlib@openssh.com"); if (cmp == 0) { - session->next_crypto->delayed_compress_in = 1; + SSH_LOG(SSH_LOG_PACKET, "enabling S->C delayed compression"); + compression_enable(session, SSH_DIRECTION_IN, true); } return SSH_OK; @@ -387,12 +423,11 @@ int crypt_set_algorithms_client(ssh_session session) #ifdef WITH_SERVER int crypt_set_algorithms_server(ssh_session session){ const char *method = NULL; - size_t i = 0; + uint8_t i = 0; struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab(); struct ssh_hmac_struct *ssh_hmactab=ssh_get_hmactab(); int cmp; - if (session == NULL) { return SSH_ERROR; } @@ -511,33 +546,27 @@ int crypt_set_algorithms_server(ssh_session session){ /* compression */ method = session->next_crypto->kex_methods[SSH_COMP_C_S]; - if(strcmp(method,"zlib") == 0){ - SSH_LOG(SSH_LOG_PACKET,"enabling C->S compression"); - session->next_crypto->do_compress_in=1; + cmp = strcmp(method, "zlib"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling C->S compression"); + compression_enable(session, SSH_DIRECTION_IN, false); } - if(strcmp(method,"zlib@openssh.com") == 0){ - SSH_LOG(SSH_LOG_PACKET,"enabling C->S delayed compression"); - - if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) { - session->next_crypto->do_compress_in = 1; - } else { - session->next_crypto->delayed_compress_in = 1; - } + cmp = strcmp(method, "zlib@openssh.com"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling C->S delayed compression"); + compression_enable(session, SSH_DIRECTION_IN, true); } method = session->next_crypto->kex_methods[SSH_COMP_S_C]; - if(strcmp(method,"zlib") == 0){ + cmp = strcmp(method, "zlib"); + if (cmp == 0) { SSH_LOG(SSH_LOG_PACKET, "enabling S->C compression"); - session->next_crypto->do_compress_out=1; + compression_enable(session, SSH_DIRECTION_OUT, false); } - if(strcmp(method,"zlib@openssh.com") == 0){ - SSH_LOG(SSH_LOG_PACKET,"enabling S->C delayed compression"); - - if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) { - session->next_crypto->do_compress_out = 1; - } else { - session->next_crypto->delayed_compress_out = 1; - } + cmp = strcmp(method, "zlib@openssh.com"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling S->C delayed compression"); + compression_enable(session, SSH_DIRECTION_OUT, true); } method = session->next_crypto->kex_methods[SSH_HOSTKEYS]; |