From 83853f8aea0e2f739cacd491632eb7fd3d03ad2d Mon Sep 17 00:00:00 2001 From: xalopp Date: Sun, 20 Feb 2022 00:46:40 +0100 Subject: [PATCH] Use modern API in userauth_keyboard_interactive() (#663) Files: userauth_kbd_packet.c, userauth_kbd_packet.h, test_keyboard_interactive_auth_info_request.c, userauth.c Notes: This refactors `SSH_MSG_USERAUTH_INFO_REQUEST` processing in `userauth_keyboard_interactive()` in order to improve robustness, correctness and readability or the code. * Refactor userauth_keyboard_interactive to use new api for packet parsing * add unit test for userauth_keyboard_interactive_parse_response() * add _libssh2_get_boolean() and _libssh2_get_byte() utility functions Credit: xalopp --- Makefile.inc | 1 + include/libssh2.h | 4 +- src/CMakeLists.txt | 2 + src/libssh2_priv.h | 8 +- src/misc.c | 23 ++ src/misc.h | 2 + src/userauth.c | 212 +---------- src/userauth_kbd_packet.c | 162 +++++++++ src/userauth_kbd_packet.h | 43 +++ tests/CMakeLists.txt | 36 ++ ...t_keyboard_interactive_auth_info_request.c | 335 ++++++++++++++++++ 11 files changed, 616 insertions(+), 212 deletions(-) create mode 100644 src/userauth_kbd_packet.c create mode 100644 src/userauth_kbd_packet.h create mode 100644 tests/test_keyboard_interactive_auth_info_request.c diff --git a/Makefile.inc b/Makefile.inc index 20d2ebee..99afe0f5 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -1,5 +1,6 @@ CSOURCES = channel.c comp.c crypt.c hostkey.c kex.c mac.c misc.c \ packet.c publickey.c scp.c session.c sftp.c userauth.c transport.c \ + userauth_kbd_packet.c \ version.c knownhost.c agent.c $(CRYPTO_CSOURCES) pem.c keepalive.c global.c \ blowfish.c bcrypt_pbkdf.c agent_win.c diff --git a/include/libssh2.h b/include/libssh2.h index 15dda6fc..ef3ce115 100644 --- a/include/libssh2.h +++ b/include/libssh2.h @@ -272,8 +272,8 @@ typedef off_t libssh2_struct_stat_size; typedef struct _LIBSSH2_USERAUTH_KBDINT_PROMPT { - char *text; - unsigned int length; + unsigned char *text; + size_t length; unsigned char echo; } LIBSSH2_USERAUTH_KBDINT_PROMPT; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eee1a80d..50c028c4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -207,6 +207,8 @@ set(SOURCES sftp.h transport.c transport.h + userauth_kbd_packet.c + userauth_kbd_packet.h userauth.c userauth.h version.c) diff --git a/src/libssh2_priv.h b/src/libssh2_priv.h index be16ad2e..b3ce7af7 100644 --- a/src/libssh2_priv.h +++ b/src/libssh2_priv.h @@ -760,10 +760,10 @@ struct _LIBSSH2_SESSION size_t userauth_kybd_data_len; unsigned char *userauth_kybd_packet; size_t userauth_kybd_packet_len; - unsigned int userauth_kybd_auth_name_len; - char *userauth_kybd_auth_name; - unsigned userauth_kybd_auth_instruction_len; - char *userauth_kybd_auth_instruction; + size_t userauth_kybd_auth_name_len; + unsigned char *userauth_kybd_auth_name; + size_t userauth_kybd_auth_instruction_len; + unsigned char *userauth_kybd_auth_instruction; unsigned int userauth_kybd_num_prompts; int userauth_kybd_auth_failure; LIBSSH2_USERAUTH_KBDINT_PROMPT *userauth_kybd_prompts; diff --git a/src/misc.c b/src/misc.c index 594b2d1f..5e4c4335 100644 --- a/src/misc.c +++ b/src/misc.c @@ -732,6 +732,29 @@ void _libssh2_string_buf_free(LIBSSH2_SESSION *session, struct string_buf *buf) buf = NULL; } +int _libssh2_get_byte(struct string_buf *buf, unsigned char *out) +{ + if(!_libssh2_check_length(buf, 1)) { + return -1; + } + + *out = buf->dataptr[0]; + buf->dataptr += 1; + return 0; +} + +int _libssh2_get_boolean(struct string_buf *buf, unsigned char *out) +{ + if(!_libssh2_check_length(buf, 1)) { + return -1; + } + + + *out = buf->dataptr[0] == 0 ? 0 : 1; + buf->dataptr += 1; + return 0; +} + int _libssh2_get_u32(struct string_buf *buf, uint32_t *out) { if(!_libssh2_check_length(buf, 4)) { diff --git a/src/misc.h b/src/misc.h index 5481e666..3d4206db 100644 --- a/src/misc.h +++ b/src/misc.h @@ -91,6 +91,8 @@ void _libssh2_explicit_zero(void *buf, size_t size); struct string_buf* _libssh2_string_buf_new(LIBSSH2_SESSION *session); void _libssh2_string_buf_free(LIBSSH2_SESSION *session, struct string_buf *buf); +int _libssh2_get_boolean(struct string_buf *buf, unsigned char *out); +int _libssh2_get_byte(struct string_buf *buf, unsigned char *out); int _libssh2_get_u32(struct string_buf *buf, uint32_t *out); int _libssh2_get_u64(struct string_buf *buf, libssh2_uint64_t *out); int _libssh2_match_string(struct string_buf *buf, const char *match); diff --git a/src/userauth.c b/src/userauth.c index 59b76ca9..551f24d2 100644 --- a/src/userauth.c +++ b/src/userauth.c @@ -52,6 +52,7 @@ #include "transport.h" #include "session.h" #include "userauth.h" +#include "userauth_kbd_packet.h" /* libssh2_userauth_list * @@ -1878,13 +1879,13 @@ userauth_keyboard_interactive(LIBSSH2_SESSION * session, ((*response_callback))) { unsigned char *s; + int rc; static const unsigned char reply_codes[4] = { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, SSH_MSG_USERAUTH_INFO_REQUEST, 0 }; - unsigned int language_tag_len; unsigned int i; if(session->userauth_kybd_state == libssh2_NB_state_idle) { @@ -2007,215 +2008,14 @@ userauth_keyboard_interactive(LIBSSH2_SESSION * session, } /* server requested PAM-like conversation */ - s = session->userauth_kybd_data + 1; - - if(session->userauth_kybd_data_len >= 5) { - /* string name (ISO-10646 UTF-8) */ - session->userauth_kybd_auth_name_len = _libssh2_ntohu32(s); - if(session->userauth_kybd_auth_name_len > - session->userauth_kybd_data_len - 5) - return _libssh2_error(session, - LIBSSH2_ERROR_OUT_OF_BOUNDARY, - "Bad keyboard auth name"); - s += 4; - } - else { - _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, - "userauth keyboard data buffer too small" - "to get length"); + if(userauth_keyboard_interactive_decode_info_request(session) + < 0) { goto cleanup; } - if(session->userauth_kybd_auth_name_len) { - session->userauth_kybd_auth_name = - LIBSSH2_ALLOC(session, - session->userauth_kybd_auth_name_len); - if(!session->userauth_kybd_auth_name) { - _libssh2_error(session, LIBSSH2_ERROR_ALLOC, - "Unable to allocate memory for " - "keyboard-interactive 'name' " - "request field"); - goto cleanup; - } - if(s + session->userauth_kybd_auth_name_len <= - session->userauth_kybd_data + - session->userauth_kybd_data_len) { - memcpy(session->userauth_kybd_auth_name, s, - session->userauth_kybd_auth_name_len); - s += session->userauth_kybd_auth_name_len; - } - else { - _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, - "userauth keyboard data buffer too small" - "for auth name"); - goto cleanup; - } - } - - if(s + 4 <= session->userauth_kybd_data + - session->userauth_kybd_data_len) { - /* string instruction (ISO-10646 UTF-8) */ - session->userauth_kybd_auth_instruction_len = - _libssh2_ntohu32(s); - s += 4; - } - else { - _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, - "userauth keyboard data buffer too small" - "for auth instruction length"); - goto cleanup; - } - - if(session->userauth_kybd_auth_instruction_len) { - session->userauth_kybd_auth_instruction = - LIBSSH2_ALLOC(session, - session->userauth_kybd_auth_instruction_len); - if(!session->userauth_kybd_auth_instruction) { - _libssh2_error(session, LIBSSH2_ERROR_ALLOC, - "Unable to allocate memory for " - "keyboard-interactive 'instruction' " - "request field"); - goto cleanup; - } - if(s + session->userauth_kybd_auth_instruction_len <= - session->userauth_kybd_data + - session->userauth_kybd_data_len) { - memcpy(session->userauth_kybd_auth_instruction, s, - session->userauth_kybd_auth_instruction_len); - s += session->userauth_kybd_auth_instruction_len; - } - else { - _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, - "userauth keyboard data buffer too small" - "for auth instruction"); - goto cleanup; - } - } - - if(s + 4 <= session->userauth_kybd_data + - session->userauth_kybd_data_len) { - /* string language tag (as defined in [RFC-3066]) */ - language_tag_len = _libssh2_ntohu32(s); - s += 4; - } - else { - _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, - "userauth keyboard data buffer too small" - "for auth language tag length"); - goto cleanup; - } - - if(s + language_tag_len <= session->userauth_kybd_data + - session->userauth_kybd_data_len) { - /* ignoring this field as deprecated */ - s += language_tag_len; - } - else { - _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, - "userauth keyboard data buffer too small" - "for auth language tag"); - goto cleanup; - } - - if(s + 4 <= session->userauth_kybd_data + - session->userauth_kybd_data_len) { - /* int num-prompts */ - session->userauth_kybd_num_prompts = _libssh2_ntohu32(s); - s += 4; - } - else { - _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, - "userauth keyboard data buffer too small" - "for auth num keyboard prompts"); - goto cleanup; - } - - if(session->userauth_kybd_num_prompts > 100) { - _libssh2_error(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, - "Too many replies for " - "keyboard-interactive prompts"); - goto cleanup; - } - - if(session->userauth_kybd_num_prompts) { - session->userauth_kybd_prompts = - LIBSSH2_CALLOC(session, - sizeof(LIBSSH2_USERAUTH_KBDINT_PROMPT) * - session->userauth_kybd_num_prompts); - if(!session->userauth_kybd_prompts) { - _libssh2_error(session, LIBSSH2_ERROR_ALLOC, - "Unable to allocate memory for " - "keyboard-interactive prompts array"); - goto cleanup; - } - - session->userauth_kybd_responses = - LIBSSH2_CALLOC(session, - sizeof(LIBSSH2_USERAUTH_KBDINT_RESPONSE) * - session->userauth_kybd_num_prompts); - if(!session->userauth_kybd_responses) { - _libssh2_error(session, LIBSSH2_ERROR_ALLOC, - "Unable to allocate memory for " - "keyboard-interactive responses array"); - goto cleanup; - } - - for(i = 0; i < session->userauth_kybd_num_prompts; i++) { - if(s + 4 <= session->userauth_kybd_data + - session->userauth_kybd_data_len) { - /* string prompt[1] (ISO-10646 UTF-8) */ - session->userauth_kybd_prompts[i].length = - _libssh2_ntohu32(s); - s += 4; - } - else { - _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, - "userauth keyboard data buffer too " - "small for auth keyboard " - "prompt length"); - goto cleanup; - } - - session->userauth_kybd_prompts[i].text = - LIBSSH2_CALLOC(session, - session->userauth_kybd_prompts[i]. - length); - if(!session->userauth_kybd_prompts[i].text) { - _libssh2_error(session, LIBSSH2_ERROR_ALLOC, - "Unable to allocate memory for " - "keyboard-interactive prompt message"); - goto cleanup; - } - - if(s + session->userauth_kybd_prompts[i].length <= - session->userauth_kybd_data + - session->userauth_kybd_data_len) { - memcpy(session->userauth_kybd_prompts[i].text, s, - session->userauth_kybd_prompts[i].length); - s += session->userauth_kybd_prompts[i].length; - } - else { - _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, - "userauth keyboard data buffer too " - "small for auth keyboard prompt"); - goto cleanup; - } - if(s < session->userauth_kybd_data + - session->userauth_kybd_data_len) { - /* boolean echo[1] */ - session->userauth_kybd_prompts[i].echo = *s++; - } - else { - _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, - "userauth keyboard data buffer too " - "small for auth keyboard prompt echo"); - goto cleanup; - } - } - } - - response_callback(session->userauth_kybd_auth_name, + response_callback((const char *)session->userauth_kybd_auth_name, session->userauth_kybd_auth_name_len, + (const char *) session->userauth_kybd_auth_instruction, session->userauth_kybd_auth_instruction_len, session->userauth_kybd_num_prompts, diff --git a/src/userauth_kbd_packet.c b/src/userauth_kbd_packet.c new file mode 100644 index 00000000..56cd0efe --- /dev/null +++ b/src/userauth_kbd_packet.c @@ -0,0 +1,162 @@ +/* Copyright (c) 2022, Xaver Loppenstedt + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include "userauth_kbd_packet.h" + +int userauth_keyboard_interactive_decode_info_request(LIBSSH2_SESSION *session) +{ + unsigned char *language_tag; + size_t language_tag_len; + unsigned int i; + unsigned char packet_type; + + struct string_buf decoded; + + decoded.data = session->userauth_kybd_data; + decoded.dataptr = session->userauth_kybd_data; + decoded.len = session->userauth_kybd_data_len; + + if(session->userauth_kybd_data_len < 17) { + _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "userauth keyboard data buffer too small " + "to get length"); + return -1; + } + + /* byte SSH_MSG_USERAUTH_INFO_REQUEST */ + _libssh2_get_byte(&decoded, &packet_type); + + /* string name (ISO-10646 UTF-8) */ + if(_libssh2_copy_string(session, &decoded, + &session->userauth_kybd_auth_name, + &session->userauth_kybd_auth_name_len) == -1) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to decode " + "keyboard-interactive 'name' " + "request field"); + return -1; + } + + /* string instruction (ISO-10646 UTF-8) */ + if(_libssh2_copy_string(session, &decoded, + &session->userauth_kybd_auth_instruction, + &session->userauth_kybd_auth_instruction_len) + == -1) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to decode " + "keyboard-interactive 'instruction' " + "request field"); + return -1; + } + + /* string language tag (as defined in [RFC-3066]) */ + if(_libssh2_get_string(&decoded, &language_tag, + &language_tag_len) == -1) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to decode " + "keyboard-interactive 'language tag' " + "request field"); + return -1; + } + + /* int num-prompts */ + if(_libssh2_get_u32(&decoded, &session->userauth_kybd_num_prompts) == -1) { + _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Unable to decode " + "keyboard-interactive number of keyboard prompts"); + return -1; + } + + if(session->userauth_kybd_num_prompts > 100) { + _libssh2_error(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, + "Too many replies for " + "keyboard-interactive prompts"); + return -1; + } + + if(session->userauth_kybd_num_prompts == 0) { + return 0; + } + + session->userauth_kybd_prompts = + LIBSSH2_CALLOC(session, + sizeof(LIBSSH2_USERAUTH_KBDINT_PROMPT) * + session->userauth_kybd_num_prompts); + if(!session->userauth_kybd_prompts) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "keyboard-interactive prompts array"); + return -1; + } + + session->userauth_kybd_responses = + LIBSSH2_CALLOC(session, + sizeof(LIBSSH2_USERAUTH_KBDINT_RESPONSE) * + session->userauth_kybd_num_prompts); + if(!session->userauth_kybd_responses) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for " + "keyboard-interactive responses array"); + return -1; + } + + for(i = 0; i < session->userauth_kybd_num_prompts; i++) { + /* string prompt[1] (ISO-10646 UTF-8) */ + if(_libssh2_copy_string(session, &decoded, + &session->userauth_kybd_prompts[i].text, + &session->userauth_kybd_prompts[i].length) + == -1) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to decode " + "keyboard-interactive prompt message"); + return -1; + } + + /* boolean echo[1] */ + if(_libssh2_get_boolean(&decoded, + &session->userauth_kybd_prompts[i].echo) + == -1) { + _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Unable to decode " + "user auth keyboard prompt echo"); + return -1; + } + } + + return 0; +} diff --git a/src/userauth_kbd_packet.h b/src/userauth_kbd_packet.h new file mode 100644 index 00000000..cce0a731 --- /dev/null +++ b/src/userauth_kbd_packet.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2022, Xaver Loppenstedt + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifndef __LIBSSH2_USERAUTH_KBD_PARSE_H +#define __LIBSSH2_USERAUTH_KBD_PARSE_H + +int userauth_keyboard_interactive_decode_info_request(LIBSSH2_SESSION *); + +#endif /* __LIBSSH2_USERAUTH_KBD_PARSE_H */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cf4b3f76..26244309 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -76,6 +76,8 @@ if(CRYPTO_BACKEND STREQUAL "OpenSSL" OR NOT CRYPTO_BACKEND) if(OPENSSL_FOUND) set(CRYPTO_BACKEND "OpenSSL") + set(CRYPTO_BACKEND_DEFINE "LIBSSH2_OPENSSL") + set(CRYPTO_BACKEND_INCLUDE_DIR ${OPENSSL_INCLUDE_DIR}) endif() endif() @@ -85,6 +87,8 @@ if(CRYPTO_BACKEND STREQUAL "Libgcrypt" OR NOT CRYPTO_BACKEND) if(LIBGCRYPT_FOUND) set(CRYPTO_BACKEND "Libgcrypt") + set(CRYPTO_BACKEND_DEFINE "LIBSSH2_LIBGCRYPT") + set(CRYPTO_BACKEND_INCLUDE_DIR ${LIBGCRYPT_INCLUDE_DIRS}) endif() endif() @@ -95,6 +99,8 @@ if(CRYPTO_BACKEND STREQUAL "WinCNG" OR NOT CRYPTO_BACKEND) if(HAVE_BCRYPT_H) set(CRYPTO_BACKEND "WinCNG") + set(CRYPTO_BACKEND_DEFINE "LIBSSH2_WINCNG") + set(CRYPTO_BACKEND_INCLUDE_DIR "") endif() endif() @@ -104,6 +110,8 @@ if(CRYPTO_BACKEND STREQUAL "mbedTLS" OR NOT CRYPTO_BACKEND) if(MBEDTLS_FOUND) set(CRYPTO_BACKEND "mbedTLS") + set(CRYPTO_BACKEND_DEFINE "LIBSSH2_MBEDTLS") + set(CRYPTO_BACKEND_INCLUDE_DIR ${MBEDTLS_INCLUDE_DIR}) endif() endif() @@ -166,6 +174,34 @@ foreach(test ${TESTS}) WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") endforeach() +if(WIN32 AND BUILD_SHARED_LIBS) + # Workaround for Visual Studio + add_executable(test_keyboard_interactive_auth_info_request test_keyboard_interactive_auth_info_request.c ../src/userauth_kbd_packet.c ../src/misc.c) +else() + add_executable(test_keyboard_interactive_auth_info_request test_keyboard_interactive_auth_info_request.c ../src/userauth_kbd_packet.c) +endif() +target_compile_definitions(test_keyboard_interactive_auth_info_request PRIVATE "${CRYPTO_BACKEND_DEFINE}") +target_include_directories(test_keyboard_interactive_auth_info_request PRIVATE "${CMAKE_CURRENT_BINARY_DIR}" "../src/" "${CRYPTO_BACKEND_INCLUDE_DIR}") +find_program(GCOV_PATH gcov) +if(CMAKE_COMPILER_IS_GNUCC AND GCOV_PATH) + target_compile_options(test_keyboard_interactive_auth_info_request BEFORE PRIVATE + -g --coverage -fprofile-abs-path) + target_link_libraries(test_keyboard_interactive_auth_info_request ${LIBRARIES} libssh2 gcov) +else() + target_link_libraries(test_keyboard_interactive_auth_info_request ${LIBRARIES} libssh2) +endif() +add_test( + NAME test_keyboard_interactive_auth_info_request COMMAND $ + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") + +add_custom_target(coverage + COMMAND gcovr -r "${CMAKE_SOURCE_DIR}" --exclude tests/* + COMMAND mkdir -p "${CMAKE_CURRENT_BINARY_DIR}/coverage/" + COMMAND gcovr -r "${CMAKE_SOURCE_DIR}" --exclude tests/* --html-details --output "${CMAKE_CURRENT_BINARY_DIR}/coverage/index.html") + +add_custom_target(clean-coverage + COMMAND rm -rf "${CMAKE_CURRENT_BINARY_DIR}/coverage/") + add_target_to_copy_dependencies( TARGET copy_test_dependencies DEPENDENCIES ${RUNTIME_DEPENDENCIES} diff --git a/tests/test_keyboard_interactive_auth_info_request.c b/tests/test_keyboard_interactive_auth_info_request.c new file mode 100644 index 00000000..da6989d6 --- /dev/null +++ b/tests/test_keyboard_interactive_auth_info_request.c @@ -0,0 +1,335 @@ +/* Copyright (C) 2022 Xaver Loppenstedt + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include + +#include "libssh2_priv.h" +#include "userauth_kbd_packet.h" + +#define PASS 0 +#define FAIL -1 + +struct expected { + int rc; + int last_error_code; + char *last_error_message; +}; +struct test_case { + char *data; + int data_len; + struct expected expected; +}; + +#define TEST_CASES_LEN 16 +struct test_case test_cases[TEST_CASES_LEN] = { + /* to small */ + { + NULL, 0, + {FAIL, -38, + "userauth keyboard data buffer too small to get length"}}, + /* to small */ + { + "1234", 4, + {FAIL, -38, + "userauth keyboard data buffer too small to get length"}}, + /* smalest valid packet possible */ + { + "<" + "\0\0\0\0" + "\0\0\0\0" + "\0\0\0\0" + "\0\0\0\0", 17, + {PASS, 0, ""}}, + /* overrun name */ + { + "<" + "\0\0\0\x7f" + "\0\0\0\0" + "\0\0\0\0" + "\0\0\0\0", 17, + {FAIL, -6, + "Unable to decode keyboard-interactive 'name' request field"}}, + /* overrun instruction */ + { + "<" + "\0\0\0\0" + "\0\0\0\x7f" + "\0\0\0\0" + "\0\0\0\0", 17, + {FAIL, -6, + "Unable to decode keyboard-interactive 'instruction' " + "request field"}}, + /* overrun language */ + { + "<" + "\0\0\0\0" + "\0\0\0\0" + "\0\0\0\x7f" + "\0\0\0\0", 17, + {FAIL, -6, "Unable to decode keyboard-interactive 'language tag' " + "request field"}}, + /* underrun prompt number */ + { + "<" + "\0\0\0\x01" + "\0\0\0\0" + "\0\0\0\0" + "\0\0\0\0", 17, + {FAIL, -38, + "Unable to decode keyboard-interactive number of " + "keyboard prompts"}}, + /* too many prompts */ + { + "<" + "\0\0\0\0" + "\0\0\0\0" + "\0\0\0\0" + "\0\0\0\x7f", 17, + {FAIL, -41, "Too many replies for keyboard-interactive prompts"}}, + /* empty prompt */ + { + "<" + "\0\0\0\0" + "\0\0\0\0" + "\0\0\0\0" + "\0\0\0\x01" + "\0\0\0\0" + "\0", 22, {PASS, 0, ""}}, + /* copied from OpenSSH */ + { + "<" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01" + "\0\0\0\x0aPassword: \0", 32, {PASS, 0, ""}}, + /* overrun in prompt text */ + { + "<" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01" + "\0\0\0\x7bPassword: \0", 32, + {FAIL, -6, "Unable to decode keyboard-interactive " + "prompt message"}}, + /* no echo prompt boolean */ + { + "<" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01" + "\0\0\0\x0bPassword: \0", 32, + {FAIL, -38, "Unable to decode user auth keyboard prompt echo"}}, + /* two prompts */ + { + "<" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x02" + "\0\0\0\x0aPassword: \0" + "\0\0\0\x07Token: \1", 44, + {PASS, 0, ""}}, + /* example from RFC 4256 */ + { + "<" + "\0\0\0\x19""CRYPTOCard Authentication" + "\0\0\0\x1b""The challenge is '14315716'" + "\0\0\0\x05""en-US" + "\0\0\0\x01" + "\0\0\0\x0aResponse: " + "\x01" + , 89, {PASS, 0, ""}}, + /* three prompts, 3rd missing*/ + { + "<" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x03" + "\0\0\0\x0aPassword: \0" + "\0\0\0\x07Token: \1", 44, + {FAIL, -6, + "Unable to decode keyboard-interactive prompt message"}}, + /* overflow language on 32 bit platform */ + { + "<" + "\0\0\0\x19" + "\0\0\0\x01" + "\0\0\0\x05""PWN3D\0\1\2\3\4\5\6\7\1\2\3" + "\x01" + "\0\0\0\x1b""The challenge is '14315716'" + "\xff\xff\xff\xc4""en-US" + "\0\0\0\x01" + "\0\0\0\x0aResponse: " + "\x01", + 89, + {FAIL, -6, + "Unable to decode keyboard-interactive 'language tag' " + "request field"}}, +}; + +#define FAILED_MALLOC_TEST_CASES_LEN 2 +struct test_case failed_malloc_test_cases[FAILED_MALLOC_TEST_CASES_LEN] = { + /* malloc fail */ + { + "<" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01" + "\0\0\0\x0aPassword: \0", 32, + {FAIL, -6, + "Unable to allocate memory for " + "keyboard-interactive prompts array"}}, + /* malloc fail */ + { + "<" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01" + "\0\0\0\x0aPassword: \0", 32, + {FAIL, -6, + "Unable to allocate memory for " + "keyboard-interactive responses array" + }} +}; + +static int alloc_count = 0; +static int free_count = 0; + +/* libssh2_default_alloc + */ +static +LIBSSH2_ALLOC_FUNC(test_alloc) +{ + alloc_count++; + + int *threshold_int_ptr = *abstract; + if (*abstract != NULL && *threshold_int_ptr == alloc_count) { + return NULL; + } + + return malloc(count); +} + +/* libssh2_default_free + */ +static +LIBSSH2_FREE_FUNC(test_free) +{ + (void) abstract; + free_count++; + free(ptr); +} + +static +int test_case(int num, + char *data, int data_len, void *abstract, + struct expected expected) +{ + alloc_count = 0; + free_count = 0; + LIBSSH2_SESSION *session = NULL; + session = libssh2_session_init_ex(test_alloc, test_free, NULL, abstract); + if(session == NULL) { + fprintf(stderr, "libssh2_session_init_ex failed\n"); + return 1; + } + + session->userauth_kybd_data = LIBSSH2_ALLOC(session, data_len); + session->userauth_kybd_data_len = data_len; + memcpy(session->userauth_kybd_data, data, data_len); + + int rc = userauth_keyboard_interactive_decode_info_request(session); + + if(rc != expected.rc) { + fprintf(stdout, + "Test case %d: expected return code to be %d got %d\n", + num, expected.rc, rc); + return 1; + } + + char *message; + int error_code = libssh2_session_last_error(session, &message, NULL, 0); + + if(expected.last_error_code != error_code) { + fprintf(stdout, + "Test case %d: expected last error code to be " + "\"%d\" got \"%d\"\n", + num, expected.last_error_code, error_code); + return 1; + } + + if(strcmp(expected.last_error_message, message) != 0) { + fprintf(stdout, + "Test case %d: expected last error message to be " + "\"%s\" got \"%s\"\n", + num, expected.last_error_message, message); + return 1; + } + libssh2_session_free(session); + + fprintf(stderr, "Test case %d passed\n", num); + + return 0; +} + +int main() +{ + int i; + + for(i = 0; i < TEST_CASES_LEN; i++) { + test_case(i + 1, + test_cases[i].data, test_cases[i].data_len, + NULL, + test_cases[i].expected); + } + + for(i = 0; i < FAILED_MALLOC_TEST_CASES_LEN; i++) { + int tc = i + TEST_CASES_LEN + 1; + int malloc_call_num = 5 + i; + test_case(tc, + failed_malloc_test_cases[i].data, + failed_malloc_test_cases[i].data_len, + &malloc_call_num, + failed_malloc_test_cases[i].expected); + } + + return 0; +} + +/* Workaround for Visual Studio */ +#ifdef _MSC_VER +int +bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, + size_t saltlen, + uint8_t *key, size_t keylen, unsigned int rounds) +{ + (void)pass; + (void)passlen; + (void)salt; + (void)saltlen; + (void)key; + (void)keylen; + (void)rounds; + + return -1; +} +#endif \ No newline at end of file