From 31306d91f22e7d6012d23346d4a5526234573d35 Mon Sep 17 00:00:00 2001 From: Packit Service Date: Dec 09 2020 20:31:21 +0000 Subject: libssh-0.9.4 base --- diff --git a/.arcconfig b/.arcconfig new file mode 100644 index 0000000..30b1a60 --- /dev/null +++ b/.arcconfig @@ -0,0 +1,4 @@ +{ + "phabricator.uri" : "https://bugs.libssh.org/", + "history.immutable": true +} diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..8570694 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,515 @@ +variables: + BUILD_IMAGES_PROJECT: libssh/build-images + FEDORA_BUILD: buildenv-fedora + CENTOS7_BUILD: buildenv-centos7 + TUMBLEWEED_BUILD: buildenv-tumbleweed + MINGW_BUILD: buildenv-mingw + +# pkd tests fail on CentOS7 docker images, so we don't use -DSERVER_TESTING=ON +centos7/openssl_1.0.x/x86_64: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS7_BUILD + script: + - mkdir -p obj && cd obj && cmake3 + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON -DCLIENT_TESTING=ON .. && + make -j$(nproc) && ctest --output-on-failure + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +fedora/openssl_1.1.x/x86_64: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + script: + - mkdir -p obj && cd obj && cmake + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DPICKY_DEVELOPER=ON + -DWITH_BLOWFISH_CIPHER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DWITH_DEBUG_CRYPTO=ON + -DWITH_DEBUG_PACKET=ON -DWITH_DEBUG_CALLTRACE=ON + -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON .. && + make -j$(nproc) && ctest --output-on-failure + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +fedora/openssl_1.1.x/x86_64/fips: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + script: + - echo 1 > /etc/system-fips + - update-crypto-policies --set FIPS + - mkdir -p obj && cd obj && cmake + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DPICKY_DEVELOPER=ON + -DWITH_BLOWFISH_CIPHER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DWITH_DEBUG_CRYPTO=ON -DWITH_DEBUG_PACKET=ON -DWITH_DEBUG_CALLTRACE=ON + -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON .. && + make -j$(nproc) && OPENSSL_FORCE_FIPS_MODE=1 ctest --output-on-failure + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +fedora/openssl_1.1.x/x86_64/minimal: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + script: + - mkdir -p obj && cd obj && cmake + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=OFF -DWITH_SERVER=OFF -DWITH_ZLIB=OFF -DWITH_PCAP=OFF + -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DWITH_GEX=OFF .. && + make -j$(nproc) && ctest --output-on-failure + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +# Address sanitizer doesn't mix well with LD_PRELOAD used in the testsuite +# so, this is only enabled for unit tests right now. +# TODO: add -DCLIENT_TESTING=ON -DSERVER_TESTING=ON +fedora/address-sanitizer: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + script: + - mkdir -p obj && cd obj && cmake + -DCMAKE_BUILD_TYPE=AddressSanitizer + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON .. && + make -j$(nproc) && ctest --output-on-failure + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +# This is disabled as it report OpenSSL issues +# It also has ethe same issues with cwrap as AddressSanitizer +.fedora/memory-sanitizer: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + script: + - mkdir -p obj && cd obj && cmake + -DCMAKE_BUILD_TYPE=MemorySanitizer + -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON .. + && make -j$(nproc) && ctest --output-on-failure + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +fedora/undefined-sanitizer: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + script: + - mkdir -p obj && cd obj && cmake + -DCMAKE_BUILD_TYPE=UndefinedSanitizer + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON .. + && make -j$(nproc) && ctest --output-on-failure + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +fedora/csbuild: + variables: + GIT_DEPTH: "100" + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + script: + - | + if [[ -z "$CI_COMMIT_BEFORE_SHA" ]]; then + export CI_COMMIT_BEFORE_SHA=$(git rev-parse "${CI_COMMIT_SHA}~20") + fi + + # Check if the commit exists in this branch + # This is not the case for a force push + git branch --contains $CI_COMMIT_BEFORE_SHA 2>/dev/null || export CI_COMMIT_BEFORE_SHA=$(git rev-parse "${CI_COMMIT_SHA}~20") + + export CI_COMMIT_RANGE="$CI_COMMIT_BEFORE_SHA..$CI_COMMIT_SHA" + + - csbuild + --build-dir=obj-csbuild + --build-cmd "rm -rf CMakeFiles CMakeCache.txt && cmake -DCMAKE_BUILD_TYPE=Debug -DPICKY_DEVELOPER=ON -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON -DFUZZ_TESTING=ON @SRCDIR@ && make clean && make -j$(nproc)" + --git-commit-range $CI_COMMIT_RANGE + --color + --print-current --print-fixed + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj-csbuild/ + +# That is a specific runner that we cannot enable universally. +# We restrict it to builds under the $BUILD_IMAGES_PROJECT project. +freebsd/x86_64: + image: + script: + - mkdir -p obj && cd obj && cmake + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON .. && + make && ctest --output-on-failure + tags: + - freebsd + except: + - tags + only: + - branches@libssh/libssh-mirror + - branches@cryptomilk/libssh-mirror + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +fedora/libgcrypt/x86_64: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + script: + - mkdir -p obj && cd obj && cmake + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON + -DWITH_GCRYPT=ON -DWITH_DEBUG_CRYPTO=ON .. && + make -j$(nproc) && ctest --output-on-failure + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +fedora/mbedtls/x86_64: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + script: + - mkdir -p obj && cd obj && cmake + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON + -DWITH_MBEDTLS=ON -DWITH_DEBUG_CRYPTO=ON .. && + make -j$(nproc) && ctest --output-on-failure + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +# Unit testing only, no client and pkd testing, because cwrap is not available +# for MinGW +fedora/mingw64: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$MINGW_BUILD + script: + - export WINEPATH=/usr/x86_64-w64-mingw32/sys-root/mingw/bin + - export WINEDEBUG=-all + - mkdir -p obj && cd obj && mingw64-cmake + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON .. && + make -j$(nproc) && + ctest --output-on-failure + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +# Unit testing only, no client and pkd testing, because cwrap is not available +# for MinGW +fedora/mingw32: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$MINGW_BUILD + script: + - export WINEPATH=/usr/i686-w64-mingw32/sys-root/mingw/bin + - export WINEDEBUG=-all + - mkdir -p obj && cd obj && mingw32-cmake + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON .. && + make -j$(nproc) && + ctest --output-on-failure + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +tumbleweed/openssl_1.1.x/x86_64/gcc: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD + script: + - mkdir -p obj && cd obj && cmake + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config + -DUNIT_TESTING=ON -DSERVER_TESTING=ON .. && + make -j$(nproc) && ctest --output-on-failure + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +tumbleweed/openssl_1.1.x/x86/gcc: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD + script: + - mkdir -p obj && cd obj && cmake + -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-cross-m32.cmake + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON .. && + make -j$(nproc) && ctest --output-on-failure + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +tumbleweed/openssl_1.1.x/x86_64/gcc7: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD + script: + - mkdir -p obj && cd obj && cmake + -DCMAKE_C_COMPILER=gcc-7 -DCMAKE_CXX_COMPILER=g++-7 + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config + -DUNIT_TESTING=ON -DSERVER_TESTING=ON .. && + make -j$(nproc) && ctest --output-on-failure + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +tumbleweed/openssl_1.1.x/x86/gcc7: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD + script: + - mkdir -p obj && cd obj && cmake + -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-cross-m32.cmake + -DCMAKE_C_COMPILER=gcc-7 -DCMAKE_CXX_COMPILER=g++-7 + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON .. && + make -j$(nproc) && ctest --output-on-failure + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +tumbleweed/openssl_1.1.x/x86_64/clang: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD + script: + - mkdir -p obj && cd obj && cmake + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config + -DUNIT_TESTING=ON + -DSERVER_TESTING=ON .. && + make -j$(nproc) && ctest --output-on-failure + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +tumbleweed/docs: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD + script: + - mkdir -p obj && cd obj && cmake .. && make docs + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +tumbleweed/undefined-sanitizer: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD + script: + - mkdir -p obj && cd obj && cmake + -DCMAKE_BUILD_TYPE=UndefinedSanitizer + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON -DSERVER_TESTING=ON .. && + make -j$(nproc) && ctest --output-on-failure + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +tumbleweed/static-analysis: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD + script: + - export CCC_CC=clang + - export CCC_CXX=clang++ + - mkdir -p obj && cd obj && scan-build cmake + -DCMAKE_BUILD_TYPE=Debug + -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON -DSERVER_TESTING=ON .. && + scan-build --status-bugs -o scan make -j$(nproc) + tags: + - shared + except: + - tags + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/scan + +visualstudio/x86_64: + variables: + ErrorActionPreference: STOP + script: + - $env:VCPKG_DEFAULT_TRIPLET="x64-windows" + - mkdir -p obj; if ($?) {cd obj}; if (! $?) {exit 1} + - cmake + -A x64 + -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_TOOLCHAIN_FILE" + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON .. + - cmake --build . + - ctest --output-on-failure + tags: + - vs2017 + - windows + except: + - tags + only: + - branches@libssh/libssh-mirror + - branches@ansasaki/libssh-mirror + - branches@cryptomilk/libssh-mirror + - branches@jjelen/libssh-mirror + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ + +visualstudio/x86: + variables: + ErrorActionPreference: STOP + script: + - $env:VCPKG_DEFAULT_TRIPLET="x86-windows" + - mkdir -p obj; if ($?) {cd obj}; if (! $?) {exit 1} + - cmake + -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_TOOLCHAIN_FILE" + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON .. + - cmake --build . + - ctest --output-on-failure + tags: + - vs2017 + - windows + except: + - tags + only: + - branches@libssh/libssh-mirror + - branches@ansasaki/libssh-mirror + - branches@cryptomilk/libssh-mirror + - branches@jjelen/libssh-mirror + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..51b3e46 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,15 @@ +Author(s): +Aris Adamantiadis (project initiator) + +Andreas Schneider (developer) + +Nick Zitzmann (mostly client SFTP stuff) + +Norbert Kiesel (getaddrinfo and other patches) + +Jean-Philippe Garcia Ballester (Port to libgcrypt and configure.in voodoo, debian packaging) + +Contributor(s): + +Laurent Bigonville (debian packaging) + diff --git a/BSD b/BSD new file mode 100644 index 0000000..b8dba0d --- /dev/null +++ b/BSD @@ -0,0 +1,24 @@ +Some parts are under the BSDv2 License : + + +Copyright (c) 2000 Markus Friedl. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. 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. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..41976ff --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,248 @@ +cmake_minimum_required(VERSION 3.3.0) +cmake_policy(SET CMP0048 NEW) + +# Specify search path for CMake modules to be loaded by include() +# and find_package() +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules") + +# Add defaults for cmake +# Those need to be set before the project() call. +include(DefineCMakeDefaults) +include(DefineCompilerFlags) + +project(libssh VERSION 0.9.4 LANGUAGES C) + +# global needed variable +set(APPLICATION_NAME ${PROJECT_NAME}) + +# SOVERSION scheme: CURRENT.AGE.REVISION +# If there was an incompatible interface change: +# Increment CURRENT. Set AGE and REVISION to 0 +# If there was a compatible interface change: +# Increment AGE. Set REVISION to 0 +# If the source code was changed, but there were no interface changes: +# Increment REVISION. +set(LIBRARY_VERSION "4.8.5") +set(LIBRARY_SOVERSION "4") + +# where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked + +# add definitions +include(DefinePlatformDefaults) +include(DefineOptions.cmake) +include(CPackConfig.cmake) +include(GNUInstallDirs) + +include(CompilerChecks.cmake) + +# disallow in-source build +include(MacroEnsureOutOfSourceBuild) +macro_ensure_out_of_source_build("${PROJECT_NAME} requires an out of source build. Please create a separate build directory and run 'cmake /path/to/${PROJECT_NAME} [options]' there.") + +# Copy library files to a lib sub-directory +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") + +# search for libraries +if (WITH_ZLIB) + find_package(ZLIB REQUIRED) +endif (WITH_ZLIB) + +if (WITH_GCRYPT) + find_package(GCrypt 1.5.0 REQUIRED) + if (NOT GCRYPT_FOUND) + message(FATAL_ERROR "Could not find GCrypt") + endif (NOT GCRYPT_FOUND) +elseif(WITH_MBEDTLS) + find_package(MbedTLS REQUIRED) + if (NOT MBEDTLS_FOUND) + message(FATAL_ERROR "Could not find mbedTLS") + endif (NOT MBEDTLS_FOUND) +else (WITH_GCRYPT) + find_package(OpenSSL) + if (NOT OPENSSL_FOUND) + find_package(GCrypt) + if (NOT GCRYPT_FOUND) + find_package(MbedTLS) + if (NOT MBEDTLS_FOUND) + message(FATAL_ERROR "Could not find OpenSSL, GCrypt or mbedTLS") + endif (NOT MBEDTLS_FOUND) + endif (NOT GCRYPT_FOUND) + endif (NOT OPENSSL_FOUND) +endif(WITH_GCRYPT) + +if (UNIT_TESTING) + find_package(CMocka REQUIRED) +endif () + +# Find out if we have threading available +set(CMAKE_THREAD_PREFER_PTHREADS ON) +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads) + +if (WITH_GSSAPI) + find_package(GSSAPI) +endif (WITH_GSSAPI) + +if (WITH_NACL) + find_package(NaCl) + if (NOT NACL_FOUND) + set(WITH_NACL OFF) + endif (NOT NACL_FOUND) +endif (WITH_NACL) + +if (BSD OR SOLARIS OR OSX) + find_package(Argp) +endif (BSD OR SOLARIS OR OSX) + +# Disable symbol versioning in non UNIX platforms +if (UNIX) + find_package(ABIMap 0.3.1) +else (UNIX) + set(WITH_SYMBOL_VERSIONING OFF) +endif (UNIX) + +# config.h checks +include(ConfigureChecks.cmake) +configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) + +# check subdirectories +add_subdirectory(doc) +add_subdirectory(include) +add_subdirectory(src) + +# pkg-config file +if (UNIX) +configure_file(libssh.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/libssh.pc) +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/libssh.pc + DESTINATION + ${CMAKE_INSTALL_LIBDIR}/pkgconfig + COMPONENT + pkgconfig +) +endif (UNIX) + +# CMake config files +include(CMakePackageConfigHelpers) + +set(LIBSSH_LIBRARY_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}ssh${CMAKE_SHARED_LIBRARY_SUFFIX}) + +# libssh-config-version.cmake +write_basic_package_version_file(libssh-config-version.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion) + +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake + DESTINATION + ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} + COMPONENT + devel) + +if (WITH_EXAMPLES) + add_subdirectory(examples) +endif (WITH_EXAMPLES) + +if (UNIT_TESTING) + include(AddCMockaTest) + add_subdirectory(tests) +endif (UNIT_TESTING) + +### SOURCE PACKAGE +if (WITH_SYMBOL_VERSIONING AND ABIMAP_FOUND) + # Get the current ABI version from source + get_filename_component(current_abi_path + "${CMAKE_SOURCE_DIR}/src/ABI/current" + ABSOLUTE) + + # Check if the ABI version should be updated + file(READ ${current_abi_path} CURRENT_ABI_CONTENT) + string(STRIP "${CURRENT_ABI_CONTENT}" CURRENT_ABI_VERSION) + + if (LIBRARY_VERSION VERSION_GREATER CURRENT_ABI_VERSION) + set(UPDATE_ABI TRUE) + endif () + + if (UPDATE_ABI) + message(STATUS "Library version bumped to ${LIBRARY_VERSION}: Updating ABI") + + # Get the list of header files + get_file_list(${PROJECT_NAME}_header_list + DIRECTORIES "${CMAKE_SOURCE_DIR}/include/libssh" + FILES_PATTERNS "*.h") + + # Extract the symbols marked as "LIBSSH_API" from the header files + extract_symbols(${PROJECT_NAME}.symbols + HEADERS_LIST ${PROJECT_NAME}_header_list + FILTER_PATTERN "LIBSSH_API" + COPY_TO "${CMAKE_SOURCE_DIR}/src/ABI/${PROJECT_NAME}-${LIBRARY_VERSION}.symbols") + + if (WITH_ABI_BREAK) + set(ALLOW_ABI_BREAK "BREAK_ABI") + endif() + + # Target we can depend on in 'make dist' + set(_SYMBOL_TARGET "${PROJECT_NAME}.map") + + # Set the path to the current map file + set(MAP_PATH "${CMAKE_SOURCE_DIR}/src/${_SYMBOL_TARGET}") + + # Generate the symbol version map file + generate_map_file(${_SYMBOL_TARGET} + SYMBOLS ${PROJECT_NAME}.symbols + RELEASE_NAME_VERSION ${PROJECT_NAME}_${LIBRARY_VERSION} + CURRENT_MAP ${MAP_PATH} + COPY_TO ${MAP_PATH} + FINAL + ${ALLOW_ABI_BREAK}) + + # Write the current version to the source + file(WRITE ${current_abi_path} ${LIBRARY_VERSION}) + endif(UPDATE_ABI) +endif (WITH_SYMBOL_VERSIONING AND ABIMAP_FOUND) + +add_custom_target(dist COMMAND ${CMAKE_MAKE_PROGRAM} package_source DEPENDS ${_SYMBOL_TARGET}) + +# Link compile database for clangd +execute_process(COMMAND cmake -E create_symlink + "${CMAKE_BINARY_DIR}/compile_commands.json" + "${CMAKE_SOURCE_DIR}/compile_commands.json") + +message(STATUS "********************************************") +message(STATUS "********** ${PROJECT_NAME} build options : **********") + +message(STATUS "zlib support: ${WITH_ZLIB}") +message(STATUS "libgcrypt support: ${WITH_GCRYPT}") +message(STATUS "libmbedTLS support: ${WITH_MBEDTLS}") +message(STATUS "libnacl support: ${WITH_NACL}") +message(STATUS "SFTP support: ${WITH_SFTP}") +message(STATUS "Server support : ${WITH_SERVER}") +message(STATUS "GSSAPI support : ${WITH_GSSAPI}") +message(STATUS "GEX support : ${WITH_GEX}") +message(STATUS "Pcap debugging support : ${WITH_PCAP}") +message(STATUS "Build shared library: ${BUILD_SHARED_LIBS}") +message(STATUS "Unit testing: ${UNIT_TESTING}") +message(STATUS "Client code testing: ${CLIENT_TESTING}") +message(STATUS "Blowfish cipher support: ${WITH_BLOWFISH_CIPHER}") +set(_SERVER_TESTING OFF) +if (WITH_SERVER) + set(_SERVER_TESTING ${SERVER_TESTING}) +endif() +message(STATUS "Server code testing: ${_SERVER_TESTING}") +if (WITH_INTERNAL_DOC) + message(STATUS "Internal documentation generation") +else (WITH_INTERNAL_DOC) + message(STATUS "Public API documentation generation") +endif (WITH_INTERNAL_DOC) +message(STATUS "Benchmarks: ${WITH_BENCHMARKS}") +message(STATUS "Symbol versioning: ${WITH_SYMBOL_VERSIONING}") +message(STATUS "Allow ABI break: ${WITH_ABI_BREAK}") +message(STATUS "Release is final: ${WITH_FINAL}") +message(STATUS "Global client config: ${GLOBAL_CLIENT_CONFIG}") +if (WITH_SERVER) +message(STATUS "Global bind config: ${GLOBAL_BIND_CONFIG}") +endif() +message(STATUS "********************************************") + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..ff91d17 --- /dev/null +++ b/COPYING @@ -0,0 +1,469 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + Linking with OpenSSL + + 17. In addition, as a special exception, we give permission to link the code +of its release of libssh with the OpenSSL project's "OpenSSL" library (or with +modified versions of it that use the same license as the "OpenSSL" library), +and distribute the linked executables. You must obey the GNU Lesser General +Public License in all respects for all of the code used other than "OpenSSL". +If you modify this file, you may extend this exception to your version of the +file, but you are not obligated to do so. If you do not wish to do so, delete +this exception statement from your version. + + END OF TERMS AND CONDITIONS diff --git a/CPackConfig.cmake b/CPackConfig.cmake new file mode 100644 index 0000000..c4a3598 --- /dev/null +++ b/CPackConfig.cmake @@ -0,0 +1,44 @@ +### GENERAL SETTINGS +set(CPACK_PACKAGE_NAME ${PROJECT_NAME}) +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "The SSH Library") +set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README") +set(CPACK_PACKAGE_VENDOR "The SSH Library Development Team") +set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING") + +set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) + +# SOURCE GENERATOR +set(CPACK_SOURCE_GENERATOR "TXZ") +set(CPACK_SOURCE_IGNORE_FILES "~$;[.]swp$;/[.]git/;/[.]clangd/;.gitignore;/build*;/obj*;tags;cscope.*;compile_commands.json;.*\.patch") +set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}") + +### NSIS INSTALLER +if (WIN32) + set(CPACK_GENERATOR "ZIP") + + ### nsis generator + find_package(NSIS) + if (NSIS_MAKE) + set(CPACK_GENERATOR "${CPACK_GENERATOR};NSIS") + set(CPACK_NSIS_DISPLAY_NAME "The SSH Library") + set(CPACK_NSIS_COMPRESSOR "/SOLID zlib") + set(CPACK_NSIS_MENU_LINKS "https://www.libssh.org/" "libssh homepage") + endif (NSIS_MAKE) +endif (WIN32) + +set(CPACK_PACKAGE_INSTALL_DIRECTORY "libssh") + +set(CPACK_PACKAGE_FILE_NAME ${APPLICATION_NAME}-${CPACK_PACKAGE_VERSION}) + +set(CPACK_COMPONENT_LIBRARIES_DISPLAY_NAME "Libraries") +set(CPACK_COMPONENT_HEADERS_DISPLAY_NAME "C/C++ Headers") +set(CPACK_COMPONENT_LIBRARIES_DESCRIPTION + "Libraries used to build programs which use libssh") +set(CPACK_COMPONENT_HEADERS_DESCRIPTION + "C/C++ header files for use with libssh") +set(CPACK_COMPONENT_HEADERS_DEPENDS libraries) +set(CPACK_COMPONENT_LIBRARIES_GROUP "Development") +set(CPACK_COMPONENT_HEADERS_GROUP "Development") + +include(CPack) diff --git a/CTestConfig.cmake b/CTestConfig.cmake new file mode 100644 index 0000000..7a18672 --- /dev/null +++ b/CTestConfig.cmake @@ -0,0 +1,9 @@ +set(UPDATE_TYPE "true") + +set(CTEST_PROJECT_NAME "libssh") +set(CTEST_NIGHTLY_START_TIME "01:00:00 UTC") + +set(CTEST_DROP_METHOD "https") +set(CTEST_DROP_SITE "test.libssh.org") +set(CTEST_DROP_LOCATION "/submit.php?project=libssh") +set(CTEST_DROP_SITE_CDASH TRUE) diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e8bc19d --- /dev/null +++ b/ChangeLog @@ -0,0 +1,589 @@ +ChangeLog +========== + +version 0.9.4 (released 2020-04-09) + * Fixed CVE-2020-1730 - Possible DoS in client and server when handling + AES-CTR keys with OpenSSL + * Added diffie-hellman-group14-sha256 + * Fixed serveral possible memory leaks + +version 0.9.3 (released 2019-12-10) + * Fixed CVE-2019-14889 - SCP: Unsanitized location leads to command execution + * SSH-01-003 Client: Missing NULL check leads to crash in erroneous state + * SSH-01-006 General: Various unchecked Null-derefs cause DOS + * SSH-01-007 PKI Gcrypt: Potential UAF/double free with RSA pubkeys + * SSH-01-010 SSH: Deprecated hash function in fingerprinting + * SSH-01-013 Conf-Parsing: Recursive wildcards in hostnames lead to DOS + * SSH-01-014 Conf-Parsing: Integer underflow leads to OOB array access + * SSH-01-001 State Machine: Initial machine states should be set explicitly + * SSH-01-002 Kex: Differently bound macros used to iterate same array + * SSH-01-005 Code-Quality: Integer sign confusion during assignments + * SSH-01-008 SCP: Protocol Injection via unescaped File Names + * SSH-01-009 SSH: Update documentation which RFCs are implemented + * SSH-01-012 PKI: Information leak via uninitialized stack buffer + +version 0.9.2 (released 2019-11-07) + * Fixed libssh-config.cmake + * Fixed issues with rsa algorithm negotiation (T191) + * Fixed detection of OpenSSL ed25519 support (T197) + +version 0.9.1 (released 2019-10-25) + * Added support for Ed25519 via OpenSSL + * Added support for X25519 via OpenSSL + * Added support for localuser in Match keyword + * Fixed Match keyword to be case sensitive + * Fixed compilation with LibreSSL + * Fixed error report of channel open (T75) + * Fixed sftp documentation (T137) + * Fixed known_hosts parsing (T156) + * Fixed build issue with MinGW (T157) + * Fixed build with gcc 9 (T164) + * Fixed deprecation issues (T165) + * Fixed known_hosts directory creation (T166) + +version 0.9.0 (released 2019-06-28) + * Added support for AES-GCM + * Added improved rekeying support + * Added performance improvements + * Disabled blowfish support by default + * Fixed several ssh config parsing issues + * Added support for DH Group Exchange KEX + * Added support for Encrypt-then-MAC mode + * Added support for parsing server side configuration file + * Added support for ECDSA/Ed25519 certificates + * Added FIPS 140-2 compatibility + * Improved known_hosts parsing + * Improved documentation + * Improved OpenSSL API usage for KEX, DH, and signatures + +version 0.8.7 (released 2019-02-25) + * Fixed handling extension flags in the server implementation + * Fixed exporting ed25519 private keys + * Fixed corner cases for rsa-sha2 signatures + * Fixed some issues with connector + +version 0.8.6 (released 2018-12-24) + * Fixed compilation issues with different OpenSSL versions + * Fixed StrictHostKeyChecking in new knownhosts API + * Fixed ssh_send_keepalive() with packet filter + * Fixed possible crash with knownhosts options + * Fixed issus with rekeying + * Fixed strong ECDSA keys + * Fixed some issues with rsa-sha2 extentions + * Fixed access violation in ssh_init() (static linking) + * Fixed ssh_channel_close() handling + +version 0.8.5 (released 2018-10-29) + * Added support to get known_hosts locations with ssh_options_get() + * Fixed preferred algorithm for known hosts negotiations + * Fixed KEX with some server implementations (e.g. Cisco) + * Fixed issues with MSVC + * Fixed keyboard-interactive auth in server mode + (regression from CVE-2018-10933) + * Fixed gssapi auth in server mode (regression from CVE-2018-10933) + * Fixed socket fd handling with proxy command + * Fixed a memory leak with OpenSSL + +version 0.8.4 (released 2018-10-16) + * Fixed CVE-2018-10933 + * Fixed building without globbing support + * Fixed possible memory leaks + * Avoid SIGPIPE on sockets + +version 0.8.3 (released 2018-09-21) + * Added support for rsa-sha2 + * Added support to parse private keys in openssh container format + (other than ed25519) + * Added support for diffie-hellman-group18-sha512 and + diffie-hellman-group16-sha512 + * Added ssh_get_fingerprint_hash() + * Added ssh_pki_export_privkey_base64() + * Added support for Match keyword in config file + * Improved performance and reduced memory footprint for sftp + * Fixed ecdsa publickey auth + * Fixed reading a closed channel + * Added support to announce posix-rename@openssh.com and + hardlink@openssh.com in the sftp server + +version 0.8.2 (released 2018-08-30) + * Added sha256 fingerprints for pubkeys + * Improved compiler flag detection + * Fixed race condition in reading sftp messages + * Fixed doxygen generation and added modern style + * Fixed library initialization on Windows + * Fixed __bounded__ attribute detection + * Fixed a bug in the options parser + * Fixed documentation for new knwon_hosts API + +version 0.8.1 (released 2018-08-13) + * Fixed version number in the header + * Fixed version number in pkg-config and cmake config + * Fixed library initialization + * Fixed attribute detection + +version 0.8.0 (released 2018-08-10) + * Removed support for deprecated SSHv1 protocol + * Added new connector API for clients + * Added new known_hosts parsing API + * Added support for OpenSSL 1.1 + * Added support for chacha20-poly1305 cipher + * Added crypto backend for mbedtls crypto library + * Added ECDSA support with gcrypt backend + * Added advanced client and server testing using cwrap.org + * Added support for curve25519-sha256 alias + * Added support for global known_hosts file + * Added support for symbol versioning + * Improved ssh_config parsing + * Improved threading support + +version 0.7.5 (released 2017-04-13) + * Fixed a memory allocation issue with buffers + * Fixed PKI on Windows + * Fixed some SSHv1 functions + * Fixed config hostname expansion + +version 0.7.4 (released 2017-02-03) + * Added id_ed25519 to the default identity list + * Fixed sftp EOF packet handling + * Fixed ssh_send_banner() to confirm with RFC 4253 + * Fixed some memory leaks + +version 0.7.3 (released 2016-01-23) + * Fixed CVE-2016-0739 + * Fixed ssh-agent on big endian + * Fixed some documentation issues + +version 0.7.2 (released 2015-09-15) + * Fixed OpenSSL detection on Windows + * Fixed return status for ssh_userauth_agent() + * Fixed KEX to prefer hmac-sha2-256 + * Fixed sftp packet handling + * Fixed return values of ssh_key_is_(public|private) + * Fixed bug in global success reply + +version 0.7.1 (released 2015-06-30) + * Fixed SSH_AUTH_PARTIAL auth with auto public key + * Fixed memory leak in session options + * Fixed allocation of ed25519 public keys + * Fixed channel exit-status and exit-signal + * Reintroduce ssh_forward_listen() + +version 0.7.0 (released 2015-05-11) + * Added support for ed25519 keys + * Added SHA2 algorithms for HMAC + * Added improved and more secure buffer handling code + * Added callback for auth_none_function + * Added support for ECDSA private key signing + * Added more tests + * Fixed a lot of bugs + * Improved API documentation + +version 0.6.5 (released 2015-04-29) + * Fixed CVE-2015-3146 + * Fixed port handling in config file + * Fixed the build with libgcrypt + * Fixed SFTP endian issues (rlo #179) + * Fixed uninitilized sig variable (rlo #167) + * Fixed polling issues which could result in a hang + * Fixed handling of EINTR in ssh_poll() (rlo #186) + * Fixed C99 issues with __func__ + * Fixed some memory leaks + * Improved macro detection on Windows + +version 0.6.4 (released 2014-12-19) + * Fixed CVE-2014-8132. + * Added SHA-2 for session ID signing with ECDSA keys. + * Added support for ECDSA host keys. + * Added support for more ECDSA hostkey algorithms. + * Added ssh_pki_key_ecdsa_name() API. + * Fixed setting the bindfd only after successful listen. + * Fixed issues with user created sockets. + * Fixed several issues in libssh C++ wrapper. + * Fixed several documentation issues. + * Fixed channel exit-signal request. + * Fixed X11 request screen number in messages. + * Fixed several memory leaks. + +version 0.6.3 (released 2014-03-04) + * Fixed CVE-2014-0017. + * Fixed memory leak with ecdsa signatures. + +version 0.6.2 (released 2014-03-04) + * security: fix for vulnerability CVE-2014-0017 + +version 0.6.1 (released 2014-02-08) + * Added support for libgcrypt 1.6. + * Added ssh_channel_accept_forward(). + * Added known_hosts heuristic during connection (#138). + * Added getters for session cipher names. + * Fixed decrypt of zero length buffer. + * Fixed padding in RSA signature blobs. + * Fixed DSA signature extraction. + * Fixed some memory leaks. + * Fixed read of non-connected socket. + * Fixed thread dectection. + +version 0.6.0 (released 2014-01-08) + * Added new publicy key API. + * Added new userauth API. + * Added ssh_get_publickey_hash() function. + * Added ssh_get_poll_flags() function. + * Added gssapi-mic userauth. + * Added GSSAPIServerIdentity option. + * Added GSSAPIClientIdentity option. + * Added GSSAPIDelegateCredentials option. + * Added new callback based server API. + * Added Elliptic Curve DSA (ECDSA) support (with OpenSSL). + * Added Elliptic Curve Diffie Hellman (ECDH) support. + * Added Curve25519 for ECDH key exchange. + * Added improved logging system. + * Added SSH-agent forwarding. + * Added key-reexchange. + * Added more unit tests. + * Improved documentation. + * Fixed timeout handling. + +version 0.5.5 (released 2013-07-26) + * BUG 103: Fix ProxyCommand parsing. + * Fix setting -D_FORTIFY_SOURCE=2. + * Fix pollset error return if emtpy. + * Fix NULL pointer checks in channel functions. + * Several bugfixes. + +version 0.5.4 (released 2013-01-22) + * CVE-2013-0176 - NULL dereference leads to denial of service + * Fixed several NULL pointer dereferences in SSHv1. + * Fixed a free crash bug in options parsing. + +version 0.5.3 (released 2012-11-20) + * CVE-2012-4559 Fixed multiple double free() flaws. + * CVE-2012-4560 Fixed multiple buffer overflow flaws. + * CVE-2012-4561 Fixed multiple invalid free() flaws. + * BUG #84 - Fix bug in sftp_mkdir not returning on error. + * BUG #85 - Fixed a possible channel infinite loop if the connection dropped. + * BUG #88 - Added missing channel request_state and set it to accepted. + * BUG #89 - Reset error state to no error on successful SSHv1 authentiction. + * Fixed a possible use after free in ssh_free(). + * Fixed multiple possible NULL pointer dereferences. + * Fixed multiple memory leaks in error paths. + * Fixed timeout handling. + * Fixed regression in pre-connected socket setting. + * Handle all unknown global messages. + +version 0.5.2 (released 2011-09-17) + * Increased window size x10. + * Fixed SSHv1. + * Fixed bugged lists. + * Fixed use-after-free + inconsistent callbacks call in poll. + * Fixed scp documentation. + * Fixed possible infinite loop in channel_read(). + * Fixed handling of short reads of sftp_async_read(). + * Fixed handling request service timeout in blocking mode. + * Fixed ssh_auth_list() documentation. + * Fixed incorrect return values in ssh_channel_write(). + * Fixed an infinite loop in the termination callback. + * Fixed handling of SSH_AGAIN in channel_open(). + * Fixed "status -5 inflating zlib packet" + +version 0.5.1 (released 2011-08-09) + * Added checks for NULL pointers in string.c. + * Set the channel max packet size to 32768. + * Don't (de)compress empty buffers. + * Fixed ssh_scp_write so it works when doing recursive copy. + * Fixed another source of endless wait. + * Fixed an endless loop in case of a channel_open error. + * Fixed session timeout handling. + * Fixed ssh_channel_from_local() loop. + * Fixed permissions of scp example when we copy a file. + * Workaround ssh_get_user_home_dir on LDAP users. + * Added pkg-config support for libssh_threads. + * Fixed compilation without server and sftp modes. + * Fix static .lib overwriting on Windows. + +version 0.5.0 (released 2011-06-01) + * Added ssh_ prefix to all functions. + * Added complete Windows support. + * Added improved server support. + * Added unit tests for a lot of functions. + * Added asynchronous service request. + * Added a multiplatform ssh_getpass() function. + * Added a tutorial. + * Added a lot of documentation. + * Fixed a lot of bugs. + * Fixed several memory leaks. + +version 0.4.8 (released 2011-01-15) + * Fixed memory leaks in session signing. + * Fixed memory leak in ssh_print_hexa. + * Fixed problem with ssh_connect w/ timeout and fd > 1024. + * Fixed some warnings on OS/2. + * Fixed installation path for OS/2. + +version 0.4.7 (released 2010-12-28) + * Fixed a possible memory leak in ssh_get_user_home(). + * Fixed a memory leak in sftp_xstat. + * Fixed uninitialized fd->revents member. + * Fixed timout value in ssh_channel_accept(). + * Fixed length checks in ssh_analyze_banner(). + * Fixed a possible data overread and crash bug. + * Fixed setting max_fd which breaks ssh_select(). + * Fixed some pedantic build warnings. + * Fixed a memory leak with session->bindaddr. + +version 0.4.6 (released 2010-09-03) + * Added a cleanup function to free the ws2_32 library. + * Fixed build with gcc 3.4. + * Fixed the Windows build on Vista and newer. + * Fixed the usage of WSAPoll() on Windows. + * Fixed "@deprecated" in doxygen + * Fixed some mingw warnings. + * Fixed handling of opened channels. + * Fixed keepalive problem on older openssh servers. + * Fixed testing for big endian on Windows. + * Fixed the Windows preprocessor macros and defines. + +version 0.4.5 (released 2010-07-13) + * Added option to bind a client to an ip address. + * Fixed the ssh socket polling function. + * Fixed Windows related bugs in bsd_poll(). + * Fixed serveral build warnings. + +version 0.4.4 (released 2010-06-01) + * Fixed a bug in the expand function for escape sequences. + * Fixed a bug in the tilde expand function. + * Fixed a bug in setting the options. + +version 0.4.3 (released 2010-05-18) + * Added global/keepalive responses. + * Added runtime detection of WSAPoll(). + * Added a select(2) based poll-emulation if poll(2) is not available. + * Added a function to expand an escaped string. + * Added a function to expand the tilde from a path. + * Added a proxycommand support. + * Added ssh_privatekey_type public function + * Added the possibility to define _OPENSSL_DIR and _ZLIB_DIR. + * Fixed sftp_chown. + * Fixed sftp_rename on protocol version 3. + * Fixed a blocking bug in channel_poll. + * Fixed config parsing wich has overwritten user specified values. + * Fixed hashed [host]:port format in knownhosts + * Fixed Windows build. + * Fixed doublefree happening after a negociation error. + * Fixed aes*-ctr with <= OpenSSL 0.9.7b. + * Fixed some documentation. + * Fixed exec example which has broken read usage. + * Fixed broken algorithm choice for server. + * Fixed a typo that we don't export all symbols. + * Removed the unneeded dependency to doxygen. + * Build examples only on the Linux plattform. + +version 0.4.2 (released 2010-03-15) + * Added owner and group information in sftp attributes. + * Added missing SSH_OPTIONS_FD option. + * Added printout of owner and group in the sftp example. + * Added a prepend function for ssh_list. + * Added send back replies to openssh's keepalives. + * Fixed documentation in scp code + * Fixed longname parsing, this only workings with readdir. + * Fixed and added support for several identity files. + * Fixed sftp_parse_longname() on Windows. + * Fixed a race condition bug in ssh_scp_close() + * Remove config support for SSHv1 Cipher variable. + * Rename ssh_list_add to ssh_list_append. + * Rename ssh_list_get_head to ssh_list_pop_head + +version 0.4.1 (released 2010-02-13) + * Added support for aes128-ctr, aes192-ctr and aes256-ctr encryption. + * Added an example for exec. + * Added private key type detection feature in privatekey_from_file(). + * Fixed zlib compression fallback. + * Fixed kex bug that client preference should be prioritary + * Fixed known_hosts file set by the user. + * Fixed a memleak in channel_accept(). + * Fixed underflow when leave_function() are unbalanced + * Fixed memory corruption in handle_channel_request_open(). + * Fixed closing of a file handle case of errors in privatekey_from_file(). + * Fixed ssh_get_user_home_dir() to be thread safe. + * Fixed the doxygen documentation. + +version 0.4.0 (released 2009-12-10) + * Added scp support. + * Added support for sending signals (RFC 4254, section 6.9). + * Added MSVC support. + * Added support for ~/.ssh/config. + * Added sftp extension support. + * Added X11 forwarding support for client. + * Added forward listening. + * Added support for openssh extensions (statvfs, fstatvfs). + * Added a cleaned up interface for setting options. + * Added a generic way to handle sockets asynchronously. + * Added logging of the sftp flags used to open a file. + * Added full poll() support and poll-emulation for win32. + * Added missing 64bit functions in sftp. + * Added support for ~/ and SSH_DIR/ in filenames instead of %s/. + * Fixed Fix channel_get_exit_status bug. + * Fixed calltrace logging to make it optional. + * Fixed compilation on Solaris. + * Fixed resolving of ip addresses. + * Fixed libssh compilation without server support. + * Fixed possible memory corruptions (ticket #14). + +version 0.3.4 (released 2009-09-14) + * Added ssh_basename and ssh_dirname. + * Added a portable ssh_mkdir function. + * Added a sftp_tell64() function. + * Added missing NULL pointer checks to crypt_set_algorithms_server. + * Fixed ssh_write_knownhost if ~/.ssh doesn't exist. + * Fixed a possible integer overflow in buffer_get_data(). + * Fixed possible security bug in packet_decrypt(). + * Fixed a possible stack overflow in agent code. + +version 0.3.3 (released 2009-08-18) + * Fixed double free pointer crash in dsa_public_to_string. + * Fixed channel_get_exit_status bug. + * Fixed ssh_finalize which didn't clear the flag. + * Fixed memory leak introduced by previous bugfix. + * Fixed channel_poll broken when delayed EOF recvd. + * Fixed stupid "can't parse known host key" bug. + * Fixed possible memory corruption (ticket #14). + +version 0.3.2 (released 2009-08-05) + * Added ssh_init() function. + * Added sftp_readlink() function. + * Added sftp_symlink() function. + * Fixed ssh_write_knownhost(). + * Fixed compilation on Solaris. + * Fixed SSHv1 compilation. + +version 0.3.1 (released 2009-07-14) + * Added return code SSH_SERVER_FILE_NOT_FOUND. + * Fixed compilation of SSHv1. + * Fixed several memory leaks. + * Fixed possible infinite loops. + * Fixed a possible crash bug. + * Fixed build warnings. + * Fixed cmake on BSD. +version 0.3.1 (released 2009-07-14) + * Added return code SSH_SERVER_FILE_NOT_FOUND. + * Fixed compilation of SSHv1. + * Fixed several memory leaks. + * Fixed possible infinite loops. + * Fixed a possible crash bug. + * Fixed build warnings. + * Fixed cmake on BSD. + +version 0.3 (released 2009-05-21) + * Added support for ssh-agent authentication. + * Added POSIX like sftp implementation. + * Added error checking to all functions. + * Added const to arguments where it was needed. + * Added a channel_get_exit_status() function. + * Added a channel_read_buffer() function, channel_read() is now + a POSIX like function. + * Added a more generic auth callback function. + * Added printf attribute checking for log and error functions. + * Added runtime function tracer support. + * Added NSIS build support with CPack. + * Added openssh hashed host support. + * Added API documentation for all public functions. + * Added asynchronous SFTP read function. + * Added a ssh_bind_set_fd() function. + * Fixed known_hosts parsing. + * Fixed a lot of build warnings. + * Fixed the Windows build. + * Fixed a lot of memory leaks. + * Fixed a double free corruption in the server support. + * Fixed the "ssh_accept:" bug in server support. + * Fixed important channel bugs. + * Refactored the socket handling. + * Switched to CMake build system. + * Improved performance. + +version 0.2 (released 2007-11-29) + * General cleanup + * More comprehensive API + * Up-to-date Doxygen documentation of each public function + * Basic server-based support + * Libgcrypt support (alternative to openssl and its license) + * SSH1 support (disabled by default) + * Added 3des-cbc + * A lot of bugfixes + +version 0.11-dev + * Server implementation development. + * Small bug corrected when connecting to sun ssh servers. + * Channel wierdness corrected (writing huge data packets) + * Channel_read_nonblocking added + * Channel bug where stderr wasn't correctly read fixed. + * Added sftp_file_set_nonblocking(), which is nonblocking SFTP IO + * Connect_status callback. + * Priv.h contains the internal functions, libssh.h the public interface + * Options_set_timeout (thx marcelo) really working. + * Tcp tunneling through channel_open_forward. + * Channel_request_exec() + * Channel_request_env() + * Ssh_get_pubkey_hash() + * Ssh_is_server_known() + * Ssh_write_known_host() + * Options_set_ssh_dir + * How could this happen ! there weren't any channel_close ! + * Nasty channel_free bug resolved. + * Removed the unsigned long all around the code. use only u8,u32 & u64. + * It now compiles and runs under amd64 ! + * Channel_request_pty_size + * Channel_change_pty_size + * Options_copy() + * Ported the doc to an HTML file. + * Small bugfix in packet.c + * Prefixed error constants with SSH_ + * Sftp_stat, sftp_lstat, sftp_fstat. thanks Michel Bardiaux for the patch. + * Again channel number mismatch fixed. + * Fixed a bug in ssh_select making the select fail when a signal has been + caught. + * Keyboard-interactive authentication working. + +version 0.1 (released 2004-03-05) + * Begining of sftp subsystem implementation. + * Some cleanup into channels implementation + * Now every channel functions is called by its CHANNEL handler. + * Added channel_poll() and channel_read(). + * Changed the client so it uses the new channel_poll and channel_read interface + * Small use-after-free bug with channels resolved + * Changed stupidities in lot of function names. + * Removed a debug output file opened by default. + * Added API.txt, the libssh programmer handbook. + * Various bug fixes from Nick Zitzmann. + * Developed a cryptographic structure for handling protocols. + * An autoconf script which took me half of a day to set up. + * A ssh_select wrapper has been written. + +version 0.0.4 (released 2003-10-10) + * Some terminal code (eof handling) added + * Channels bugfix (it still needs some tweaking though) + * Zlib support + * Added a wrapper.c file. The goal is to provide a similar API to every + cryptographic functions. bignums and sha/md5 are wrapped now. + * More work than it first looks. + * Support for other crypto libs planed (lighter libs) + * Fixed stupid select() bug. + * Libssh now compiles and links with openssl 0.9.6 + * RSA pubkey authentication code now works ! + +version 0.0.3 (released 2003-09-15) + * Added install target in makefile + * Some cleanup in headers files and source code + * Change default banner and project name to libssh. + * New file auth.c to support more and more authentication ways + * Bugfix(read offbyone) in send_kex + * A base64 parser. don't read the source, it's awful. pure 0xbadc0de. + * Changed the client filename to "ssh". logic isn't it ? + * Dss publickey authentication ! still need to wait for the rsa one + * Bugfix in packet.c + * New misc.c contains misc functions + +version 0.0.2 (released 2003-09-03) + * Initial release. + * Client supports both ssh and dss hostkey verification, but doesn't compare them to openssh's files. (~/.ssh/known_hosts) + * The only supported authentication method is password. + * Compiles on linux and openbsd. freebsd and netbsd should work, too + * Lot of work which hasn't been discussed here. diff --git a/CompilerChecks.cmake b/CompilerChecks.cmake new file mode 100644 index 0000000..5bdc05c --- /dev/null +++ b/CompilerChecks.cmake @@ -0,0 +1,122 @@ +include(AddCCompilerFlag) +include(CheckCCompilerFlagSSP) + +if (UNIX) + # + # Check for -Werror turned on if possible + # + # This will prevent that compiler flags are detected incorrectly. + # + check_c_compiler_flag("-Werror" REQUIRED_FLAGS_WERROR) + if (REQUIRED_FLAGS_WERROR) + set(CMAKE_REQUIRED_FLAGS "-Werror") + + if (PICKY_DEVELOPER) + list(APPEND SUPPORTED_COMPILER_FLAGS "-Werror") + endif() + endif() + + add_c_compiler_flag("-std=gnu99" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wpedantic" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wall" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wshadow" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wmissing-prototypes" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wcast-align" SUPPORTED_COMPILER_FLAGS) + #add_c_compiler_flag("-Wcast-qual" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=address" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wstrict-prototypes" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=strict-prototypes" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wwrite-strings" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=write-strings" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror-implicit-function-declaration" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wpointer-arith" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=pointer-arith" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wdeclaration-after-statement" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=declaration-after-statement" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wreturn-type" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=return-type" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wuninitialized" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=uninitialized" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wimplicit-fallthrough" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=strict-overflow" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wstrict-overflow=2" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wno-format-zero-length" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wmissing-field-initializers" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wsign-compare" SUPPORTED_COMPILER_FLAGS) + + check_c_compiler_flag("-Wformat" REQUIRED_FLAGS_WFORMAT) + if (REQUIRED_FLAGS_WFORMAT) + list(APPEND SUPPORTED_COMPILER_FLAGS "-Wformat") + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -Wformat") + endif() + add_c_compiler_flag("-Wformat-security" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=format-security" SUPPORTED_COMPILER_FLAGS) + + # Allow zero for a variadic macro argument + string(TOLOWER "${CMAKE_C_COMPILER_ID}" _C_COMPILER_ID) + if ("${_C_COMPILER_ID}" STREQUAL "clang") + add_c_compiler_flag("-Wno-gnu-zero-variadic-macro-arguments" SUPPORTED_COMPILER_FLAGS) + endif() + + add_c_compiler_flag("-fno-common" SUPPORTED_COMPILER_FLAGS) + + if (CMAKE_BUILD_TYPE) + string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) + if (CMAKE_BUILD_TYPE_LOWER MATCHES (release|relwithdebinfo|minsizerel)) + add_c_compiler_flag("-Wp,-D_FORTIFY_SOURCE=2" SUPPORTED_COMPILER_FLAGS) + endif() + endif() + + check_c_compiler_flag_ssp("-fstack-protector-strong" WITH_STACK_PROTECTOR_STRONG) + if (WITH_STACK_PROTECTOR_STRONG) + list(APPEND SUPPORTED_COMPILER_FLAGS "-fstack-protector-strong") + # This is needed as Solaris has a seperate libssp + if (SOLARIS) + list(APPEND SUPPORTED_LINKER_FLAGS "-fstack-protector-strong") + endif() + else (WITH_STACK_PROTECTOR_STRONG) + check_c_compiler_flag_ssp("-fstack-protector" WITH_STACK_PROTECTOR) + if (WITH_STACK_PROTECTOR) + list(APPEND SUPPORTED_COMPILER_FLAGS "-fstack-protector") + # This is needed as Solaris has a seperate libssp + if (SOLARIS) + list(APPEND SUPPORTED_LINKER_FLAGS "-fstack-protector") + endif() + endif() + endif (WITH_STACK_PROTECTOR_STRONG) + + check_c_compiler_flag_ssp("-fstack-clash-protection" WITH_STACK_CLASH_PROTECTION) + if (WITH_STACK_CLASH_PROTECTION) + list(APPEND SUPPORTED_COMPILER_FLAGS "-fstack-clash-protection") + endif() + + if (PICKY_DEVELOPER) + add_c_compiler_flag("-Wno-error=deprecated-declarations" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wno-error=tautological-compare" SUPPORTED_COMPILER_FLAGS) + endif() + + add_c_compiler_flag("-Wno-deprecated-declarations" DEPRECATION_COMPILER_FLAGS) + + # Unset CMAKE_REQUIRED_FLAGS + unset(CMAKE_REQUIRED_FLAGS) +endif() + +if (MSVC) + add_c_compiler_flag("/D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("/D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT=1" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("/D _CRT_NONSTDC_NO_WARNINGS=1" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("/D _CRT_SECURE_NO_WARNINGS=1" SUPPORTED_COMPILER_FLAGS) +endif() + +# This removes this annoying warning +# "warning: 'BN_CTX_free' is deprecated: first deprecated in OS X 10.7 [-Wdeprecated-declarations]" +if (OSX) + add_c_compiler_flag("-Wno-deprecated-declarations" SUPPORTED_COMPILER_FLAGS) +endif() + +set(DEFAULT_C_COMPILE_FLAGS ${SUPPORTED_COMPILER_FLAGS} CACHE INTERNAL "Default C Compiler Flags" FORCE) +set(DEFAULT_LINK_FLAGS ${SUPPORTED_LINKER_FLAGS} CACHE INTERNAL "Default C Linker Flags" FORCE) + +if (DEPRECATION_COMPILER_FLAGS) + set(DEFAULT_C_NO_DEPRECATION_FLAGS ${DEPRECATION_COMPILER_FLAGS} CACHE INTERNAL "Default no deprecation flags" FORCE) +endif() diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake new file mode 100644 index 0000000..c8bb2aa --- /dev/null +++ b/ConfigureChecks.cmake @@ -0,0 +1,470 @@ +include(CheckIncludeFile) +include(CheckIncludeFiles) +include(CheckSymbolExists) +include(CheckFunctionExists) +include(CheckLibraryExists) +include(CheckTypeSize) +include(CheckStructHasMember) +include(TestBigEndian) + +set(PACKAGE ${PROJECT_NAME}) +set(VERSION ${PROJECT_VERSION}) +set(SYSCONFDIR ${CMAKE_INSTALL_SYSCONFDIR}) + +set(BINARYDIR ${CMAKE_BINARY_DIR}) +set(SOURCEDIR ${CMAKE_SOURCE_DIR}) + +function(COMPILER_DUMPVERSION _OUTPUT_VERSION) + # Remove whitespaces from the argument. + # This is needed for CC="ccache gcc" cmake .. + string(REPLACE " " "" _C_COMPILER_ARG "${CMAKE_C_COMPILER_ARG1}") + + execute_process( + COMMAND + ${CMAKE_C_COMPILER} ${_C_COMPILER_ARG} -dumpversion + OUTPUT_VARIABLE _COMPILER_VERSION + ) + + string(REGEX REPLACE "([0-9])\\.([0-9])(\\.[0-9])?" "\\1\\2" + _COMPILER_VERSION "${_COMPILER_VERSION}") + + set(${_OUTPUT_VERSION} ${_COMPILER_VERSION} PARENT_SCOPE) +endfunction() + +if(CMAKE_COMPILER_IS_GNUCC AND NOT MINGW AND NOT OS2) + compiler_dumpversion(GNUCC_VERSION) + if (NOT GNUCC_VERSION EQUAL 34) + set(CMAKE_REQUIRED_FLAGS "-fvisibility=hidden") + check_c_source_compiles( +"void __attribute__((visibility(\"default\"))) test() {} +int main(void){ return 0; } +" WITH_VISIBILITY_HIDDEN) + unset(CMAKE_REQUIRED_FLAGS) + endif (NOT GNUCC_VERSION EQUAL 34) +endif(CMAKE_COMPILER_IS_GNUCC AND NOT MINGW AND NOT OS2) + +# HEADER FILES +set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} ${ARGP_INCLUDE_DIR}) +check_include_file(argp.h HAVE_ARGP_H) +unset(CMAKE_REQUIRED_INCLUDES) + +check_include_file(pty.h HAVE_PTY_H) +check_include_file(utmp.h HAVE_UTMP_H) +check_include_file(termios.h HAVE_TERMIOS_H) +check_include_file(unistd.h HAVE_UNISTD_H) +check_include_file(stdint.h HAVE_STDINT_H) +check_include_file(util.h HAVE_UTIL_H) +check_include_file(libutil.h HAVE_LIBUTIL_H) +check_include_file(sys/time.h HAVE_SYS_TIME_H) +check_include_file(sys/utime.h HAVE_SYS_UTIME_H) +check_include_file(sys/param.h HAVE_SYS_PARAM_H) +check_include_file(arpa/inet.h HAVE_ARPA_INET_H) +check_include_file(byteswap.h HAVE_BYTESWAP_H) +check_include_file(glob.h HAVE_GLOB_H) +check_include_file(valgrind/valgrind.h HAVE_VALGRIND_VALGRIND_H) + +if (WIN32) + check_include_file(io.h HAVE_IO_H) + + check_include_files("winsock2.h;ws2tcpip.h;wspiapi.h" HAVE_WSPIAPI_H) + if (NOT HAVE_WSPIAPI_H) + message(STATUS "WARNING: Without wspiapi.h, this build will only work on Windows XP and newer versions") + endif (NOT HAVE_WSPIAPI_H) + check_include_files("winsock2.h;ws2tcpip.h" HAVE_WS2TCPIP_H) +endif (WIN32) + +if (OPENSSL_FOUND) + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + check_include_file(openssl/des.h HAVE_OPENSSL_DES_H) + if (NOT HAVE_OPENSSL_DES_H) + message(FATAL_ERROR "Could not detect openssl/des.h") + endif() + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + check_include_file(openssl/aes.h HAVE_OPENSSL_AES_H) + if (NOT HAVE_OPENSSL_AES_H) + message(FATAL_ERROR "Could not detect openssl/aes.h") + endif() + + if (WITH_BLOWFISH_CIPHER) + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + check_include_file(openssl/blowfish.h HAVE_OPENSSL_BLOWFISH_H) + endif() + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + check_include_file(openssl/ecdh.h HAVE_OPENSSL_ECDH_H) + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + check_include_file(openssl/ec.h HAVE_OPENSSL_EC_H) + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + check_include_file(openssl/ecdsa.h HAVE_OPENSSL_ECDSA_H) + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) + check_function_exists(EVP_aes_128_ctr HAVE_OPENSSL_EVP_AES_CTR) + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) + check_function_exists(EVP_aes_128_cbc HAVE_OPENSSL_EVP_AES_CBC) + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) + check_function_exists(EVP_aes_128_gcm HAVE_OPENSSL_EVP_AES_GCM) + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) + check_function_exists(CRYPTO_THREADID_set_callback HAVE_OPENSSL_CRYPTO_THREADID_SET_CALLBACK) + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) + check_function_exists(CRYPTO_ctr128_encrypt HAVE_OPENSSL_CRYPTO_CTR128_ENCRYPT) + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) + check_function_exists(EVP_CIPHER_CTX_new HAVE_OPENSSL_EVP_CIPHER_CTX_NEW) + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) + check_function_exists(EVP_KDF_CTX_new_id HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID) + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) + check_function_exists(FIPS_mode HAVE_OPENSSL_FIPS_MODE) + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) + check_function_exists(RAND_priv_bytes HAVE_OPENSSL_RAND_PRIV_BYTES) + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) + check_function_exists(EVP_DigestSign HAVE_OPENSSL_EVP_DIGESTSIGN) + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) + check_function_exists(EVP_DigestVerify HAVE_OPENSSL_EVP_DIGESTVERIFY) + + check_function_exists(OPENSSL_ia32cap_loc HAVE_OPENSSL_IA32CAP_LOC) + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) + check_symbol_exists(EVP_PKEY_ED25519 "openssl/evp.h" FOUND_OPENSSL_ED25519) + + if (HAVE_OPENSSL_EVP_DIGESTSIGN AND HAVE_OPENSSL_EVP_DIGESTVERIFY AND + FOUND_OPENSSL_ED25519) + set(HAVE_OPENSSL_ED25519 1) + endif() + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) + check_symbol_exists(EVP_PKEY_X25519 "openssl/evp.h" HAVE_OPENSSL_X25519) + + unset(CMAKE_REQUIRED_INCLUDES) + unset(CMAKE_REQUIRED_LIBRARIES) +endif() + +if (CMAKE_HAVE_PTHREAD_H) + set(HAVE_PTHREAD_H 1) +endif (CMAKE_HAVE_PTHREAD_H) + +if (NOT WITH_GCRYPT AND NOT WITH_MBEDTLS) + if (HAVE_OPENSSL_EC_H AND HAVE_OPENSSL_ECDSA_H) + set(HAVE_OPENSSL_ECC 1) + endif (HAVE_OPENSSL_EC_H AND HAVE_OPENSSL_ECDSA_H) + + if (HAVE_OPENSSL_ECC) + set(HAVE_ECC 1) + endif (HAVE_OPENSSL_ECC) +endif () + +if (NOT WITH_MBEDTLS) + set(HAVE_DSA 1) +endif (NOT WITH_MBEDTLS) + +# FUNCTIONS + +check_function_exists(isblank HAVE_ISBLANK) +check_function_exists(strncpy HAVE_STRNCPY) +check_function_exists(strndup HAVE_STRNDUP) +check_function_exists(strtoull HAVE_STRTOULL) +check_function_exists(explicit_bzero HAVE_EXPLICIT_BZERO) +check_function_exists(memset_s HAVE_MEMSET_S) + +if (HAVE_GLOB_H) + check_struct_has_member(glob_t gl_flags glob.h HAVE_GLOB_GL_FLAGS_MEMBER) + check_function_exists(glob HAVE_GLOB) +endif (HAVE_GLOB_H) + +if (NOT WIN32) + check_function_exists(vsnprintf HAVE_VSNPRINTF) + check_function_exists(snprintf HAVE_SNPRINTF) +endif (NOT WIN32) + +if (WIN32) + check_symbol_exists(vsnprintf "stdio.h" HAVE_VSNPRINTF) + check_symbol_exists(snprintf "stdio.h" HAVE_SNPRINTF) + + check_symbol_exists(_vsnprintf_s "stdio.h" HAVE__VSNPRINTF_S) + check_symbol_exists(_vsnprintf "stdio.h" HAVE__VSNPRINTF) + check_symbol_exists(_snprintf "stdio.h" HAVE__SNPRINTF) + check_symbol_exists(_snprintf_s "stdio.h" HAVE__SNPRINTF_S) + + if (HAVE_WSPIAPI_H OR HAVE_WS2TCPIP_H) + check_symbol_exists(ntohll winsock2.h HAVE_NTOHLL) + check_symbol_exists(htonll winsock2.h HAVE_HTONLL) + + set(CMAKE_REQUIRED_LIBRARIES ws2_32) + check_symbol_exists(select "winsock2.h;ws2tcpip.h" HAVE_SELECT) + check_symbol_exists(poll "winsock2.h;ws2tcpip.h" HAVE_SELECT) + # The getaddrinfo function is defined to the WspiapiGetAddrInfo inline function + check_symbol_exists(getaddrinfo "winsock2.h;ws2tcpip.h" HAVE_GETADDRINFO) + unset(CMAKE_REQUIRED_LIBRARIES) + endif (HAVE_WSPIAPI_H OR HAVE_WS2TCPIP_H) + + check_function_exists(_strtoui64 HAVE__STRTOUI64) + + set(HAVE_SELECT TRUE) + + check_symbol_exists(SecureZeroMemory "windows.h" HAVE_SECURE_ZERO_MEMORY) +else (WIN32) + check_function_exists(poll HAVE_POLL) + check_function_exists(select HAVE_SELECT) + check_function_exists(getaddrinfo HAVE_GETADDRINFO) + + check_symbol_exists(ntohll arpa/inet.h HAVE_NTOHLL) + check_symbol_exists(htonll arpa/inet.h HAVE_HTONLL) +endif (WIN32) + + +if (UNIX) + if (NOT LINUX) + # libsocket (Solaris) + check_library_exists(socket getaddrinfo "" HAVE_LIBSOCKET) + if (HAVE_LIBSOCKET) + set(HAVE_GETADDRINFO TRUE) + set(_REQUIRED_LIBRARIES ${_REQUIRED_LIBRARIES} socket) + endif (HAVE_LIBSOCKET) + + # libnsl/inet_pton (Solaris) + check_library_exists(nsl inet_pton "" HAVE_LIBNSL) + if (HAVE_LIBNSL) + set(_REQUIRED_LIBRARIES ${_REQUIRED_LIBRARIES} nsl) + endif (HAVE_LIBNSL) + + # librt + check_library_exists(rt nanosleep "" HAVE_LIBRT) + endif (NOT LINUX) + + check_library_exists(rt clock_gettime "" HAVE_CLOCK_GETTIME) + if (HAVE_LIBRT OR HAVE_CLOCK_GETTIME) + set(_REQUIRED_LIBRARIES ${_REQUIRED_LIBRARIES} rt) + endif (HAVE_LIBRT OR HAVE_CLOCK_GETTIME) + + check_library_exists(util forkpty "" HAVE_LIBUTIL) + check_function_exists(cfmakeraw HAVE_CFMAKERAW) + check_function_exists(__strtoull HAVE___STRTOULL) +endif (UNIX) + +set(LIBSSH_REQUIRED_LIBRARIES ${_REQUIRED_LIBRARIES} CACHE INTERNAL "libssh required system libraries") + +# LIBRARIES +if (OPENSSL_FOUND) + set(HAVE_LIBCRYPTO 1) +endif (OPENSSL_FOUND) + +if (GCRYPT_FOUND) + set(HAVE_LIBGCRYPT 1) + if (GCRYPT_VERSION VERSION_GREATER "1.4.6") + set(HAVE_GCRYPT_ECC 1) + set(HAVE_ECC 1) + endif (GCRYPT_VERSION VERSION_GREATER "1.4.6") +endif (GCRYPT_FOUND) + +if (MBEDTLS_FOUND) + set(HAVE_LIBMBEDCRYPTO 1) + set(HAVE_ECC 1) +endif (MBEDTLS_FOUND) + +if (CMAKE_USE_PTHREADS_INIT) + set(HAVE_PTHREAD 1) +endif (CMAKE_USE_PTHREADS_INIT) + +if (UNIT_TESTING) + if (CMOCKA_FOUND) + set(CMAKE_REQUIRED_LIBRARIES ${CMOCKA_LIBRARIES}) + check_function_exists(cmocka_set_test_filter HAVE_CMOCKA_SET_TEST_FILTER) + unset(CMAKE_REQUIRED_LIBRARIES) + endif () +endif () + +# OPTIONS +check_c_source_compiles(" +__thread int tls; + +int main(void) { + return 0; +}" HAVE_GCC_THREAD_LOCAL_STORAGE) + +check_c_source_compiles(" +__declspec(thread) int tls; + +int main(void) { + return 0; +}" HAVE_MSC_THREAD_LOCAL_STORAGE) + +########################################################### +# For detecting attributes we need to treat warnings as +# errors +if (UNIX OR MINGW) + # Get warnings for attributs + check_c_compiler_flag("-Wattributes" REQUIRED_FLAGS_WERROR) + if (REQUIRED_FLAGS_WERROR) + string(APPEND CMAKE_REQUIRED_FLAGS "-Wattributes ") + endif() + + # Turn warnings into errors + check_c_compiler_flag("-Werror" REQUIRED_FLAGS_WERROR) + if (REQUIRED_FLAGS_WERROR) + string(APPEND CMAKE_REQUIRED_FLAGS "-Werror ") + endif() +endif () + +check_c_source_compiles(" +void test_constructor_attribute(void) __attribute__ ((constructor)); + +void test_constructor_attribute(void) +{ + return; +} + +int main(void) { + return 0; +}" HAVE_CONSTRUCTOR_ATTRIBUTE) + +check_c_source_compiles(" +void test_destructor_attribute(void) __attribute__ ((destructor)); + +void test_destructor_attribute(void) +{ + return; +} + +int main(void) { + return 0; +}" HAVE_DESTRUCTOR_ATTRIBUTE) + +check_c_source_compiles(" +#define FALL_THROUGH __attribute__((fallthrough)) + +int main(void) { + int i = 2; + + switch (i) { + case 0: + FALL_THROUGH; + case 1: + break; + default: + break; + } + + return 0; +}" HAVE_FALLTHROUGH_ATTRIBUTE) + +if (NOT WIN32) + check_c_source_compiles(" + #define __unused __attribute__((unused)) + + static int do_nothing(int i __unused) + { + return 0; + } + + int main(void) + { + int i; + + i = do_nothing(5); + if (i > 5) { + return 1; + } + + return 0; + }" HAVE_UNUSED_ATTRIBUTE) +endif() + +check_c_source_compiles(" +#include + +int main(void) +{ + char buf[] = \"This is some content\"; + + memset(buf, '\\\\0', sizeof(buf)); __asm__ volatile(\"\" : : \"g\"(&buf) : \"memory\"); + + return 0; +}" HAVE_GCC_VOLATILE_MEMORY_PROTECTION) + +check_c_source_compiles(" +#include +int main(void) { + printf(\"%s\", __func__); + return 0; +}" HAVE_COMPILER__FUNC__) + +check_c_source_compiles(" +#include +int main(void) { + printf(\"%s\", __FUNCTION__); + return 0; +}" HAVE_COMPILER__FUNCTION__) + +# This is only available with OpenBSD's gcc implementation */ +if (OPENBSD) +check_c_source_compiles(" +#define ARRAY_LEN 16 +void test_attr(const unsigned char *k) + __attribute__((__bounded__(__minbytes__, 2, 16))); + +int main(void) { + return 0; +}" HAVE_GCC_BOUNDED_ATTRIBUTE) +endif(OPENBSD) + +# Stop treating warnings as errors +unset(CMAKE_REQUIRED_FLAGS) + +# Check for version script support +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/conftest.map" "VERS_1 { + global: sym; +}; +VERS_2 { + global: sym; +} VERS_1; +") + +set(CMAKE_REQUIRED_FLAGS "-Wl,--version-script=\"${CMAKE_CURRENT_BINARY_DIR}/conftest.map\"") +check_c_source_compiles("int main(void) { return 0; }" HAVE_LD_VERSION_SCRIPT) +unset(CMAKE_REQUIRED_FLAGS) +file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/conftest.map") + +if (WITH_DEBUG_CRYPTO) + set(DEBUG_CRYPTO 1) +endif (WITH_DEBUG_CRYPTO) + +if (WITH_DEBUG_PACKET) + set(DEBUG_PACKET 1) +endif (WITH_DEBUG_PACKET) + +if (WITH_DEBUG_CALLTRACE) + set(DEBUG_CALLTRACE 1) +endif (WITH_DEBUG_CALLTRACE) + +if (WITH_GSSAPI AND NOT GSSAPI_FOUND) + set(WITH_GSSAPI 0) +endif (WITH_GSSAPI AND NOT GSSAPI_FOUND) + +# ENDIAN +if (NOT WIN32) + test_big_endian(WORDS_BIGENDIAN) +endif (NOT WIN32) diff --git a/DefineOptions.cmake b/DefineOptions.cmake new file mode 100644 index 0000000..b82a501 --- /dev/null +++ b/DefineOptions.cmake @@ -0,0 +1,55 @@ +option(WITH_GSSAPI "Build with GSSAPI support" ON) +option(WITH_ZLIB "Build with ZLIB support" ON) +option(WITH_SFTP "Build with SFTP support" ON) +option(WITH_SERVER "Build with SSH server support" ON) +option(WITH_DEBUG_CRYPTO "Build with cryto debug output" OFF) +option(WITH_DEBUG_PACKET "Build with packet debug output" OFF) +option(WITH_DEBUG_CALLTRACE "Build with calltrace debug output" ON) +option(WITH_GCRYPT "Compile against libgcrypt" OFF) +option(WITH_MBEDTLS "Compile against libmbedtls" OFF) +option(WITH_BLOWFISH_CIPHER "Compile with blowfish support" OFF) +option(WITH_PCAP "Compile with Pcap generation support" ON) +option(WITH_INTERNAL_DOC "Compile doxygen internal documentation" OFF) +option(BUILD_SHARED_LIBS "Build shared libraries" ON) +option(UNIT_TESTING "Build with unit tests" OFF) +option(CLIENT_TESTING "Build with client tests; requires openssh" OFF) +option(SERVER_TESTING "Build with server tests; requires openssh and dropbear" OFF) +option(WITH_BENCHMARKS "Build benchmarks tools" OFF) +option(WITH_EXAMPLES "Build examples" ON) +option(WITH_NACL "Build with libnacl (curve25519)" ON) +option(WITH_SYMBOL_VERSIONING "Build with symbol versioning" ON) +option(WITH_ABI_BREAK "Allow ABI break" OFF) +option(WITH_GEX "Enable DH Group exchange mechanisms" ON) +option(FUZZ_TESTING "Build with fuzzer for the server" OFF) +option(PICKY_DEVELOPER "Build with picky developer flags" OFF) + +if (WITH_ZLIB) + set(WITH_LIBZ ON) +else (WITH_ZLIB) + set(WITH_LIBZ OFF) +endif (WITH_ZLIB) + +if (WITH_BENCHMARKS) + set(UNIT_TESTING ON) + set(CLIENT_TESTING ON) +endif() + +if (UNIT_TESTING OR CLIENT_TESTING OR SERVER_TESTING) + set(BUILD_STATIC_LIB ON) +endif() + +if (WITH_NACL) + set(WITH_NACL ON) +endif (WITH_NACL) + +if (WITH_ABI_BREAK) + set(WITH_SYMBOL_VERSIONING ON) +endif (WITH_ABI_BREAK) + +if (NOT GLOBAL_BIND_CONFIG) + set(GLOBAL_BIND_CONFIG "/etc/ssh/libssh_server_config") +endif (NOT GLOBAL_BIND_CONFIG) + +if (NOT GLOBAL_CLIENT_CONFIG) + set(GLOBAL_CLIENT_CONFIG "/etc/ssh/ssh_config") +endif (NOT GLOBAL_CLIENT_CONFIG) diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..0f0cebf --- /dev/null +++ b/INSTALL @@ -0,0 +1,120 @@ +# How to build from source + +## Requirements + +### Common requirements + +In order to build libssh, you need to install several components: + +- A C compiler +- [CMake](https://www.cmake.org) >= 2.6.0. +- [openssl](https://www.openssl.org) >= 0.9.8 +or +- [gcrypt](https://www.gnu.org/directory/Security/libgcrypt.html) >= 1.4 +- [libz](https://www.zlib.net) >= 1.2 + +optional: +- [cmocka](https://cmocka.org/) >= 1.1.0 +- [socket_wrapper](https://cwrap.org/) >= 1.1.5 +- [nss_wrapper](https://cwrap.org/) >= 1.1.2 +- [uid_wrapper](https://cwrap.org/) >= 1.2.0 +- [pam_wrapper](https://cwrap.org/) >= 1.0.1 + +Note that these version numbers are version we know works correctly. If you +build and run libssh successfully with an older version, please let us know. + +For Windows use vcpkg: + +https://github.com/Microsoft/vcpkg + +which you can use to install openssl and zlib. libssh itself is also part of +vcpkg! + +## Building +First, you need to configure the compilation, using CMake. Go inside the +`build` dir. Create it if it doesn't exist. + +GNU/Linux, MacOS X, MSYS/MinGW: + + cmake -DUNIT_TESTING=ON -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug .. + make + +On Windows you should choose a makefile gernerator with -G or use + + cmake-gui.exe .. + +To enable additional client tests against a local OpenSSH server, add the +compile option -DCLIENT_TESTING=ON. These tests require an OpenSSH +server package and some wrapper libraries (see optional requirements) to +be installed. + +If you're interested in server testing, then a OpenSSH client should be +installed on the system and if possible also dropbear. Once that is done +enable server support with -DWITH_SERVER=ON and enable testing of it with +-DSERVER_TESTING=ON. + +## Testing build + + make test + +### CMake standard options +Here is a list of the most interesting options provided out of the box by +CMake. + +- CMAKE_BUILD_TYPE: The type of build (can be Debug Release MinSizeRel + RelWithDebInfo) +- CMAKE_INSTALL_PREFIX: The prefix to use when running make install (Default + to /usr/local on GNU/Linux and MacOS X) +- CMAKE_C_COMPILER: The path to the C compiler +- CMAKE_CXX_COMPILER: The path to the C++ compiler + +### CMake options defined for libssh + +Options are defined in the following files: + +- DefineOptions.cmake + +They can be changed with the -D option: + +`cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug -DWITH_ZLIB=OFF ..` + +### Browsing/editing CMake options + +In addition to passing options on the command line, you can browse and edit +CMake options using `cmakesetup` (Windows), `cmake-gui` or `ccmake` (GNU/Linux +and MacOS X). + +- Go to the build dir +- On Windows: run `cmakesetup` +- On GNU/Linux and MacOS X: run `ccmake ..` + +### Useful Windows options: + +If you have installed OpenSSL or ZLIB in non standard directories, maybe you +want to set: + +OPENSSL_ROOT_DIR + +and + +ZLIB_ROOT_DIR + +## Installing + +If you want to install libssh after compilation run: + + make install + +## Running + +The libssh binary can be found in the `build/src` directory. +You can use `build/examples/samplessh` which is a sample client to +test libssh on UNIX. + +## About this document + +This document is written using [Markdown][] syntax, making it possible to +provide usable information in both plain text and HTML format. Whenever +modifying this document please use [Markdown][] syntax. + +[markdown]: https://www.daringfireball.net/projects/markdown diff --git a/README b/README new file mode 100644 index 0000000..44100e7 --- /dev/null +++ b/README @@ -0,0 +1,44 @@ + _ _ _ _ + (_) (_) (_) (_) + (_) _ (_) _ _ _ _ _ (_) _ + (_) (_) (_)(_) _ (_)(_) (_)(_) (_)(_) _ + (_) (_) (_) (_) _ (_) _ (_) (_) (_) + (_) (_) (_)(_)(_) (_)(_) (_)(_) (_) (_).org + + The SSH library +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1* Why ? +-_-_-_-_-_ + +Why not ? :) I've began to work on my own implementation of the ssh protocol +because i didn't like the currently public ones. +Not any allowed you to import and use the functions as a powerful library, +and so i worked on a library-based SSH implementation which was non-existing +in the free and open source software world. + + +2* How/Who ? +-_-_-_-_-_-_-_ + +If you downloaded this file, you must know what it is : a library for +accessing ssh client services through C libraries calls in a simple manner. +Everybody can use this software under the terms of the LGPL - see the COPYING +file + +If you ask yourself how to compile libssh, please read INSTALL before anything. + +3* Where ? +-_-_-_-_-_-_ + +https://www.libssh.org + +4* Contributing +-_-_-_-_-_-_-_-_-_ + +Please read the file 'SubmittingPatches' next to this README file. It explains +our copyright policy and how you should send patches for upstream inclusion. + +Have fun and happy libssh hacking! + +The libssh Team diff --git a/README.CodingStyle b/README.CodingStyle new file mode 100644 index 0000000..7489cce --- /dev/null +++ b/README.CodingStyle @@ -0,0 +1,375 @@ +Coding conventions in the libssh tree +====================================== + +=========== +Quick Start +=========== + +Coding style guidelines are about reducing the number of unnecessary +reformatting patches and making things easier for developers to work together. + +You don't have to like them or even agree with them, but once put in place we +all have to abide by them (or vote to change them). However, coding style +should never outweigh coding itself and so the guidelines described here are +hopefully easy enough to follow as they are very common and supported by tools +and editors. + +The basic style for C code, is the Linux kernel coding style (See +Documentation/CodingStyle in the kernel source tree). This closely matches what +libssh developers use already anyways, with a few exceptions as mentioned +below. + +But to save you the trouble of reading the Linux kernel style guide, here +are the highlights. + +* Maximum Line Width is 80 Characters + The reason is not about people with low-res screens but rather sticking + to 80 columns prevents you from easily nesting more than one level of + if statements or other code blocks. + +* Use 4 Spaces to Indent + +* No Trailing Whitespace + Clean up your files before committing. + +* Follow the K&R guidelines. We won't go through all of them here. Do you + have a copy of "The C Programming Language" anyways right? + + +============= +Editor Hints +============= + +Emacs +------ +Add the follow to your $HOME/.emacs file: + + (add-hook 'c-mode-hook + (lambda () + (c-set-style "linux") + (c-toggle-auto-state))) + + +Vim +---- + +For the basic vi editor included with all variants of \*nix, add the +following to $HOME/.vimrc: + + set ts=4 sw=4 et cindent + +You can use the Vim gitmodline plugin to store this in the git config: + + https://git.cryptomilk.org/projects/vim-gitmodeline.git/ + +For Vim, the following settings in $HOME/.vimrc will also deal with +displaying trailing whitespace: + + if has("syntax") && (&t_Co > 2 || has("gui_running")) + syntax on + function! ActivateInvisibleCharIndicator() + syntax match TrailingSpace "[ \t]\+$" display containedin=ALL + highlight TrailingSpace ctermbg=Red + endf + autocmd BufNewFile,BufRead * call ActivateInvisibleCharIndicator() + endif + " Show tabs, trailing whitespace, and continued lines visually + set list listchars=tab:»·,trail:·,extends:… + + " highlight overly long lines same as TODOs. + set textwidth=80 + autocmd BufNewFile,BufRead *.c,*.h exec 'match Todo /\%>' . &textwidth . 'v.\+/' + + +========================== +FAQ & Statement Reference +========================== + +Comments +--------- + +Comments should always use the standard C syntax. C++ style comments are not +currently allowed. + +The lines before a comment should be empty. If the comment directly belongs to +the following code, there should be no empty line after the comment, except if +the comment contains a summary of multiple following code blocks. + +This is good: + + ... + int i; + + /* + * This is a multi line comment, + * which explains the logical steps we have to do: + * + * 1. We need to set i=5, because... + * 2. We need to call complex_fn1 + */ + + /* This is a one line comment about i = 5. */ + i = 5; + + /* + * This is a multi line comment, + * explaining the call to complex_fn1() + */ + ret = complex_fn1(); + if (ret != 0) { + ... + + /** + * @brief This is a doxygen comment. + * + * This is a more detailed explanation of + * this simple function. + * + * @param[in] param1 The parameter value of the function. + * + * @param[out] result1 The result value of the function. + * + * @return 0 on success and -1 on error. + */ + int example(int param1, int *result1); + +This is bad: + + ... + int i; + /* + * This is a multi line comment, + * which explains the logical steps we have to do: + * + * 1. We need to set i=5, because... + * 2. We need to call complex_fn1 + */ + /* This is a one line comment about i = 5. */ + i = 5; + /* + * This is a multi line comment, + * explaining the call to complex_fn1() + */ + ret = complex_fn1(); + if (ret != 0) { + ... + + /*This is a one line comment.*/ + + /* This is a multi line comment, + with some more words...*/ + + /* + * This is a multi line comment, + * with some more words...*/ + +Indention & Whitespace & 80 columns +------------------------------------ + +To avoid confusion, indentations have to be 4 spaces. Do not use tabs!. When +wrapping parameters for function calls, align the parameter list with the first +parameter on the previous line. For example, + + var1 = foo(arg1, + arg2, + arg3); + +The previous example is intended to illustrate alignment of function +parameters across lines and not as encourage for gratuitous line +splitting. Never split a line before columns 70 - 79 unless you +have a really good reason. Be smart about formatting. + + +If, switch, & Code blocks +-------------------------- + +Always follow an 'if' keyword with a space but don't include additional +spaces following or preceding the parentheses in the conditional. +This is good: + + if (x == 1) + +This is bad: + + if ( x == 1 ) + +or + + if (x==1) + +Yes we have a lot of code that uses the second and third form and we are trying +to clean it up without being overly intrusive. + +Note that this is a rule about parentheses following keywords and not +functions. Don't insert a space between the name and left parentheses when +invoking functions. + +Braces for code blocks used by for, if, switch, while, do..while, etc. should +begin on the same line as the statement keyword and end on a line of their own. +You should always include braces, even if the block only contains one +statement. NOTE: Functions are different and the beginning left brace should +be located in the first column on the next line. + +If the beginning statement has to be broken across lines due to length, the +beginning brace should be on a line of its own. + +The exception to the ending rule is when the closing brace is followed by +another language keyword such as else or the closing while in a do..while loop. + +Good examples: + + if (x == 1) { + printf("good\n"); + } + + for (x = 1; x < 10; x++) { + print("%d\n", x); + } + + for (really_really_really_really_long_var_name = 0; + really_really_really_really_long_var_name < 10; + really_really_really_really_long_var_name++) + { + print("%d\n", really_really_really_really_long_var_name); + } + + do { + printf("also good\n"); + } while (1); + +Bad examples: + + while (1) + { + print("I'm in a loop!\n"); } + + for (x=1; + x<10; + x++) + { + print("no good\n"); + } + + if (i < 10) + print("I should be in braces.\n"); + + +Goto +----- + +While many people have been academically taught that "goto"s are fundamentally +evil, they can greatly enhance readability and reduce memory leaks when used as +the single exit point from a function. But in no libssh world what so ever is a +goto outside of a function or block of code a good idea. + +Good Examples: + + int function foo(int y) + { + int *z = NULL; + int rc = 0; + + if (y < 10) { + z = malloc(sizeof(int)*y); + if (z == NULL) { + rc = 1; + goto done; + } + } + + print("Allocated %d elements.\n", y); + + done: + if (z != NULL) { + free(z); + } + + return rc; + } + +Initialize pointers +------------------- + +All pointer variables MUST be initialized to NULL. History has +demonstrated that uninitialized pointer variables have lead to various +bugs and security issues. + +Pointers MUST be initialized even if the assignment directly follows +the declaration, like pointer2 in the example below, because the +instructions sequence may change over time. + +Good Example: + + char *pointer1 = NULL; + char *pointer2 = NULL; + + pointer2 = some_func2(); + + ... + + pointer1 = some_func1(); + +Typedefs +--------- + +libssh tries to avoid "typedef struct { .. } x_t;" so we do always try to use +"struct x { .. };". We know there are still such typedefs in the code, but for +new code, please don't do that anymore. + +Make use of helper variables +----------------------------- + +Please try to avoid passing function calls as function parameters in new code. +This makes the code much easier to read and it's also easier to use the "step" +command within gdb. + +Good Example: + + char *name; + + name = get_some_name(); + if (name == NULL) { + ... + } + + rc = some_function_my_name(name); + ... + + +Bad Example: + + rc = some_function_my_name(get_some_name()); + ... + +Please try to avoid passing function return values to if- or while-conditions. +The reason for this is better handling of code under a debugger. + +Good example: + + x = malloc(sizeof(short) * 10); + if (x == NULL) { + fprintf(stderr, "Unable to alloc memory!\n"); + } + +Bad example: + + if ((x = malloc(sizeof(short)*10)) == NULL ) { + fprintf(stderr, "Unable to alloc memory!\n"); + } + +There are exceptions to this rule. One example is walking a data structure in +an iterator style: + + while ((opt = poptGetNextOpt(pc)) != -1) { + ... do something with opt ... + } + +But in general, please try to avoid this pattern. + + +Control-Flow changing macros +----------------------------- + +Macros like STATUS_NOT_OK_RETURN that change control flow (return/goto/etc) +from within the macro are considered bad, because they look like function calls +that never change control flow. Please do not introduce them. diff --git a/README.mbedtls b/README.mbedtls new file mode 100644 index 0000000..fdf3b25 --- /dev/null +++ b/README.mbedtls @@ -0,0 +1,11 @@ +mbedTLS and libssh in multithreaded applications +================================================== + +To use libssh with mbedTLS in a multithreaded application, mbedTLS has to be +built with threading support enabled. + +If threading support is not available and multi threading is used, ssh_init +will fail. + +More information about building mbedTLS with threading support can be found +in the mbedTLS documentation. diff --git a/README.md b/README.md new file mode 100644 index 0000000..450b67c --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +[![pipeline status](https://gitlab.com/libssh/libssh-mirror/badges/master/pipeline.svg)](https://gitlab.com/libssh/libssh-mirror/commits/master) + +``` + _ _ _ _ + (_) (_) (_) (_) + (_) _ (_) _ _ _ _ _ (_) _ + (_) (_) (_)(_) _ (_)(_) (_)(_) (_)(_) _ + (_) (_) (_) (_) _ (_) _ (_) (_) (_) + (_) (_) (_)(_)(_) (_)(_) (_)(_) (_) (_).org + + The SSH library + +``` + +# Why? + +Why not ? :) I've began to work on my own implementation of the ssh protocol +because i didn't like the currently public ones. +Not any allowed you to import and use the functions as a powerful library, +and so i worked on a library-based SSH implementation which was non-existing +in the free and open source software world. + + +# How/Who? + +If you downloaded this file, you must know what it is : a library for +accessing ssh client services through C libraries calls in a simple manner. +Everybody can use this software under the terms of the LGPL - see the COPYING +file + +If you ask yourself how to compile libssh, please read INSTALL before anything. + +# Where ? + +https://www.libssh.org + +# Contributing + +Please read the file 'SubmittingPatches' next to this README file. It explains +our copyright policy and how you should send patches for upstream inclusion. + +Have fun and happy libssh hacking! + +The libssh Team diff --git a/SubmittingPatches b/SubmittingPatches new file mode 100644 index 0000000..bd38fae --- /dev/null +++ b/SubmittingPatches @@ -0,0 +1,118 @@ +How to contribute a patch to libssh +==================================== + +Please checkout the libssh source code using git. Change the code and then +use "git format-patch" to create a patch. The patch should be signed (see +below) and send it to libssh@libssh.org, or attach it to a bug report at +https://red.libssh.org/ + +For larger code changes, breaking the changes up into a set of simple +patches, each of which does a single thing, are much easier to review. +Patch sets like that will most likely have an easier time being merged +into the libssh code than large single patches that make lots of +changes in one large diff. + +Ownership of the contributed code +================================== + +libssh is a project with distributed copyright ownership, which means +we prefer the copyright on parts of libssh to be held by individuals +rather than corporations if possible. There are historical legal +reasons for this, but one of the best ways to explain it is that it's +much easier to work with individuals who have ownership than corporate +legal departments if we ever need to make reasonable compromises with +people using and working with libssh. + +We track the ownership of every part of libssh via https://git.libssh.org, +our source code control system, so we know the provenance of every piece +of code that is committed to libssh. + +So if possible, if you're doing libssh changes on behalf of a company +who normally owns all the work you do please get them to assign +personal copyright ownership of your changes to you as an individual, +that makes things very easy for us to work with and avoids bringing +corporate legal departments into the picture. + +If you can't do this we can still accept patches from you owned by +your employer under a standard employment contract with corporate +copyright ownership. It just requires a simple set-up process first. + +We use a process very similar to the way things are done in the Linux +Kernel community, so it should be very easy to get a sign off from +your corporate legal department. The only changes we've made are to +accommodate the license we use, which is LGPLv2 (or later) whereas the +Linux kernel uses GPLv2. + +The process is called signing. + +How to sign your work +---------------------- + +Once you have permission to contribute to libssh from your employer, simply +email a copy of the following text from your corporate email address to: + +contributing@libssh.org + + + +libssh Developer's Certificate of Origin. Version 1.0 + + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the appropriate + version of the GNU General Public License; or + +(b) The contribution is based upon previous work that, to the best of + my knowledge, is covered under an appropriate open source license + and I have the right under that license to submit that work with + modifications, whether created in whole or in part by me, under + the GNU General Public License, in the appropriate version; or + +(c) The contribution was provided directly to me by some other + person who certified (a) or (b) and I have not modified it. + +(d) I understand and agree that this project and the contribution are + public and that a record of the contribution (including all + metadata and personal information I submit with it, including my + sign-off) is maintained indefinitely and may be redistributed + consistent with the libssh Team's policies and the requirements of + the GNU GPL where they are relevant. + +(e) I am granting this work to this project 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 the option of the project) any later version. + + https://www.gnu.org/licenses/lgpl-2.1.html + + +We will maintain a copy of that email as a record that you have the +rights to contribute code to libssh under the required licenses whilst +working for the company where the email came from. + +Then when sending in a patch via the normal mechanisms described +above, add a line that states: + + Signed-off-by: Random J Developer + +using your real name and the email address you sent the original email +you used to send the libssh Developer's Certificate of Origin to us +(sorry, no pseudonyms or anonymous contributions.) + +That's it! Such code can then quite happily contain changes that have +copyright messages such as: + + (c) Example Corporation. + +and can be merged into the libssh codebase in the same way as patches +from any other individual. You don't need to send in a copy of the +libssh Developer's Certificate of Origin for each patch, or inside each +patch. Just the sign-off message is all that is required once we've +received the initial email. + +Have fun and happy libssh hacking ! + +The libssh Team + diff --git a/cmake/Modules/AddCCompilerFlag.cmake b/cmake/Modules/AddCCompilerFlag.cmake new file mode 100644 index 0000000..c24c215 --- /dev/null +++ b/cmake/Modules/AddCCompilerFlag.cmake @@ -0,0 +1,21 @@ +# +# add_c_compiler_flag("-Werror" SUPPORTED_CFLAGS) +# +# Copyright (c) 2018 Andreas Schneider +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(CheckCCompilerFlag) + +macro(add_c_compiler_flag _COMPILER_FLAG _OUTPUT_VARIABLE) + string(TOUPPER ${_COMPILER_FLAG} _COMPILER_FLAG_NAME) + string(REGEX REPLACE "^-" "" _COMPILER_FLAG_NAME "${_COMPILER_FLAG_NAME}") + string(REGEX REPLACE "(-|=|\ )" "_" _COMPILER_FLAG_NAME "${_COMPILER_FLAG_NAME}") + + check_c_compiler_flag("${_COMPILER_FLAG}" WITH_${_COMPILER_FLAG_NAME}_FLAG) + if (WITH_${_COMPILER_FLAG_NAME}_FLAG) + #string(APPEND ${_OUTPUT_VARIABLE} "${_COMPILER_FLAG} ") + list(APPEND ${_OUTPUT_VARIABLE} ${_COMPILER_FLAG}) + endif() +endmacro() diff --git a/cmake/Modules/AddCMockaTest.cmake b/cmake/Modules/AddCMockaTest.cmake new file mode 100644 index 0000000..4b0c2da --- /dev/null +++ b/cmake/Modules/AddCMockaTest.cmake @@ -0,0 +1,120 @@ +# +# Copyright (c) 2007 Daniel Gollub +# Copyright (c) 2007-2018 Andreas Schneider +# Copyright (c) 2018 Anderson Toshiyuki Sasaki +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +#.rst: +# AddCMockaTest +# ------------- +# +# This file provides a function to add a test +# +# Functions provided +# ------------------ +# +# :: +# +# add_cmocka_test(target_name +# SOURCES src1 src2 ... srcN +# [COMPILE_OPTIONS opt1 opt2 ... optN] +# [LINK_LIBRARIES lib1 lib2 ... libN] +# [LINK_OPTIONS lopt1 lop2 .. loptN] +# ) +# +# ``target_name``: +# Required, expects the name of the test which will be used to define a target +# +# ``SOURCES``: +# Required, expects one or more source files names +# +# ``COMPILE_OPTIONS``: +# Optional, expects one or more options to be passed to the compiler +# +# ``LINK_LIBRARIES``: +# Optional, expects one or more libraries to be linked with the test +# executable. +# +# ``LINK_OPTIONS``: +# Optional, expects one or more options to be passed to the linker +# +# +# Example: +# +# .. code-block:: cmake +# +# add_cmocka_test(my_test +# SOURCES my_test.c other_source.c +# COMPILE_OPTIONS -g -Wall +# LINK_LIBRARIES mylib +# LINK_OPTIONS -Wl,--enable-syscall-fixup +# ) +# +# Where ``my_test`` is the name of the test, ``my_test.c`` and +# ``other_source.c`` are sources for the binary, ``-g -Wall`` are compiler +# options to be used, ``mylib`` is a target of a library to be linked, and +# ``-Wl,--enable-syscall-fixup`` is an option passed to the linker. +# + +enable_testing() +include(CTest) + +if (CMAKE_CROSSCOMPILING) + if (WIN32) + find_program(WINE_EXECUTABLE + NAMES wine) + set(TARGET_SYSTEM_EMULATOR ${WINE_EXECUTABLE}) + endif() +endif() + +function(ADD_CMOCKA_TEST _TARGET_NAME) + + set(one_value_arguments + ) + + set(multi_value_arguments + SOURCES + COMPILE_OPTIONS + LINK_LIBRARIES + LINK_OPTIONS + ) + + cmake_parse_arguments(_add_cmocka_test + "" + "${one_value_arguments}" + "${multi_value_arguments}" + ${ARGN} + ) + + if (NOT DEFINED _add_cmocka_test_SOURCES) + message(FATAL_ERROR "No sources provided for target ${_TARGET_NAME}") + endif() + + add_executable(${_TARGET_NAME} ${_add_cmocka_test_SOURCES}) + + if (DEFINED _add_cmocka_test_COMPILE_OPTIONS) + target_compile_options(${_TARGET_NAME} + PRIVATE ${_add_cmocka_test_COMPILE_OPTIONS} + ) + endif() + + if (DEFINED _add_cmocka_test_LINK_LIBRARIES) + target_link_libraries(${_TARGET_NAME} + PRIVATE ${_add_cmocka_test_LINK_LIBRARIES} + ) + endif() + + if (DEFINED _add_cmocka_test_LINK_OPTIONS) + set_target_properties(${_TARGET_NAME} + PROPERTIES LINK_FLAGS + ${_add_cmocka_test_LINK_OPTIONS} + ) + endif() + + add_test(${_TARGET_NAME} + ${TARGET_SYSTEM_EMULATOR} ${_TARGET_NAME} + ) + +endfunction (ADD_CMOCKA_TEST) diff --git a/cmake/Modules/COPYING-CMAKE-SCRIPTS b/cmake/Modules/COPYING-CMAKE-SCRIPTS new file mode 100644 index 0000000..4b41776 --- /dev/null +++ b/cmake/Modules/COPYING-CMAKE-SCRIPTS @@ -0,0 +1,22 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. diff --git a/cmake/Modules/CheckCCompilerFlagSSP.cmake b/cmake/Modules/CheckCCompilerFlagSSP.cmake new file mode 100644 index 0000000..ab206ca --- /dev/null +++ b/cmake/Modules/CheckCCompilerFlagSSP.cmake @@ -0,0 +1,29 @@ +# - Check whether the C compiler supports a given flag in the +# context of a stack checking compiler option. + +# CHECK_C_COMPILER_FLAG_SSP(FLAG VARIABLE) +# +# FLAG - the compiler flag +# VARIABLE - variable to store the result +# +# This actually calls check_c_source_compiles. +# See help for CheckCSourceCompiles for a listing of variables +# that can modify the build. + +# Copyright (c) 2006, Alexander Neundorf, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +# Requires cmake 3.10 +#include_guard(GLOBAL) +include(CheckCSourceCompiles) + +macro(CHECK_C_COMPILER_FLAG_SSP _FLAG _RESULT) + set(SAFE_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") + set(CMAKE_REQUIRED_FLAGS "${_FLAG}") + + check_c_source_compiles("int main(int argc, char **argv) { char buffer[256]; return buffer[argc]=0;}" ${_RESULT}) + + set(CMAKE_REQUIRED_FLAGS "${SAFE_CMAKE_REQUIRED_FLAGS}") +endmacro(CHECK_C_COMPILER_FLAG_SSP) diff --git a/cmake/Modules/DefineCMakeDefaults.cmake b/cmake/Modules/DefineCMakeDefaults.cmake new file mode 100644 index 0000000..ef4fb33 --- /dev/null +++ b/cmake/Modules/DefineCMakeDefaults.cmake @@ -0,0 +1,21 @@ +# Always include srcdir and builddir in include path +# This saves typing ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY} in +# about every subdir +# since cmake 2.4.0 +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Put the include dirs which are in the source or build tree +# before all other include dirs, so the headers in the sources +# are prefered over the already installed ones +# since cmake 2.4.1 +set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE ON) + +# Use colored output +# since cmake 2.4.0 +set(CMAKE_COLOR_MAKEFILE ON) + +# Create the compile command database for clang by default +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Always build with -fPIC +set(CMAKE_POSITION_INDEPENDENT_CODE ON) diff --git a/cmake/Modules/DefineCompilerFlags.cmake b/cmake/Modules/DefineCompilerFlags.cmake new file mode 100644 index 0000000..c9aea58 --- /dev/null +++ b/cmake/Modules/DefineCompilerFlags.cmake @@ -0,0 +1,49 @@ +if (UNIX AND NOT WIN32) + # Activate with: -DCMAKE_BUILD_TYPE=Profiling + set(CMAKE_C_FLAGS_PROFILING "-O0 -g -fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the C compiler during PROFILING builds.") + set(CMAKE_CXX_FLAGS_PROFILING "-O0 -g -fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the CXX compiler during PROFILING builds.") + set(CMAKE_SHARED_LINKER_FLAGS_PROFILING "-fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the linker during the creation of shared libraries during PROFILING builds.") + set(CMAKE_MODULE_LINKER_FLAGS_PROFILING "-fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the linker during the creation of shared libraries during PROFILING builds.") + set(CMAKE_EXEC_LINKER_FLAGS_PROFILING "-fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the linker during PROFILING builds.") + + # Activate with: -DCMAKE_BUILD_TYPE=AddressSanitizer + set(CMAKE_C_FLAGS_ADDRESSSANITIZER "-g -O1 -fsanitize=address -fno-omit-frame-pointer" + CACHE STRING "Flags used by the C compiler during ADDRESSSANITIZER builds.") + set(CMAKE_CXX_FLAGS_ADDRESSSANITIZER "-g -O1 -fsanitize=address -fno-omit-frame-pointer" + CACHE STRING "Flags used by the CXX compiler during ADDRESSSANITIZER builds.") + set(CMAKE_SHARED_LINKER_FLAGS_ADDRESSSANITIZER "-fsanitize=address" + CACHE STRING "Flags used by the linker during the creation of shared libraries during ADDRESSSANITIZER builds.") + set(CMAKE_MODULE_LINKER_FLAGS_ADDRESSSANITIZER "-fsanitize=address" + CACHE STRING "Flags used by the linker during the creation of shared libraries during ADDRESSSANITIZER builds.") + set(CMAKE_EXEC_LINKER_FLAGS_ADDRESSSANITIZER "-fsanitize=address" + CACHE STRING "Flags used by the linker during ADDRESSSANITIZER builds.") + + # Activate with: -DCMAKE_BUILD_TYPE=MemorySanitizer + set(CMAKE_C_FLAGS_MEMORYSANITIZER "-g -O2 -fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer" + CACHE STRING "Flags used by the C compiler during MEMORYSANITIZER builds.") + set(CMAKE_CXX_FLAGS_MEMORYSANITIZER "-g -O2 -fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer" + CACHE STRING "Flags used by the CXX compiler during MEMORYSANITIZER builds.") + set(CMAKE_SHARED_LINKER_FLAGS_MEMORYSANITIZER "-fsanitize=memory" + CACHE STRING "Flags used by the linker during the creation of shared libraries during MEMORYSANITIZER builds.") + set(CMAKE_MODULE_LINKER_FLAGS_MEMORYSANITIZER "-fsanitize=memory" + CACHE STRING "Flags used by the linker during the creation of shared libraries during MEMORYSANITIZER builds.") + set(CMAKE_EXEC_LINKER_FLAGS_MEMORYSANITIZER "-fsanitize=memory" + CACHE STRING "Flags used by the linker during MEMORYSANITIZER builds.") + + # Activate with: -DCMAKE_BUILD_TYPE=UndefinedSanitizer + set(CMAKE_C_FLAGS_UNDEFINEDSANITIZER "-g -O1 -fsanitize=undefined -fsanitize=null -fsanitize=alignment -fno-sanitize-recover" + CACHE STRING "Flags used by the C compiler during UNDEFINEDSANITIZER builds.") + set(CMAKE_CXX_FLAGS_UNDEFINEDSANITIZER "-g -O1 -fsanitize=undefined -fsanitize=null -fsanitize=alignment -fno-sanitize-recover" + CACHE STRING "Flags used by the CXX compiler during UNDEFINEDSANITIZER builds.") + set(CMAKE_SHARED_LINKER_FLAGS_UNDEFINEDSANITIZER "-fsanitize=undefined" + CACHE STRING "Flags used by the linker during the creation of shared libraries during UNDEFINEDSANITIZER builds.") + set(CMAKE_MODULE_LINKER_FLAGS_UNDEFINEDSANITIZER "-fsanitize=undefined" + CACHE STRING "Flags used by the linker during the creation of shared libraries during UNDEFINEDSANITIZER builds.") + set(CMAKE_EXEC_LINKER_FLAGS_UNDEFINEDSANITIZER "-fsanitize=undefined" + CACHE STRING "Flags used by the linker during UNDEFINEDSANITIZER builds.") +endif() diff --git a/cmake/Modules/DefinePlatformDefaults.cmake b/cmake/Modules/DefinePlatformDefaults.cmake new file mode 100644 index 0000000..77f8a46 --- /dev/null +++ b/cmake/Modules/DefinePlatformDefaults.cmake @@ -0,0 +1,32 @@ +# Set system vars + +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + set(LINUX TRUE) +endif(CMAKE_SYSTEM_NAME MATCHES "Linux") + +if (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + set(FREEBSD TRUE) + set(BSD TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + +if (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + set(OPENBSD TRUE) + set(BSD TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + +if (CMAKE_SYSTEM_NAME MATCHES "NetBSD") + set(NETBSD TRUE) + set(BSD TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "NetBSD") + +if (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + set(SOLARIS TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + +if (CMAKE_SYSTEM_NAME MATCHES "OS2") + set(OS2 TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "OS2") + +if (CMAKE_SYSTEM_NAME MATCHES "Darwin") + set (OSX TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "Darwin") diff --git a/cmake/Modules/ExtractSymbols.cmake b/cmake/Modules/ExtractSymbols.cmake new file mode 100644 index 0000000..f782933 --- /dev/null +++ b/cmake/Modules/ExtractSymbols.cmake @@ -0,0 +1,92 @@ +# +# Copyright (c) 2018 Anderson Toshiyuki Sasaki +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +#.rst: +# ExtractSymbols +# -------------- +# +# This is a helper script for FindABImap.cmake. +# +# Extract symbols from header files and output a list to a file. +# This script is run in build time to extract symbols from the provided header +# files. This way, symbols added or removed can be checked and used to update +# the symbol version script. +# +# All symbols followed by the character ``'('`` are extracted. If a +# ``FILTER_PATTERN`` is provided, only the lines containing the given string are +# considered. +# +# Expected defined variables +# -------------------------- +# +# ``HEADERS_LIST_FILE``: +# Required, expects a file containing the list of header files to be parsed. +# +# ``OUTPUT_PATH``: +# Required, expects the output file path. +# +# Optionally defined variables +# ---------------------------- +# +# ``FILTER_PATTERN``: +# Expects a string. Only lines containing the given string will be considered +# when extracting symbols. +# + +if (NOT DEFINED OUTPUT_PATH) + message(SEND_ERROR "OUTPUT_PATH not defined") +endif() + +if (NOT DEFINED HEADERS_LIST_FILE) + message(SEND_ERROR "HEADERS not defined") +endif() + +file(READ ${HEADERS_LIST_FILE} HEADERS_LIST) + +set(symbols) +foreach(header ${HEADERS_LIST}) + + # Filter only lines containing the FILTER_PATTERN + file(STRINGS ${header} contain_filter + REGEX "^.*${FILTER_PATTERN}.*[(]" + ) + + # Remove function-like macros + foreach(line ${contain_filter}) + if (NOT ${line} MATCHES ".*#[ ]*define") + list(APPEND not_macro ${line}) + endif() + endforeach() + + set(functions) + + # Get only the function names followed by '(' + foreach(line ${not_macro}) + string(REGEX MATCHALL "[a-zA-Z0-9_]+[ ]*[(]" func ${line}) + list(APPEND functions ${func}) + endforeach() + + set(extracted_symbols) + + # Remove '(' + foreach(line ${functions}) + string(REGEX REPLACE "[(]" "" symbol ${line}) + string(STRIP "${symbol}" symbol) + list(APPEND extracted_symbols ${symbol}) + endforeach() + + list(APPEND symbols ${extracted_symbols}) +endforeach() + +list(REMOVE_DUPLICATES symbols) + +list(SORT symbols) + +string(REPLACE ";" "\n" symbols_list "${symbols}") + +file(WRITE ${OUTPUT_PATH} "${symbols_list}") diff --git a/cmake/Modules/FindABIMap.cmake b/cmake/Modules/FindABIMap.cmake new file mode 100644 index 0000000..e0520ab --- /dev/null +++ b/cmake/Modules/FindABIMap.cmake @@ -0,0 +1,486 @@ +# +# Copyright (c) 2018 Anderson Toshiyuki Sasaki +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +#.rst: +# FindABIMap +# ---------- +# +# This file provides functions to generate the symbol version script. It uses +# the ``abimap`` tool to generate and update the linker script file. It can be +# installed by calling:: +# +# $ pip install abimap +# +# The ``function generate_map_file`` generates a symbol version script +# containing the provided symbols. It defines a custom command which sets +# ``target_name`` as its ``OUTPUT``. +# +# The experimental function ``extract_symbols()`` is provided as a simple +# parser to extract the symbols from C header files. It simply extracts symbols +# followed by an opening '``(``'. It is recommended to use a filter pattern to +# select the lines to be considered. It defines a custom command which sets +# ``target_name`` as its output. +# +# The helper function ``get_files_list()`` is provided to find files given a +# name pattern. It defines a custom command which sets ``target_name`` as its +# output. +# +# Functions provided +# ------------------ +# +# :: +# +# generate_map_file(target_name +# RELEASE_NAME_VERSION release_name +# SYMBOLS symbols_target +# [CURRENT_MAP cur_map] +# [FINAL] +# [BREAK_ABI] +# [COPY_TO output] +# ) +# +# ``target_name``: +# Required, expects the name of the file to receive the generated symbol +# version script. It should be added as a dependency for the library. Use the +# linker option ``--version-script filename`` to add the version information +# to the symbols when building the library. +# +# ``RELEASE_NAME_VERSION``: +# Required, expects a string containing the name and version information to be +# added to the symbols in the format ``lib_name_1_2_3``. +# +# ``SYMBOLS``: +# Required, expects a target with the property ``LIST_FILE`` containing a path +# to a file containing the list of symbols to be added to the symbol version +# script. +# +# ``CURRENT_MAP``: +# Optional. If given, the new set of symbols will be checked against the +# ones contained in the ``cur_map`` file and updated properly. If an +# incompatible change is detected and ``BREAK_ABI`` is not defined, the build +# will fail. +# +# ``FINAL``: +# Optional. If given, will provide the ``--final`` option to ``abimap`` tool, +# which will mark the modified release in the symbol version script with a +# special comment, preventing later changes. This option should be set when +# creating a library release and the resulting map file should be stored with +# the source code. +# +# ``BREAK_ABI``: +# Optional. If provided, will use ``abimap`` ``--allow-abi-break`` option, which +# accepts incompatible changes to the set of symbols. This is necessary if any +# previously existing symbol were removed. +# +# ``COPY_TO``: +# Optional, expects a string containing the path to where the generated +# map file will be copied. +# +# Example: +# +# .. code-block:: cmake +# +# find_package(ABIMap) +# generate_map_file("lib.map" +# RELEASE_NAME_VERSION "lib_1_0_0" +# SYMBOLS symbols +# ) +# +# Where the target ``symbols`` has its property ``LIST_FILE`` set to the path to +# a file containing:: +# +# ``symbol1`` +# ``symbol2`` +# +# This example would result in the symbol version script to be created in +# ``${CMAKE_CURRENT_BINARY_DIR}/lib.map`` containing the provided symbols. +# +# :: +# +# get_files_list(target_name +# DIRECTORIES dir1 [dir2 ...] +# FILES_PATTERNS exp1 [exp2 ...] +# [COPY_TO output] +# ) +# +# ``target_name``: +# Required, expects the name of the target to be created. A file named as +# ``${target_name}.list`` will be created in +# ``${CMAKE_CURRENT_BINARY_DIR}`` to receive the list of files found. +# +# ``DIRECTORIES``: +# Required, expects a list of directories paths. Only absolute paths are +# supported. +# +# ``FILES_PATTERN``: +# Required, expects a list of matching expressions to find the files to be +# considered in the directories. +# +# ``COPY_TO``: +# Optional, expects a string containing the path to where the file containing +# the list of files will be copied. +# +# This command searches the directories provided in ``DIRECTORIES`` for files +# matching any of the patterns provided in ``FILES_PATTERNS``. The obtained list +# is written to the path specified by ``output``. A target named ``target_name`` +# will be created and its property ``LIST_FILE`` will be set to contain +# ``${CMAKE_CURRENT_BINARY_DIR}/${target_name}.list`` +# +# Example: +# +# .. code-block:: cmake +# +# find_package(ABIMap) +# get_files_list(target +# DIRECTORIES "/include/mylib" +# FILES_PATTERNS "*.h" +# COPY_TO "my_list.txt" +# ) +# +# Consider that ``/include/mylib`` contains 3 files, ``h1.h``, ``h2.h``, and +# ``h3.hpp`` +# +# Will result in a file ``my_list.txt`` containing:: +# +# ``h1.h;h2.h`` +# +# And the target ``target`` will have its property ``LIST_FILE`` set to contain +# ``${CMAKE_CURRENT_BINARY_DIR}/target.list`` +# +# :: +# +# extract_symbols(target_name +# HEADERS_LIST headers_list_target +# [FILTER_PATTERN pattern] +# [COPY_TO output] +# ) +# +# ``target_name``: +# Required, expects the name of the target to be created. A file named after +# the string given in ``target_name`` will be created in +# ``${CMAKE_CURRENT_BINARY_DIR}`` to receive the list of symbols. +# +# ``HEADERS_LIST``: +# Required, expects a target with the property ``LIST_FILE`` set, containing a +# file path. Such file must contain a list of files paths. +# +# ``FILTER_PATTERN``: +# Optional, expects a string. Only the lines containing the filter pattern +# will be considered. +# +# ``COPY_TO``: +# Optional, expects a string containing the path to where the file containing +# the found symbols will be copied. +# +# This command extracts the symbols from the files listed in +# ``headers_list`` and write them on the ``output`` file. If ``pattern`` +# is provided, then only the lines containing the string given in ``pattern`` +# will be considered. It is recommended to provide a ``FILTER_PATTERN`` to mark +# the lines containing exported function declaration, since this function is +# experimental and can return wrong symbols when parsing the header files. A +# target named ``target_name`` will be created with the property ``LIST_FILE`` +# set to contain ``${CMAKE_CURRENT_BINARY_DIR}/${target_name}.list``. +# +# Example: +# +# .. code-block:: cmake +# +# find_package(ABIMap) +# extract_symbols("lib.symbols" +# HEADERS_LIST "headers_target" +# FILTER_PATTERN "API_FUNCTION" +# ) +# +# Where ``LIST_FILE`` property in ``headers_target`` points to a file +# containing:: +# +# header1.h;header2.h +# +# Where ``header1.h`` contains:: +# +# API_FUNCTION int exported_func1(int a, int b); +# +# ``header2.h`` contains:: +# +# API_FUNCTION int exported_func2(int a); +# +# int private_func2(int b); +# +# Will result in a file ``lib.symbols.list`` in ``${CMAKE_CURRENT_BINARY_DIR}`` +# containing:: +# +# ``exported_func1`` +# ``exported_func2`` +# + +# Search for python which is required +if (ABIMap_FIND_REQURIED) + find_package(PythonInterp REQUIRED) +else() + find_package(PythonInterp) +endif() + + +if (PYTHONINTERP_FOUND) + # Search for abimap tool used to generate the map files + find_program(ABIMAP_EXECUTABLE NAMES abimap DOC "path to the abimap executable") + mark_as_advanced(ABIMAP_EXECUTABLE) + + if (NOT ABIMAP_EXECUTABLE AND UNIX) + message(STATUS "Could not find `abimap` in PATH." + " It can be found in PyPI as `abimap`" + " (try `pip install abimap`)") + endif () + + if (ABIMAP_EXECUTABLE) + # Get the abimap version + execute_process(COMMAND ${ABIMAP_EXECUTABLE} version + OUTPUT_VARIABLE ABIMAP_VERSION_STRING + OUTPUT_STRIP_TRAILING_WHITESPACE) + + # If the version string starts with abimap-, strip it + if ("abimap" STRLESS_EQUAL ${ABIMAP_VERSION_STRING}) + string(REGEX REPLACE "abimap-" "" ABIMAP_VERSION_STRING "${ABIMAP_VERSION_STRING}") + endif() + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(ABIMap + REQUIRED_VARS ABIMAP_EXECUTABLE + VERSION_VAR ABIMAP_VERSION_STRING) +endif() + + +if (ABIMAP_FOUND) + +# Define helper scripts +set(_EXTRACT_SYMBOLS_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/ExtractSymbols.cmake) +set(_GENERATE_MAP_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/GenerateMap.cmake) +set(_GET_FILES_LIST_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/GetFilesList.cmake) + +function(get_file_list _TARGET_NAME) + + set(one_value_arguments + COPY_TO + ) + + set(multi_value_arguments + DIRECTORIES + FILES_PATTERNS + ) + + cmake_parse_arguments(_get_files_list + "" + "${one_value_arguments}" + "${multi_value_arguments}" + ${ARGN} + ) + + # The DIRS argument is required + if (NOT DEFINED _get_files_list_DIRECTORIES) + message(FATAL_ERROR "No directories paths provided. Provide a list of" + " directories paths containing header files.") + endif() + + # The FILES_PATTERNS argument is required + if (NOT DEFINED _get_files_list_FILES_PATTERNS) + message(FATAL_ERROR "No matching expressions provided. Provide a list" + " of matching patterns for the header files.") + endif() + + set(_FILES_LIST_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/${_TARGET_NAME}.list) + + get_filename_component(_get_files_list_OUTPUT_PATH + "${_FILES_LIST_OUTPUT_PATH}" + ABSOLUTE) + + add_custom_target( + ${_TARGET_NAME}_int ALL + COMMAND ${CMAKE_COMMAND} + -DOUTPUT_PATH="${_get_files_list_OUTPUT_PATH}" + -DDIRECTORIES="${_get_files_list_DIRECTORIES}" + -DFILES_PATTERNS="${_get_files_list_FILES_PATTERNS}" + -P ${_GET_FILES_LIST_SCRIPT} + COMMENT + "Searching for files" + ) + + if (DEFINED _get_files_list_COPY_TO) + # Copy the generated file back to the COPY_TO + add_custom_target(${_TARGET_NAME} ALL + COMMAND + ${CMAKE_COMMAND} -E copy_if_different + ${_FILES_LIST_OUTPUT_PATH} ${_get_files_list_COPY_TO} + DEPENDS ${_TARGET_NAME}_int + COMMENT "Copying ${_TARGET_NAME} to ${_get_files_list_COPY_TO}" + ) + else() + add_custom_target(${_TARGET_NAME} ALL + DEPENDS ${_TARGET_NAME}_int + ) + endif() + + set_target_properties(${_TARGET_NAME} + PROPERTIES LIST_FILE ${_FILES_LIST_OUTPUT_PATH} + ) + +endfunction() + +function(extract_symbols _TARGET_NAME) + + set(one_value_arguments + FILTER_PATTERN + HEADERS_LIST + COPY_TO + ) + + set(multi_value_arguments + ) + + cmake_parse_arguments(_extract_symbols + "" + "${one_value_arguments}" + "${multi_value_arguments}" + ${ARGN} + ) + + # The HEADERS_LIST_FILE argument is required + if (NOT DEFINED _extract_symbols_HEADERS_LIST) + message(FATAL_ERROR "No target provided in HEADERS_LIST. Provide a" + " target with the property LIST_FILE set as the" + " path to the file containing the list of headers.") + endif() + + get_filename_component(_SYMBOLS_OUTPUT_PATH + "${CMAKE_CURRENT_BINARY_DIR}/${_TARGET_NAME}.list" + ABSOLUTE + ) + + get_target_property(_HEADERS_LIST_FILE + ${_extract_symbols_HEADERS_LIST} + LIST_FILE + ) + + add_custom_target( + ${_TARGET_NAME}_int ALL + COMMAND ${CMAKE_COMMAND} + -DOUTPUT_PATH="${_SYMBOLS_OUTPUT_PATH}" + -DHEADERS_LIST_FILE="${_HEADERS_LIST_FILE}" + -DFILTER_PATTERN=${_extract_symbols_FILTER_PATTERN} + -P ${_EXTRACT_SYMBOLS_SCRIPT} + DEPENDS ${_extract_symbols_HEADERS_LIST} + COMMENT "Extracting symbols from headers" + ) + + if (DEFINED _extract_symbols_COPY_TO) + # Copy the generated file back to the COPY_TO + add_custom_target(${_TARGET_NAME} ALL + COMMAND + ${CMAKE_COMMAND} -E copy_if_different + ${_SYMBOLS_OUTPUT_PATH} ${_extract_symbols_COPY_TO} + DEPENDS ${_TARGET_NAME}_int + COMMENT "Copying ${_TARGET_NAME} to ${_extract_symbols_COPY_TO}" + ) + else() + add_custom_target(${_TARGET_NAME} ALL + DEPENDS ${_TARGET_NAME}_int + ) + endif() + + set_target_properties(${_TARGET_NAME} + PROPERTIES LIST_FILE ${_SYMBOLS_OUTPUT_PATH} + ) + +endfunction() + +function(generate_map_file _TARGET_NAME) + + set(options + FINAL + BREAK_ABI + ) + + set(one_value_arguments + RELEASE_NAME_VERSION + SYMBOLS + CURRENT_MAP + COPY_TO + ) + + set(multi_value_arguments + ) + + cmake_parse_arguments(_generate_map_file + "${options}" + "${one_value_arguments}" + "${multi_value_arguments}" + ${ARGN} + ) + + if (NOT DEFINED _generate_map_file_SYMBOLS) + message(FATAL_ERROR "No target provided in SYMBOLS. Provide a target" + " with the property LIST_FILE set as the path to" + " the file containing the list of symbols.") + endif() + + if (NOT DEFINED _generate_map_file_RELEASE_NAME_VERSION) + message(FATAL_ERROR "Release name and version not provided." + " (e.g. libname_1_0_0)") + endif() + + + get_target_property(_SYMBOLS_FILE + ${_generate_map_file_SYMBOLS} + LIST_FILE + ) + + # Set generated map file path + get_filename_component(_MAP_OUTPUT_PATH + "${CMAKE_CURRENT_BINARY_DIR}/${_TARGET_NAME}" + ABSOLUTE + ) + + add_custom_target( + ${_TARGET_NAME}_int ALL + COMMAND ${CMAKE_COMMAND} + -DABIMAP_EXECUTABLE=${ABIMAP_EXECUTABLE} + -DSYMBOLS="${_SYMBOLS_FILE}" + -DCURRENT_MAP=${_generate_map_file_CURRENT_MAP} + -DOUTPUT_PATH="${_MAP_OUTPUT_PATH}" + -DFINAL=${_generate_map_file_FINAL} + -DBREAK_ABI=${_generate_map_file_BREAK_ABI} + -DRELEASE_NAME_VERSION=${_generate_map_file_RELEASE_NAME_VERSION} + -P ${_GENERATE_MAP_SCRIPT} + DEPENDS ${_generate_map_file_SYMBOLS} + COMMENT "Generating the map ${_TARGET_NAME}" + ) + + # Add a custom command setting the map as OUTPUT to allow it to be added as + # a generated source + add_custom_command( + OUTPUT ${_MAP_OUTPUT_PATH} + DEPENDS ${_TARGET_NAME} + ) + + if (DEFINED _generate_map_file_COPY_TO) + # Copy the generated map back to the COPY_TO + add_custom_target(${_TARGET_NAME} ALL + COMMAND + ${CMAKE_COMMAND} -E copy_if_different ${_MAP_OUTPUT_PATH} + ${_generate_map_file_COPY_TO} + DEPENDS ${_TARGET_NAME}_int + COMMENT "Copying ${_MAP_OUTPUT_PATH} to ${_generate_map_file_COPY_TO}" + ) + else() + add_custom_target(${_TARGET_NAME} ALL + DEPENDS ${_TARGET_NAME}_int + ) + endif() +endfunction() + +endif (ABIMAP_FOUND) diff --git a/cmake/Modules/FindArgp.cmake b/cmake/Modules/FindArgp.cmake new file mode 100644 index 0000000..454965a --- /dev/null +++ b/cmake/Modules/FindArgp.cmake @@ -0,0 +1,66 @@ +# - Try to find ARGP +# Once done this will define +# +# ARGP_ROOT_DIR - Set this variable to the root installation of ARGP +# +# Read-Only variables: +# ARGP_FOUND - system has ARGP +# ARGP_INCLUDE_DIR - the ARGP include directory +# ARGP_LIBRARIES - Link these to use ARGP +# ARGP_DEFINITIONS - Compiler switches required for using ARGP +# +#============================================================================= +# Copyright (c) 2011-2016 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +set(_ARGP_ROOT_HINTS +) + +set(_ARGP_ROOT_PATHS + "$ENV{PROGRAMFILES}/argp" +) + +find_path(ARGP_ROOT_DIR + NAMES + include/argp.h + HINTS + ${_ARGP_ROOT_HINTS} + PATHS + ${_ARGP_ROOT_PATHS} +) +mark_as_advanced(ARGP_ROOT_DIR) + +find_path(ARGP_INCLUDE_DIR + NAMES + argp.h + PATHS + ${ARGP_ROOT_DIR}/include +) + +find_library(ARGP_LIBRARY + NAMES + argp + PATHS + ${ARGP_ROOT_DIR}/lib +) + +if (ARGP_LIBRARY) + set(ARGP_LIBRARIES + ${ARGP_LIBRARIES} + ${ARGP_LIBRARY} + ) +endif (ARGP_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(ARGP DEFAULT_MSG ARGP_LIBRARIES ARGP_INCLUDE_DIR) + +# show the ARGP_INCLUDE_DIR and ARGP_LIBRARIES variables only in the advanced view +mark_as_advanced(ARGP_INCLUDE_DIR ARGP_LIBRARIES) diff --git a/cmake/Modules/FindCMocka.cmake b/cmake/Modules/FindCMocka.cmake new file mode 100644 index 0000000..76b4ba7 --- /dev/null +++ b/cmake/Modules/FindCMocka.cmake @@ -0,0 +1,66 @@ +# - Try to find CMocka +# Once done this will define +# +# CMOCKA_ROOT_DIR - Set this variable to the root installation of CMocka +# +# Read-Only variables: +# CMOCKA_FOUND - system has CMocka +# CMOCKA_INCLUDE_DIR - the CMocka include directory +# CMOCKA_LIBRARIES - Link these to use CMocka +# CMOCKA_DEFINITIONS - Compiler switches required for using CMocka +# +#============================================================================= +# Copyright (c) 2011-2012 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +set(_CMOCKA_ROOT_HINTS +) + +set(_CMOCKA_ROOT_PATHS + "$ENV{PROGRAMFILES}/cmocka" +) + +find_path(CMOCKA_ROOT_DIR + NAMES + include/cmocka.h + HINTS + ${_CMOCKA_ROOT_HINTS} + PATHS + ${_CMOCKA_ROOT_PATHS} +) +mark_as_advanced(CMOCKA_ROOT_DIR) + +find_path(CMOCKA_INCLUDE_DIR + NAMES + cmocka.h + PATHS + ${CMOCKA_ROOT_DIR}/include +) + +find_library(CMOCKA_LIBRARY + NAMES + cmocka + PATHS + ${CMOCKA_ROOT_DIR}/lib +) + +if (CMOCKA_LIBRARY) + set(CMOCKA_LIBRARIES + ${CMOCKA_LIBRARIES} + ${CMOCKA_LIBRARY} + ) +endif (CMOCKA_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(CMocka DEFAULT_MSG CMOCKA_LIBRARIES CMOCKA_INCLUDE_DIR) + +# show the CMOCKA_INCLUDE_DIR and CMOCKA_LIBRARIES variables only in the advanced view +mark_as_advanced(CMOCKA_INCLUDE_DIR CMOCKA_LIBRARIES) diff --git a/cmake/Modules/FindGCrypt.cmake b/cmake/Modules/FindGCrypt.cmake new file mode 100644 index 0000000..b1f73d8 --- /dev/null +++ b/cmake/Modules/FindGCrypt.cmake @@ -0,0 +1,87 @@ +# - Try to find GCrypt +# Once done this will define +# +# GCRYPT_FOUND - system has GCrypt +# GCRYPT_INCLUDE_DIRS - the GCrypt include directory +# GCRYPT_LIBRARIES - Link these to use GCrypt +# GCRYPT_DEFINITIONS - Compiler switches required for using GCrypt +# +#============================================================================= +# Copyright (c) 2009-2012 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +set(_GCRYPT_ROOT_HINTS + $ENV{GCRYTPT_ROOT_DIR} + ${GCRYPT_ROOT_DIR}) + +set(_GCRYPT_ROOT_PATHS + "$ENV{PROGRAMFILES}/libgcrypt") + +set(_GCRYPT_ROOT_HINTS_AND_PATHS + HINTS ${_GCRYPT_ROOT_HINTS} + PATHS ${_GCRYPT_ROOT_PATHS}) + + +find_path(GCRYPT_INCLUDE_DIR + NAMES + gcrypt.h + HINTS + ${_GCRYPT_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + include +) + +find_library(GCRYPT_LIBRARY + NAMES + gcrypt + gcrypt11 + libgcrypt-11 + HINTS + ${_GCRYPT_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib +) +find_library(GCRYPT_ERROR_LIBRARY + NAMES + gpg-error + libgpg-error-0 + libgpg-error6-0 + HINTS + ${_GCRYPT_ROOT_HINTS_AND_PATHS} +) +set(GCRYPT_LIBRARIES ${GCRYPT_LIBRARY} ${GCRYPT_ERROR_LIBRARY}) + +if (GCRYPT_INCLUDE_DIR) + file(STRINGS "${GCRYPT_INCLUDE_DIR}/gcrypt.h" _gcrypt_version_str REGEX "^#define GCRYPT_VERSION \"[0-9]+\\.[0-9]+\\.[0-9]") + + string(REGEX REPLACE "^.*GCRYPT_VERSION.*([0-9]+\\.[0-9]+\\.[0-9]+).*" "\\1" GCRYPT_VERSION "${_gcrypt_version_str}") +endif (GCRYPT_INCLUDE_DIR) + +include(FindPackageHandleStandardArgs) +if (GCRYPT_VERSION) + find_package_handle_standard_args(GCrypt + REQUIRED_VARS + GCRYPT_INCLUDE_DIR + GCRYPT_LIBRARIES + VERSION_VAR + GCRYPT_VERSION + FAIL_MESSAGE + "Could NOT find GCrypt, try to set the path to GCrypt root folder in the system variable GCRYPT_ROOT_DIR" + ) +else (GCRYPT_VERSION) + find_package_handle_standard_args(GCrypt + "Could NOT find GCrypt, try to set the path to GCrypt root folder in the system variable GCRYPT_ROOT_DIR" + GCRYPT_INCLUDE_DIR + GCRYPT_LIBRARIES) +endif (GCRYPT_VERSION) + +# show the GCRYPT_INCLUDE_DIRS and GCRYPT_LIBRARIES variables only in the advanced view +mark_as_advanced(GCRYPT_INCLUDE_DIR GCRYPT_LIBRARIES) diff --git a/cmake/Modules/FindGSSAPI.cmake b/cmake/Modules/FindGSSAPI.cmake new file mode 100644 index 0000000..4c3f44b --- /dev/null +++ b/cmake/Modules/FindGSSAPI.cmake @@ -0,0 +1,325 @@ +# - Try to find GSSAPI +# Once done this will define +# +# KRB5_CONFIG - Path to krb5-config +# GSSAPI_ROOT_DIR - Set this variable to the root installation of GSSAPI +# +# Read-Only variables: +# GSSAPI_FLAVOR_MIT - set to TURE if MIT Kerberos has been found +# GSSAPI_FLAVOR_HEIMDAL - set to TRUE if Heimdal Keberos has been found +# GSSAPI_FOUND - system has GSSAPI +# GSSAPI_INCLUDE_DIR - the GSSAPI include directory +# GSSAPI_LIBRARIES - Link these to use GSSAPI +# GSSAPI_DEFINITIONS - Compiler switches required for using GSSAPI +# +#============================================================================= +# Copyright (c) 2013 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +find_path(GSSAPI_ROOT_DIR + NAMES + include/gssapi.h + include/gssapi/gssapi.h + HINTS + ${_GSSAPI_ROOT_HINTS} + PATHS + ${_GSSAPI_ROOT_PATHS} +) +mark_as_advanced(GSSAPI_ROOT_DIR) + +if (UNIX) + find_program(KRB5_CONFIG + NAMES + krb5-config + PATHS + ${GSSAPI_ROOT_DIR}/bin + /opt/local/bin) + mark_as_advanced(KRB5_CONFIG) + + if (KRB5_CONFIG) + # Check if we have MIT KRB5 + execute_process( + COMMAND + ${KRB5_CONFIG} --vendor + RESULT_VARIABLE + _GSSAPI_VENDOR_RESULT + OUTPUT_VARIABLE + _GSSAPI_VENDOR_STRING) + + if ((_GSSAPI_VENDOR_STRING MATCHES ".*Massachusetts.*") OR (_GSSAPI_VENDOR_STRING + MATCHES ".*MITKerberosShim.*")) + set(GSSAPI_FLAVOR_MIT TRUE) + else() + execute_process( + COMMAND + ${KRB5_CONFIG} --libs gssapi + RESULT_VARIABLE + _GSSAPI_LIBS_RESULT + OUTPUT_VARIABLE + _GSSAPI_LIBS_STRING) + + if (_GSSAPI_LIBS_STRING MATCHES ".*roken.*") + set(GSSAPI_FLAVOR_HEIMDAL TRUE) + endif() + endif() + + # Get the include dir + execute_process( + COMMAND + ${KRB5_CONFIG} --cflags gssapi + RESULT_VARIABLE + _GSSAPI_INCLUDE_RESULT + OUTPUT_VARIABLE + _GSSAPI_INCLUDE_STRING) + string(REGEX REPLACE "(\r?\n)+$" "" _GSSAPI_INCLUDE_STRING "${_GSSAPI_INCLUDE_STRING}") + string(REGEX REPLACE " *-I" "" _GSSAPI_INCLUDEDIR "${_GSSAPI_INCLUDE_STRING}") + endif() + + if (NOT GSSAPI_FLAVOR_MIT AND NOT GSSAPI_FLAVOR_HEIMDAL) + # Check for HEIMDAL + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(_GSSAPI heimdal-gssapi) + endif (PKG_CONFIG_FOUND) + + if (_GSSAPI_FOUND) + set(GSSAPI_FLAVOR_HEIMDAL TRUE) + else() + find_path(_GSSAPI_ROKEN + NAMES + roken.h + PATHS + ${GSSAPI_ROOT_DIR}/include + ${_GSSAPI_INCLUDEDIR}) + if (_GSSAPI_ROKEN) + set(GSSAPI_FLAVOR_HEIMDAL TRUE) + endif() + endif () + endif() +endif (UNIX) + +find_path(GSSAPI_INCLUDE_DIR + NAMES + gssapi.h + gssapi/gssapi.h + PATHS + ${GSSAPI_ROOT_DIR}/include + ${_GSSAPI_INCLUDEDIR} +) + +if (GSSAPI_FLAVOR_MIT) + find_library(GSSAPI_LIBRARY + NAMES + gssapi_krb5 + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(KRB5_LIBRARY + NAMES + krb5 + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(K5CRYPTO_LIBRARY + NAMES + k5crypto + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(COM_ERR_LIBRARY + NAMES + com_err + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + if (GSSAPI_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${GSSAPI_LIBRARY} + ) + endif (GSSAPI_LIBRARY) + + if (KRB5_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${KRB5_LIBRARY} + ) + endif (KRB5_LIBRARY) + + if (K5CRYPTO_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${K5CRYPTO_LIBRARY} + ) + endif (K5CRYPTO_LIBRARY) + + if (COM_ERR_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${COM_ERR_LIBRARY} + ) + endif (COM_ERR_LIBRARY) +endif (GSSAPI_FLAVOR_MIT) + +if (GSSAPI_FLAVOR_HEIMDAL) + find_library(GSSAPI_LIBRARY + NAMES + gssapi + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(KRB5_LIBRARY + NAMES + krb5 + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(HCRYPTO_LIBRARY + NAMES + hcrypto + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(COM_ERR_LIBRARY + NAMES + com_err + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(HEIMNTLM_LIBRARY + NAMES + heimntlm + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(HX509_LIBRARY + NAMES + hx509 + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(ASN1_LIBRARY + NAMES + asn1 + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(WIND_LIBRARY + NAMES + wind + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(ROKEN_LIBRARY + NAMES + roken + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + if (GSSAPI_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${GSSAPI_LIBRARY} + ) + endif (GSSAPI_LIBRARY) + + if (KRB5_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${KRB5_LIBRARY} + ) + endif (KRB5_LIBRARY) + + if (HCRYPTO_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${HCRYPTO_LIBRARY} + ) + endif (HCRYPTO_LIBRARY) + + if (COM_ERR_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${COM_ERR_LIBRARY} + ) + endif (COM_ERR_LIBRARY) + + if (HEIMNTLM_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${HEIMNTLM_LIBRARY} + ) + endif (HEIMNTLM_LIBRARY) + + if (HX509_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${HX509_LIBRARY} + ) + endif (HX509_LIBRARY) + + if (ASN1_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${ASN1_LIBRARY} + ) + endif (ASN1_LIBRARY) + + if (WIND_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${WIND_LIBRARY} + ) + endif (WIND_LIBRARY) + + if (ROKEN_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${WIND_LIBRARY} + ) + endif (ROKEN_LIBRARY) +endif (GSSAPI_FLAVOR_HEIMDAL) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GSSAPI DEFAULT_MSG GSSAPI_LIBRARIES GSSAPI_INCLUDE_DIR) + +if (GSSAPI_INCLUDE_DIRS AND GSSAPI_LIBRARIES) + set(GSSAPI_FOUND TRUE) +endif (GSSAPI_INCLUDE_DIRS AND GSSAPI_LIBRARIES) + +# show the GSSAPI_INCLUDE_DIRS and GSSAPI_LIBRARIES variables only in the advanced view +mark_as_advanced(GSSAPI_INCLUDE_DIRS GSSAPI_LIBRARIES) diff --git a/cmake/Modules/FindMbedTLS.cmake b/cmake/Modules/FindMbedTLS.cmake new file mode 100644 index 0000000..baec8ad --- /dev/null +++ b/cmake/Modules/FindMbedTLS.cmake @@ -0,0 +1,104 @@ +# - Try to find mbedTLS +# Once done this will define +# +# MBEDTLS_FOUND - system has mbedTLS +# MBEDTLS_INCLUDE_DIRS - the mbedTLS include directory +# MBEDTLS_LIBRARIES - Link these to use mbedTLS +# MBEDTLS_DEFINITIONS - Compiler switches required for using mbedTLS +#============================================================================= +# Copyright (c) 2017 Sartura d.o.o. +# +# Author: Juraj Vijtiuk +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + + +set(_MBEDTLS_ROOT_HINTS + $ENV{MBEDTLS_ROOT_DIR} + ${MBEDTLS_ROOT_DIR}) + +set(_MBEDTLS_ROOT_PATHS + "$ENV{PROGRAMFILES}/libmbedtls") + +set(_MBEDTLS_ROOT_HINTS_AND_PATHS + HINTS ${_MBEDTLS_ROOT_HINTS} + PATHS ${_MBEDTLS_ROOT_PATHS}) + + +find_path(MBEDTLS_INCLUDE_DIR + NAMES + mbedtls/config.h + HINTS + ${_MBEDTLS_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + include +) + +find_library(MBEDTLS_SSL_LIBRARY + NAMES + mbedtls + HINTS + ${_MBEDTLS_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib + +) + +find_library(MBEDTLS_CRYPTO_LIBRARY + NAMES + mbedcrypto + HINTS + ${_MBEDTLS_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib +) + +find_library(MBEDTLS_X509_LIBRARY + NAMES + mbedx509 + HINTS + ${_MBEDTLS_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib +) + +set(MBEDTLS_LIBRARIES ${MBEDTLS_SSL_LIBRARY} ${MBEDTLS_CRYPTO_LIBRARY} + ${MBEDTLS_X509_LIBRARY}) + +if (MBEDTLS_INCLUDE_DIR AND EXISTS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h") + file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" _mbedtls_version_str REGEX + "^#[\t ]*define[\t ]+MBEDTLS_VERSION_STRING[\t ]+\"[0-9]+.[0-9]+.[0-9]+\"") + + string(REGEX REPLACE "^.*MBEDTLS_VERSION_STRING.*([0-9]+.[0-9]+.[0-9]+).*" + "\\1" MBEDTLS_VERSION "${_mbedtls_version_str}") +endif () + +include(FindPackageHandleStandardArgs) +if (MBEDTLS_VERSION) + find_package_handle_standard_args(MbedTLS + REQUIRED_VARS + MBEDTLS_INCLUDE_DIR + MBEDTLS_LIBRARIES + VERSION_VAR + MBEDTLS_VERSION + FAIL_MESSAGE + "Could NOT find mbedTLS, try to set the path to mbedTLS root folder + in the system variable MBEDTLS_ROOT_DIR" + ) +else (MBEDTLS_VERSION) + find_package_handle_standard_args(MBedTLS + "Could NOT find mbedTLS, try to set the path to mbedLS root folder in + the system variable MBEDTLS_ROOT_DIR" + MBEDTLS_INCLUDE_DIR + MBEDTLS_LIBRARIES) +endif (MBEDTLS_VERSION) + +# show the MBEDTLS_INCLUDE_DIRS and MBEDTLS_LIBRARIES variables only in the advanced view +mark_as_advanced(MBEDTLS_INCLUDE_DIR MBEDTLS_LIBRARIES) diff --git a/cmake/Modules/FindNSIS.cmake b/cmake/Modules/FindNSIS.cmake new file mode 100644 index 0000000..9f1ab17 --- /dev/null +++ b/cmake/Modules/FindNSIS.cmake @@ -0,0 +1,54 @@ +# - Try to find NSIS +# Once done this will define +# +# NSIS_ROOT_PATH - Set this variable to the root installation of NSIS +# +# Read-Only variables: +# +# NSIS_FOUND - system has NSIS +# NSIS_MAKE - NSIS creator executable +# +#============================================================================= +# Copyright (c) 2010-2013 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +if (WIN32) + set(_x86 "(x86)") + + set(_NSIS_ROOT_PATHS + "$ENV{ProgramFiles}/NSIS" + "$ENV{ProgramFiles${_x86}}/NSIS" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\NSIS;Default]") + + find_path(NSIS_ROOT_PATH + NAMES + Include/Library.nsh + PATHS + ${_NSIS_ROOT_PATHS} + ) + mark_as_advanced(NSIS_ROOT_PATH) +endif (WIN32) + +find_program(NSIS_MAKE + NAMES + makensis + PATHS + ${NSIS_ROOT_PATH} +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(NSIS DEFAULT_MSG NSIS_MAKE) + +if (NSIS_MAKE) + set(NSIS_FOUND TRUE) +endif (NSIS_MAKE) + +mark_as_advanced(NSIS_MAKE) diff --git a/cmake/Modules/FindNaCl.cmake b/cmake/Modules/FindNaCl.cmake new file mode 100644 index 0000000..b1a8da4 --- /dev/null +++ b/cmake/Modules/FindNaCl.cmake @@ -0,0 +1,61 @@ +# - Try to find NaCl +# Once done this will define +# +# NACL_FOUND - system has NaCl +# NACL_INCLUDE_DIRS - the NaCl include directory +# NACL_LIBRARIES - Link these to use NaCl +# NACL_DEFINITIONS - Compiler switches required for using NaCl +# +# Copyright (c) 2010 Andreas Schneider +# Copyright (c) 2013 Aris Adamantiadis +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + + +if (NACL_LIBRARIES AND NACL_INCLUDE_DIRS) + # in cache already + set(NACL_FOUND TRUE) +else (NACL_LIBRARIES AND NACL_INCLUDE_DIRS) + + find_path(NACL_INCLUDE_DIR + NAMES + nacl/crypto_box_curve25519xsalsa20poly1305.h + PATHS + /usr/include + /usr/local/include + /opt/local/include + /sw/include + ) + + find_library(NACL_LIBRARY + NAMES + nacl + PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib + ) + + set(NACL_INCLUDE_DIRS + ${NACL_INCLUDE_DIR} + ) + + if (NACL_LIBRARY) + set(NACL_LIBRARIES + ${NACL_LIBRARIES} + ${NACL_LIBRARY} + ) + endif (NACL_LIBRARY) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(NaCl DEFAULT_MSG NACL_LIBRARIES NACL_INCLUDE_DIRS) + + # show the NACL_INCLUDE_DIRS and NACL_LIBRARIES variables only in the advanced view + mark_as_advanced(NACL_INCLUDE_DIRS NACL_LIBRARIES) + +endif (NACL_LIBRARIES AND NACL_INCLUDE_DIRS) + diff --git a/cmake/Modules/GenerateMap.cmake b/cmake/Modules/GenerateMap.cmake new file mode 100644 index 0000000..c22dfbc --- /dev/null +++ b/cmake/Modules/GenerateMap.cmake @@ -0,0 +1,118 @@ +# +# Copyright (c) 2018 Anderson Toshiyuki Sasaki +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +#.rst: +# GenerateMap +# ----------- +# +# This is a helper script for FindABImap.cmake. +# +# Generates a symbols version script using the abimap tool. +# This script is run in build time to use the correct command depending on the +# existence of the file provided ``CURRENT_MAP``. +# +# If the file exists, the ``abimap update`` subcommand is used to update the +# existing map. Otherwise, the ``abimap new`` subcommand is used to create a new +# map file. +# +# If the file provided in ``CURRENT_MAP`` exists, it is copied to the +# ``OUTPUT_PATH`` before updating. +# This is required because ``abimap`` do not generate output if no symbols were +# changed when updating an existing file. +# +# Expected defined variables +# -------------------------- +# +# ``SYMBOLS``: +# Required file containing the symbols to be used as input. Usually this is +# the ``OUTPUT`` generated by ``extract_symbols()`` function provided in +# FindABImap.cmake +# +# ``RELEASE_NAME_VERSION``: +# Required, expects the library name and version information to be added to +# the symbols in the format ``library_name_1_2_3`` +# +# ``CURRENT_MAP``: +# Required, expects the path to the current map file (or the path were it +# should be) +# +# ``OUTPUT_PATH``: +# Required, expects the output file path. +# +# ``ABIMAP_EXECUTABLE``: +# Required, expects the path to the ``abimap`` tool. +# +# Optionally defined variables +# ---------------------------- +# +# ``FINAL``: +# If defined, will mark the modified set of symbols in the symbol version +# script as final, preventing later changes using ``abimap``. +# +# ``BREAK_ABI``: +# If defined, the build will not fail if symbols were removed. +# If defined and a symbol is removed, a new release is created containing +# all symbols from all released versions. This makes an incompatible release. +# + +if (NOT DEFINED RELEASE_NAME_VERSION) + message(SEND_ERROR "RELEASE_NAME_VERSION not defined") +endif() + +if (NOT DEFINED SYMBOLS) + message(SEND_ERROR "SYMBOLS not defined") +endif() + +if (NOT DEFINED CURRENT_MAP) + message(SEND_ERROR "CURRENT_MAP not defined") +endif() + +if (NOT DEFINED OUTPUT_PATH) + message(SEND_ERROR "OUTPUT_PATH not defined") +endif() + +if (NOT ABIMAP_EXECUTABLE) + message(SEND_ERROR "ABIMAP_EXECUTABLE not defined") +endif() + +set(ARGS_LIST) + +if (FINAL) + list(APPEND ARGS_LIST "--final") +endif() + +if (EXISTS ${CURRENT_MAP}) + if (BREAK_ABI) + list(APPEND ARGS_LIST "--allow-abi-break") + endif() + + execute_process( + COMMAND + ${CMAKE_COMMAND} -E copy_if_different ${CURRENT_MAP} ${OUTPUT_PATH} + COMMAND + ${ABIMAP_EXECUTABLE} update ${ARGS_LIST} + -r ${RELEASE_NAME_VERSION} + -i ${SYMBOLS} + -o ${OUTPUT_PATH} + ${CURRENT_MAP} + RESULT_VARIABLE result + ) +else () + execute_process( + COMMAND + ${ABIMAP_EXECUTABLE} new ${ARGS_LIST} + -r ${RELEASE_NAME_VERSION} + -i ${SYMBOLS} + -o ${OUTPUT_PATH} + RESULT_VARIABLE result + ) +endif() + +if (NOT "${result}" STREQUAL "0") + message(SEND_ERROR "Map generation failed") +endif() diff --git a/cmake/Modules/GetFilesList.cmake b/cmake/Modules/GetFilesList.cmake new file mode 100644 index 0000000..e3e8a2a --- /dev/null +++ b/cmake/Modules/GetFilesList.cmake @@ -0,0 +1,59 @@ +# +# Copyright (c) 2018 Anderson Toshiyuki Sasaki +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +#.rst: +# GetFilesList +# ------------ +# +# This is a helper script for FindABImap.cmake. +# +# Search in the provided directories for files matching the provided pattern. +# The list of files is then written to the output file. +# +# Expected defined variables +# -------------------------- +# +# ``DIRECTORIES``: +# Required, expects a list of directories paths. +# +# ``FILES_PATTERNS``: +# Required, expects a list of patterns to be used to search files +# +# ``OUTPUT_PATH``: +# Required, expects the output file path. + +if (NOT DEFINED DIRECTORIES) + message(SEND_ERROR "DIRECTORIES not defined") +endif() + +if (NOT DEFINED FILES_PATTERNS) + message(SEND_ERROR "FILES_PATTERNS not defined") +endif() + +if (NOT DEFINED OUTPUT_PATH) + message(SEND_ERROR "OUTPUT_PATH not defined") +endif() + +string(REPLACE " " ";" DIRECTORIES_LIST "${DIRECTORIES}") +string(REPLACE " " ";" FILES_PATTERNS_LIST "${FILES_PATTERNS}") + +# Create the list of expressions for the files +set(glob_expressions) +foreach(dir ${DIRECTORIES_LIST}) + foreach(exp ${FILES_PATTERNS_LIST}) + list(APPEND glob_expressions + "${dir}/${exp}" + ) + endforeach() +endforeach() + +# Create the list of files +file(GLOB files ${glob_expressions}) + +# Write to the output +file(WRITE ${OUTPUT_PATH} "${files}") diff --git a/cmake/Modules/MacroEnsureOutOfSourceBuild.cmake b/cmake/Modules/MacroEnsureOutOfSourceBuild.cmake new file mode 100644 index 0000000..a2e9480 --- /dev/null +++ b/cmake/Modules/MacroEnsureOutOfSourceBuild.cmake @@ -0,0 +1,17 @@ +# - MACRO_ENSURE_OUT_OF_SOURCE_BUILD() +# MACRO_ENSURE_OUT_OF_SOURCE_BUILD() + +# Copyright (c) 2006, Alexander Neundorf, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +macro (MACRO_ENSURE_OUT_OF_SOURCE_BUILD _errorMessage) + + string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" _insource) + if (_insource) + message(SEND_ERROR "${_errorMessage}") + message(FATAL_ERROR "Remove the file CMakeCache.txt in ${CMAKE_SOURCE_DIR} first.") + endif (_insource) + +endmacro (MACRO_ENSURE_OUT_OF_SOURCE_BUILD) diff --git a/cmake/Toolchain-cross-m32.cmake b/cmake/Toolchain-cross-m32.cmake new file mode 100644 index 0000000..7918c60 --- /dev/null +++ b/cmake/Toolchain-cross-m32.cmake @@ -0,0 +1,23 @@ +set(CMAKE_C_FLAGS "-m32" CACHE STRING "C compiler flags" FORCE) +set(CMAKE_CXX_FLAGS "-m32" CACHE STRING "C++ compiler flags" FORCE) + +set(LIB32 /usr/lib) # Fedora + +if(EXISTS /usr/lib32) + set(LIB32 /usr/lib32) # Arch, Solus +endif() + +set(CMAKE_SYSTEM_LIBRARY_PATH ${LIB32} CACHE STRING "system library search path" FORCE) +set(CMAKE_LIBRARY_PATH ${LIB32} CACHE STRING "library search path" FORCE) + +# this is probably unlikely to be needed, but just in case +set(CMAKE_EXE_LINKER_FLAGS "-m32 -L${LIB32}" CACHE STRING "executable linker flags" FORCE) +set(CMAKE_SHARED_LINKER_FLAGS "-m32 -L${LIB32}" CACHE STRING "shared library linker flags" FORCE) +set(CMAKE_MODULE_LINKER_FLAGS "-m32 -L${LIB32}" CACHE STRING "module linker flags" FORCE) + +# on Fedora and Arch and similar, point pkgconfig at 32 bit .pc files. We have +# to include the regular system .pc files as well (at the end), because some +# are not always present in the 32 bit directory +if(EXISTS ${LIB32}/pkgconfig) + set(ENV{PKG_CONFIG_LIBDIR} ${LIB32}/pkgconfig:/usr/share/pkgconfig:/usr/lib/pkgconfig:/usr/lib64/pkgconfig) +endiF() diff --git a/config.h.cmake b/config.h.cmake new file mode 100644 index 0000000..98a72f6 --- /dev/null +++ b/config.h.cmake @@ -0,0 +1,285 @@ +/* Name of package */ +#cmakedefine PACKAGE "${PROJECT_NAME}" + +/* Version number of package */ +#cmakedefine VERSION "${PROJECT_VERSION}" + +#cmakedefine SYSCONFDIR "${SYSCONFDIR}" +#cmakedefine BINARYDIR "${BINARYDIR}" +#cmakedefine SOURCEDIR "${SOURCEDIR}" + +/* Global bind configuration file path */ +#cmakedefine GLOBAL_BIND_CONFIG "${GLOBAL_BIND_CONFIG}" + +/* Global client configuration file path */ +#cmakedefine GLOBAL_CLIENT_CONFIG "${GLOBAL_CLIENT_CONFIG}" + +/************************** HEADER FILES *************************/ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_ARGP_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_ARPA_INET_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_GLOB_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_VALGRIND_VALGRIND_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PTY_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UTMP_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UTIL_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LIBUTIL_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_UTIME_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_IO_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_TERMIOS_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UNISTD_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_AES_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_WSPIAPI_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_BLOWFISH_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_DES_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_ECDH_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_EC_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_ECDSA_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PTHREAD_H 1 + +/* Define to 1 if you have eliptic curve cryptography in openssl */ +#cmakedefine HAVE_OPENSSL_ECC 1 + +/* Define to 1 if you have eliptic curve cryptography in gcrypt */ +#cmakedefine HAVE_GCRYPT_ECC 1 + +/* Define to 1 if you have eliptic curve cryptography */ +#cmakedefine HAVE_ECC 1 + +/* Define to 1 if you have DSA */ +#cmakedefine HAVE_DSA 1 + +/* Define to 1 if you have gl_flags as a glob_t sturct member */ +#cmakedefine HAVE_GLOB_GL_FLAGS_MEMBER 1 + +/* Define to 1 if you have OpenSSL with Ed25519 support */ +#cmakedefine HAVE_OPENSSL_ED25519 1 + +/* Define to 1 if you have OpenSSL with X25519 support */ +#cmakedefine HAVE_OPENSSL_X25519 1 + +/*************************** FUNCTIONS ***************************/ + +/* Define to 1 if you have the `EVP_aes128_ctr' function. */ +#cmakedefine HAVE_OPENSSL_EVP_AES_CTR 1 + +/* Define to 1 if you have the `EVP_aes128_cbc' function. */ +#cmakedefine HAVE_OPENSSL_EVP_AES_CBC 1 + +/* Define to 1 if you have the `EVP_aes128_gcm' function. */ +#cmakedefine HAVE_OPENSSL_EVP_AES_GCM 1 + +/* Define to 1 if you have the `CRYPTO_THREADID_set_callback' function. */ +#cmakedefine HAVE_OPENSSL_CRYPTO_THREADID_SET_CALLBACK 1 + +/* Define to 1 if you have the `CRYPTO_ctr128_encrypt' function. */ +#cmakedefine HAVE_OPENSSL_CRYPTO_CTR128_ENCRYPT 1 + +/* Define to 1 if you have the `EVP_CIPHER_CTX_new' function. */ +#cmakedefine HAVE_OPENSSL_EVP_CIPHER_CTX_NEW 1 + +/* Define to 1 if you have the `EVP_KDF_CTX_new_id' function. */ +#cmakedefine HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID 1 + +/* Define to 1 if you have the `FIPS_mode' function. */ +#cmakedefine HAVE_OPENSSL_FIPS_MODE 1 + +/* Define to 1 if you have the `EVP_DigestSign' function. */ +#cmakedefine HAVE_OPENSSL_EVP_DIGESTSIGN 1 + +/* Define to 1 if you have the `EVP_DigestVerify' function. */ +#cmakedefine HAVE_OPENSSL_EVP_DIGESTVERIFY 1 + +/* Define to 1 if you have the `OPENSSL_ia32cap_loc' function. */ +#cmakedefine HAVE_OPENSSL_IA32CAP_LOC 1 + +/* Define to 1 if you have the `snprintf' function. */ +#cmakedefine HAVE_SNPRINTF 1 + +/* Define to 1 if you have the `_snprintf' function. */ +#cmakedefine HAVE__SNPRINTF 1 + +/* Define to 1 if you have the `_snprintf_s' function. */ +#cmakedefine HAVE__SNPRINTF_S 1 + +/* Define to 1 if you have the `vsnprintf' function. */ +#cmakedefine HAVE_VSNPRINTF 1 + +/* Define to 1 if you have the `_vsnprintf' function. */ +#cmakedefine HAVE__VSNPRINTF 1 + +/* Define to 1 if you have the `_vsnprintf_s' function. */ +#cmakedefine HAVE__VSNPRINTF_S 1 + +/* Define to 1 if you have the `isblank' function. */ +#cmakedefine HAVE_ISBLANK 1 + +/* Define to 1 if you have the `strncpy' function. */ +#cmakedefine HAVE_STRNCPY 1 + +/* Define to 1 if you have the `strndup' function. */ +#cmakedefine HAVE_STRNDUP 1 + +/* Define to 1 if you have the `cfmakeraw' function. */ +#cmakedefine HAVE_CFMAKERAW 1 + +/* Define to 1 if you have the `getaddrinfo' function. */ +#cmakedefine HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the `poll' function. */ +#cmakedefine HAVE_POLL 1 + +/* Define to 1 if you have the `select' function. */ +#cmakedefine HAVE_SELECT 1 + +/* Define to 1 if you have the `clock_gettime' function. */ +#cmakedefine HAVE_CLOCK_GETTIME 1 + +/* Define to 1 if you have the `ntohll' function. */ +#cmakedefine HAVE_NTOHLL 1 + +/* Define to 1 if you have the `htonll' function. */ +#cmakedefine HAVE_HTONLL 1 + +/* Define to 1 if you have the `strtoull' function. */ +#cmakedefine HAVE_STRTOULL 1 + +/* Define to 1 if you have the `__strtoull' function. */ +#cmakedefine HAVE___STRTOULL 1 + +/* Define to 1 if you have the `_strtoui64' function. */ +#cmakedefine HAVE__STRTOUI64 1 + +/* Define to 1 if you have the `glob' function. */ +#cmakedefine HAVE_GLOB 1 + +/* Define to 1 if you have the `explicit_bzero' function. */ +#cmakedefine HAVE_EXPLICIT_BZERO 1 + +/* Define to 1 if you have the `memset_s' function. */ +#cmakedefine HAVE_MEMSET_S 1 + +/* Define to 1 if you have the `SecureZeroMemory' function. */ +#cmakedefine HAVE_SECURE_ZERO_MEMORY 1 + +/* Define to 1 if you have the `cmocka_set_test_filter' function. */ +#cmakedefine HAVE_CMOCKA_SET_TEST_FILTER 1 + +/*************************** LIBRARIES ***************************/ + +/* Define to 1 if you have the `crypto' library (-lcrypto). */ +#cmakedefine HAVE_LIBCRYPTO 1 + +/* Define to 1 if you have the `gcrypt' library (-lgcrypt). */ +#cmakedefine HAVE_LIBGCRYPT 1 + +/* Define to 1 if you have the 'mbedTLS' library (-lmbedtls). */ +#cmakedefine HAVE_LIBMBEDCRYPTO 1 + +/* Define to 1 if you have the `pthread' library (-lpthread). */ +#cmakedefine HAVE_PTHREAD 1 + +/* Define to 1 if you have the `cmocka' library (-lcmocka). */ +#cmakedefine HAVE_CMOCKA 1 + +/**************************** OPTIONS ****************************/ + +#cmakedefine HAVE_GCC_THREAD_LOCAL_STORAGE 1 +#cmakedefine HAVE_MSC_THREAD_LOCAL_STORAGE 1 + +#cmakedefine HAVE_FALLTHROUGH_ATTRIBUTE 1 +#cmakedefine HAVE_UNUSED_ATTRIBUTE 1 + +#cmakedefine HAVE_CONSTRUCTOR_ATTRIBUTE 1 +#cmakedefine HAVE_DESTRUCTOR_ATTRIBUTE 1 + +#cmakedefine HAVE_GCC_VOLATILE_MEMORY_PROTECTION 1 + +#cmakedefine HAVE_COMPILER__FUNC__ 1 +#cmakedefine HAVE_COMPILER__FUNCTION__ 1 + +#cmakedefine HAVE_GCC_BOUNDED_ATTRIBUTE 1 + +/* Define to 1 if you want to enable GSSAPI */ +#cmakedefine WITH_GSSAPI 1 + +/* Define to 1 if you want to enable ZLIB */ +#cmakedefine WITH_ZLIB 1 + +/* Define to 1 if you want to enable SFTP */ +#cmakedefine WITH_SFTP 1 + +/* Define to 1 if you want to enable server support */ +#cmakedefine WITH_SERVER 1 + +/* Define to 1 if you want to enable DH group exchange algorithms */ +#cmakedefine WITH_GEX 1 + +/* Define to 1 if you want to enable blowfish cipher support */ +#cmakedefine WITH_BLOWFISH_CIPHER 1 + +/* Define to 1 if you want to enable debug output for crypto functions */ +#cmakedefine DEBUG_CRYPTO 1 + +/* Define to 1 if you want to enable debug output for packet functions */ +#cmakedefine DEBUG_PACKET 1 + +/* Define to 1 if you want to enable pcap output support (experimental) */ +#cmakedefine WITH_PCAP 1 + +/* Define to 1 if you want to enable calltrace debug output */ +#cmakedefine DEBUG_CALLTRACE 1 + +/* Define to 1 if you want to enable NaCl support */ +#cmakedefine WITH_NACL 1 + +/*************************** ENDIAN *****************************/ + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +#cmakedefine WORDS_BIGENDIAN 1 diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 0000000..8a66649 --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,48 @@ +# +# Build the documentation +# +if (${CMAKE_VERSION} VERSION_GREATER "3.8.99") + +find_package(Doxygen) + +if (DOXYGEN_FOUND) + set(DOXYGEN_PROJECT_NAME ${PROJECT_NAME}) + set(DOXYGEN_PROJECT_NUMBER ${PROJECT_VERSION}) + set(DOXYGEN_PROJECT_BRIEF "The SSH library") + + set(DOXYGEN_TAB_SIZE 4) + set(DOXYGEN_OPTIMIZE_OUTPUT_FOR_C YES) + set(DOXYGEN_MARKDOWN_SUPPORT YES) + + set(DOXYGEN_PREDEFINED DOXYGEN + WITH_SERVER + WITH_SFTP + PRINTF_ATTRIBUTE(x,y)) + + set(DOXYGEN_EXCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/that_style) + set(DOXYGEN_HTML_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/that_style/header.html) + set(DOXYGEN_HTML_EXTRA_STYLESHEET ${CMAKE_CURRENT_SOURCE_DIR}/that_style/that_style.css) + set(DOXYGEN_HTML_EXTRA_FILES ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/nav_edge_left.svg + ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/nav_edge_right.svg + ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/nav_edge_inter.svg + ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/sync_off.png + ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/sync_on.png + ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/splitbar_handle.svg + ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/doc.svg + ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/mag_glass.svg + ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/folderclosed.svg + ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/folderopen.svg + ${CMAKE_CURRENT_SOURCE_DIR}/that_style/js/striped_bg.js) + + # This updates the Doxyfile if we do changes here + set(_doxyfile_template "${CMAKE_BINARY_DIR}/CMakeDoxyfile.in") + set(_target_doxyfile "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.docs") + configure_file("${_doxyfile_template}" "${_target_doxyfile}") + + doxygen_add_docs(docs + ${CMAKE_SOURCE_DIR}/include/libssh + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}) +endif() # DOXYGEN_FOUND + +endif() # CMAKE_VERSION diff --git a/doc/authentication.dox b/doc/authentication.dox new file mode 100644 index 0000000..3196f64 --- /dev/null +++ b/doc/authentication.dox @@ -0,0 +1,375 @@ +/** +@page libssh_tutor_authentication Chapter 2: A deeper insight on authentication +@section authentication_details A deeper insight on authentication + +In our guided tour, we merely mentioned that the user needed to authenticate. +We didn't explain much in detail how that was supposed to happen. +This chapter explains better the four authentication methods: with public keys, +with a password, with challenges and responses (keyboard-interactive), and with +no authentication at all. + +If your software is supposed to connect to an arbitrary server, then you +might need to support all authentication methods. If your software will +connect only to a given server, then it might be enough for your software +to support only the authentication methods used by that server. If you are +the administrator of the server, it might be your call to choose those +authentication methods. + +It is not the purpose of this document to review in detail the advantages +and drawbacks of each authentication method. You are therefore invited +to read the abundant documentation on this topic to fully understand the +advantages and security risks linked to each method. + + +@subsection pubkeys Authenticating with public keys + +libssh is fully compatible with the openssh public and private keys. You +can either use the automatic public key authentication method provided by +libssh, or roll your own using the public key functions. + +The process of authenticating by public key to a server is the following: + - you scan a list of files that contain public keys. each key is sent to + the SSH server, until the server acknowledges a key (a key it knows can be + used to authenticate the user). + - then, you retrieve the private key for this key and send a message + proving that you know that private key. + +The function ssh_userauth_autopubkey() does this using the available keys in +"~/.ssh/". The return values are the following: + - SSH_AUTH_ERROR: some serious error happened during authentication + - SSH_AUTH_DENIED: no key matched + - SSH_AUTH_SUCCESS: you are now authenticated + - SSH_AUTH_PARTIAL: some key matched but you still have to provide an other + mean of authentication (like a password). + +The ssh_userauth_publickey_auto() function also tries to authenticate using the +SSH agent, if you have one running, or the "none" method otherwise. + +If you wish to authenticate with public key by your own, follow these steps: + - Retrieve the public key with ssh_pki_import_pubkey_file(). + - Offer the public key to the SSH server using ssh_userauth_try_publickey(). + If the return value is SSH_AUTH_SUCCESS, the SSH server accepts to + authenticate using the public key and you can go to the next step. + - Retrieve the private key, using the ssh_pki_import_privkey_file() function. + If a passphrase is needed, either the passphrase specified as argument or + a callback will be used. + - Authenticate using ssh_userauth_publickey() with your private key. + - Do not forget cleaning up memory using ssh_key_free(). + +Here is a minimalistic example of public key authentication: + +@code +int authenticate_pubkey(ssh_session session) +{ + int rc; + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + + if (rc == SSH_AUTH_ERROR) + { + fprintf(stderr, "Authentication failed: %s\n", + ssh_get_error(session)); + return SSH_AUTH_ERROR; + } + + return rc; +} +@endcode + +@see ssh_userauth_publickey_auto() +@see ssh_userauth_try_publickey() +@see ssh_userauth_publickey() +@see ssh_pki_import_pubkey_file() +@see ssh_pki_import_privkey_file() +@see ssh_key_free() + + +@subsection password Authenticating with a password + +The function ssh_userauth_password() serves the purpose of authenticating +using a password. It will return SSH_AUTH_SUCCESS if the password worked, +or one of other constants otherwise. It's your work to ask the password +and to deallocate it in a secure manner. + +If your server complains that the password is wrong, but you can still +authenticate using openssh's client (issuing password), it's probably +because openssh only accept keyboard-interactive. Switch to +keyboard-interactive authentication, or try to configure plain text passwords +on the SSH server. + +Here is a small example of password authentication: + +@code +int authenticate_password(ssh_session session) +{ + char *password; + int rc; + + password = getpass("Enter your password: "); + rc = ssh_userauth_password(session, NULL, password); + if (rc == SSH_AUTH_ERROR) + { + fprintf(stderr, "Authentication failed: %s\n", + ssh_get_error(session)); + return SSH_AUTH_ERROR; + } + + return rc; +} +@endcode + +@see ssh_userauth_password + + +@subsection keyb_int The keyboard-interactive authentication method + +The keyboard-interactive method is, as its name tells, interactive. The +server will issue one or more challenges that the user has to answer, +until the server takes an authentication decision. + +ssh_userauth_kbdint() is the the main keyboard-interactive function. +It will return SSH_AUTH_SUCCESS,SSH_AUTH_DENIED, SSH_AUTH_PARTIAL, +SSH_AUTH_ERROR, or SSH_AUTH_INFO, depending on the result of the request. + +The keyboard-interactive authentication method of SSH2 is a feature that +permits the server to ask a certain number of questions in an interactive +manner to the client, until it decides to accept or deny the login. + +To begin, you call ssh_userauth_kbdint() (just set user and submethods to +NULL) and store the answer. + +If the answer is SSH_AUTH_INFO, it means that the server has sent a few +questions that you should ask the user. You can retrieve these questions +with the following functions: ssh_userauth_kbdint_getnprompts(), +ssh_userauth_kbdint_getname(), ssh_userauth_kbdint_getinstruction(), and +ssh_userauth_kbdint_getprompt(). + +Set the answer for each question in the challenge using +ssh_userauth_kbdint_setanswer(). + +Then, call again ssh_userauth_kbdint() and start the process again until +these functions returns something else than SSH_AUTH_INFO. + +Here are a few remarks: + - Even the first call can return SSH_AUTH_DENIED or SSH_AUTH_SUCCESS. + - The server can send an empty question set (this is the default behavior + on my system) after you have sent the answers to the first questions. + You must still parse the answer, it might contain some + message from the server saying hello or such things. Just call + ssh_userauth_kbdint() until needed. + - The meaning of "name", "prompt", "instruction" may be a little + confusing. An explanation is given in the RFC section that follows. + +Here is a little note about how to use the information from +keyboard-interactive authentication, coming from the RFC itself (rfc4256): + +@verbatim + + 3.3 User Interface Upon receiving a request message, the client SHOULD + prompt the user as follows: A command line interface (CLI) client SHOULD + print the name and instruction (if non-empty), adding newlines. Then for + each prompt in turn, the client SHOULD display the prompt and read the + user input. + + A graphical user interface (GUI) client has many choices on how to prompt + the user. One possibility is to use the name field (possibly prefixed + with the application's name) as the title of a dialog window in which + the prompt(s) are presented. In that dialog window, the instruction field + would be a text message, and the prompts would be labels for text entry + fields. All fields SHOULD be presented to the user, for example an + implementation SHOULD NOT discard the name field because its windows lack + titles; it SHOULD instead find another way to display this information. If + prompts are presented in a dialog window, then the client SHOULD NOT + present each prompt in a separate window. + + All clients MUST properly handle an instruction field with embedded + newlines. They SHOULD also be able to display at least 30 characters for + the name and prompts. If the server presents names or prompts longer than 30 + characters, the client MAY truncate these fields to the length it can + display. If the client does truncate any fields, there MUST be an obvious + indication that such truncation has occurred. + + The instruction field SHOULD NOT be truncated. Clients SHOULD use control + character filtering as discussed in [SSH-ARCH] to avoid attacks by + including terminal control characters in the fields to be displayed. + + For each prompt, the corresponding echo field indicates whether or not + the user input should be echoed as characters are typed. Clients SHOULD + correctly echo/mask user input for each prompt independently of other + prompts in the request message. If a client does not honor the echo field + for whatever reason, then the client MUST err on the side of + masking input. A GUI client might like to have a checkbox toggling + echo/mask. Clients SHOULD NOT add any additional characters to the prompt + such as ": " (colon-space); the server is responsible for supplying all + text to be displayed to the user. Clients MUST also accept empty responses + from the user and pass them on as empty strings. +@endverbatim + +The following example shows how to perform keyboard-interactive authentication: + +@code +int authenticate_kbdint(ssh_session session) +{ + int rc; + + rc = ssh_userauth_kbdint(session, NULL, NULL); + while (rc == SSH_AUTH_INFO) + { + const char *name, *instruction; + int nprompts, iprompt; + + name = ssh_userauth_kbdint_getname(session); + instruction = ssh_userauth_kbdint_getinstruction(session); + nprompts = ssh_userauth_kbdint_getnprompts(session); + + if (strlen(name) > 0) + printf("%s\n", name); + if (strlen(instruction) > 0) + printf("%s\n", instruction); + for (iprompt = 0; iprompt < nprompts; iprompt++) + { + const char *prompt; + char echo; + + prompt = ssh_userauth_kbdint_getprompt(session, iprompt, &echo); + if (echo) + { + char buffer[128], *ptr; + + printf("%s", prompt); + if (fgets(buffer, sizeof(buffer), stdin) == NULL) + return SSH_AUTH_ERROR; + buffer[sizeof(buffer) - 1] = '\0'; + if ((ptr = strchr(buffer, '\n')) != NULL) + *ptr = '\0'; + if (ssh_userauth_kbdint_setanswer(session, iprompt, buffer) < 0) + return SSH_AUTH_ERROR; + memset(buffer, 0, strlen(buffer)); + } + else + { + char *ptr; + + ptr = getpass(prompt); + if (ssh_userauth_kbdint_setanswer(session, iprompt, ptr) < 0) + return SSH_AUTH_ERROR; + } + } + rc = ssh_userauth_kbdint(session, NULL, NULL); + } + return rc; +} +@endcode + +@see ssh_userauth_kbdint() +@see ssh_userauth_kbdint_getnprompts() +@see ssh_userauth_kbdint_getname() +@see ssh_userauth_kbdint_getinstruction() +@see ssh_userauth_kbdint_getprompt() +@see ssh_userauth_kbdint_setanswer() + + +@subsection none Authenticating with "none" method + +The primary purpose of the "none" method is to get authenticated **without** +any credential. Don't do that, use one of the other authentication methods, +unless you really want to grant anonymous access. + +If the account has no password, and if the server is configured to let you +pass, ssh_userauth_none() might answer SSH_AUTH_SUCCESS. + +The following example shows how to perform "none" authentication: + +@code +int authenticate_none(ssh_session session) +{ + int rc; + + rc = ssh_userauth_none(session, NULL); + return rc; +} +@endcode + +@subsection auth_list Getting the list of supported authentications + +You are not meant to choose a given authentication method, you can +let the server tell you which methods are available. Once you know them, +you try them one after the other. + +The following example shows how to get the list of available authentication +methods with ssh_userauth_list() and how to use the result: + +@code +int test_several_auth_methods(ssh_session session) +{ + int method, rc; + + rc = ssh_userauth_none(session, NULL); + if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_ERROR) { + return rc; + } + + method = ssh_userauth_list(session, NULL); + + if (method & SSH_AUTH_METHOD_NONE) + { // For the source code of function authenticate_none(), + // refer to the corresponding example + rc = authenticate_none(session); + if (rc == SSH_AUTH_SUCCESS) return rc; + } + if (method & SSH_AUTH_METHOD_PUBLICKEY) + { // For the source code of function authenticate_pubkey(), + // refer to the corresponding example + rc = authenticate_pubkey(session); + if (rc == SSH_AUTH_SUCCESS) return rc; + } + if (method & SSH_AUTH_METHOD_INTERACTIVE) + { // For the source code of function authenticate_kbdint(), + // refer to the corresponding example + rc = authenticate_kbdint(session); + if (rc == SSH_AUTH_SUCCESS) return rc; + } + if (method & SSH_AUTH_METHOD_PASSWORD) + { // For the source code of function authenticate_password(), + // refer to the corresponding example + rc = authenticate_password(session); + if (rc == SSH_AUTH_SUCCESS) return rc; + } + return SSH_AUTH_ERROR; +} +@endcode + + +@subsection banner Getting the banner + +The SSH server might send a banner, which you can retrieve with +ssh_get_issue_banner(), then display to the user. + +The following example shows how to retrieve and dispose the issue banner: + +@code +int display_banner(ssh_session session) +{ + int rc; + char *banner; + +/* + *** Does not work without calling ssh_userauth_none() first *** + *** That will be fixed *** +*/ + rc = ssh_userauth_none(session, NULL); + if (rc == SSH_AUTH_ERROR) + return rc; + + banner = ssh_get_issue_banner(session); + if (banner) + { + printf("%s\n", banner); + free(banner); + } + + return rc; +} +@endcode + +*/ diff --git a/doc/command.dox b/doc/command.dox new file mode 100644 index 0000000..588151c --- /dev/null +++ b/doc/command.dox @@ -0,0 +1,94 @@ +/** +@page libssh_tutor_command Chapter 4: Passing a remote command +@section remote_command Passing a remote command + +Previous chapter has shown how to open a full shell session, with an attached +terminal or not. If you only need to execute a command on the remote end, +you don't need all that complexity. + +The method described here is suited for executing only one remote command. +If you need to issue several commands in a row, you should consider using +a non-interactive remote shell, as explained in previous chapter. + +@see shell + + +@subsection exec_remote Executing a remote command + +The first steps for executing a remote command are identical to those +for opening remote shells. You first need a SSH channel, and then +a SSH session that uses this channel: + +@code +int show_remote_files(ssh_session session) +{ + ssh_channel channel; + int rc; + + channel = ssh_channel_new(session); + if (channel == NULL) return SSH_ERROR; + + rc = ssh_channel_open_session(channel); + if (rc != SSH_OK) + { + ssh_channel_free(channel); + return rc; + } +@endcode + +Once a session is open, you can start the remote command with +ssh_channel_request_exec(): + +@code + rc = ssh_channel_request_exec(channel, "ls -l"); + if (rc != SSH_OK) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return rc; + } +@endcode + +If the remote command displays data, you get them with ssh_channel_read(). +This function returns the number of bytes read. If there is no more +data to read on the channel, this function returns 0, and you can go to next step. +If an error has been encountered, it returns a negative value: + +@code + char buffer[256]; + int nbytes; + + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + while (nbytes > 0) + { + if (fwrite(buffer, 1, nbytes, stdout) != nbytes) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + } + + if (nbytes < 0) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } +@endcode + +Once you read the result of the remote command, you send an +end-of-file to the channel, close it, and free the memory +that it used: + +@code + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + + return SSH_OK; +} +@endcode + +*/ diff --git a/doc/curve25519-sha256@libssh.org.txt b/doc/curve25519-sha256@libssh.org.txt new file mode 100644 index 0000000..7554190 --- /dev/null +++ b/doc/curve25519-sha256@libssh.org.txt @@ -0,0 +1,119 @@ +curve25519-sha256@libssh.org.txt Aris Adamantiadis + 21/9/2013 + +1. Introduction + +This document describes the key exchange methode curve25519-sha256@libssh.org +for SSH version 2 protocol. It is provided as an alternative to the existing +key exchange mechanisms based on either Diffie-Hellman or Elliptic Curve Diffie- +Hellman [RFC5656]. +The reason is the following : During summer of 2013, revelations from ex- +consultant at NSA Edward Snowden gave proof that NSA willingly inserts backdoors +into softwares, hardware components and published standards. While it is still +believed that the mathematics behind ECC cryptography are still sound and solid, +some people (including Bruce Schneier [SCHNEIER]), showed their lack of confidence +in NIST-published curves such as nistp256, nistp384, nistp521, for which constant +parameters (including the generator point) are defined without explanation. It +is also believed that NSA had a word to say in their definition. These curves +are not the most secure or fastest possible for their key sizes [DJB], and +researchers think it is possible that NSA have ways of cracking NIST curves. +It is also interesting to note that SSH belongs to the list of protocols the NSA +claims to be able to eavesdrop. Having a secure replacement would make passive +attacks much harder if such a backdoor exists. + +However an alternative exists in the form of Curve25519. This algorithm has been +proposed in 2006 by DJB [Curve25519]. Its main strengths are its speed, its +constant-time run time (and resistance against side-channel attacks), and its +lack of nebulous hard-coded constants. + +The reference version being used in this document is the one described in +[Curve25519] as implemented in the library NaCl [NaCl]. +This document does not attempt to provide alternatives to the ecdsa-sha1-* +authentication keys. + +2. Key exchange + +The key exchange procedure is very similar to the one described chapter 4 of +[RFC5656]. Public ephemeral keys are transmitted over SSH encapsulated into +standard SSH strings. + +The following is an overview of the key exchange process: + +Client Server +------ ------ +Generate ephemeral key pair. +SSH_MSG_KEX_ECDH_INIT --------> + Verify that client public key + length is 32 bytes. + Generate ephemeral key pair. + Compute shared secret. + Generate and sign exchange hash. + <-------- SSH_MSG_KEX_ECDH_REPLY +Verify that server public key length is 32 bytes. +* Verify host keys belong to server. +Compute shared secret. +Generate exchange hash. +Verify server's signature. + +* Optional but strongly recommanded as this protects against MITM attacks. + +This is implemented using the same messages as described in RFC5656 chapter 4 + +3. Method Name + +The name of this key exchange method is "curve25519-sha256@libssh.org". + +4. Implementation considerations + +The whole method is based on the curve25519 scalar multiplication. In this +method, a private key is a scalar of 256 bits, and a public key is a point +of 256 bits. + +4.1. Private key generation + +A 32 bytes private key should be generated for each new connection, + using a secure PRNG. The following actions must be done on the private key: + mysecret[0] &= 248; + mysecret[31] &= 127; + mysecret[31] |= 64; +In order to keep the key valid. However, many cryptographic libraries will do +this automatically. +It should be noted that, in opposition to NIST curves, no special validation +should be done to ensure the result is a valid and secure private key. + +4.2 Public key generation + +The 32 bytes public key of either a client or a server must be generated using +the 32 bytes private key and a common generator base. This base is defined as 9 +followed by all zeroes: + const unsigned char basepoint[32] = {9}; + +The public key is calculated using the cryptographic scalar multiplication: + const unsigned char privkey[32]; + unsigned char pubkey[32]; + crypto_scalarmult (pubkey, privkey, basepoint); +However some cryptographic libraries may provide a combined function: + crypto_scalarmult_base (pubkey, privkey); + +It should be noted that, in opposition to NIST curves, no special validation +should be done to ensure the received public keys are valid curves point. The +Curve25519 algorithm ensure that every possible public key maps to a valid +ECC Point. + +4.3 Shared secret generation + +The shared secret, k, is defined in SSH specifications to be a big integer. +This number is calculated using the following procedure: + + X is the 32 bytes point obtained by the scalar multiplication of the other + side's public key and the local private key scalar. + + The whole 32 bytes of the number X are then converted into a big integer k. + This conversion follows the network byte order. This step differs from + RFC5656. + +[RFC5656] https://tools.ietf.org/html/rfc5656 +[SCHNEIER] https://www.schneier.com/blog/archives/2013/09/the_nsa_is_brea.html#c1675929 +[DJB] https://cr.yp.to/talks/2013.05.31/slides-dan+tanja-20130531-4x3.pdf +[Curve25519] "Curve25519: new Diffie-Hellman speed records." + https://cr.yp.to/ecdh/curve25519-20060209.pdf diff --git a/doc/forwarding.dox b/doc/forwarding.dox new file mode 100644 index 0000000..bb93c7b --- /dev/null +++ b/doc/forwarding.dox @@ -0,0 +1,230 @@ +/** +@page libssh_tutor_forwarding Chapter 7: Forwarding connections (tunnel) +@section forwarding_connections Forwarding connections + +Port forwarding comes in SSH protocol in two different flavours: +direct or reverse port forwarding. Direct port forwarding is also +named local port forwarding, and reverse port forwarding is also called +remote port forwarding. SSH also allows X11 tunnels. + + + +@subsection forwarding_direct Direct port forwarding + +Direct port forwarding is from client to server. The client opens a tunnel, +and forwards whatever data to the server. Then, the server connects to an +end point. The end point can reside on another machine or on the SSH +server itself. + +Example of use of direct port forwarding: +@verbatim +Mail client application Google Mail + | ^ + 5555 (arbitrary) | + | 143 (IMAP2) + V | + SSH client =====> SSH server + +Legend: +--P-->: port connections through port P +=====>: SSH tunnel +@endverbatim +A mail client connects to port 5555 of a client. An encrypted tunnel is +established to the server. The server connects to port 143 of Google Mail (the +end point). Now the local mail client can retrieve mail. + + +@subsection forwarding_reverse Reverse port forwarding + +The reverse forwarding is slightly different. It goes from server to client, +even though the client has the initiative of establishing the tunnel. +Once the tunnel is established, the server will listen on a port. Whenever +a connection to this port is made, the server forwards the data to the client. + +Example of use of reverse port forwarding: +@verbatim + Local mail server Mail client application + ^ | + | 5555 (arbitrary) + 143 (IMAP2) | + | V + SSH client <===== SSH server + +Legend: +--P-->: port connections through port P +=====>: SSH tunnel +@endverbatim +In this example, the SSH client establishes the tunnel, +but it is used to forward the connections established at +the server to the client. + + +@subsection forwarding_x11 X11 tunnels + +X11 tunnels allow a remote application to display locally. + +Example of use of X11 tunnels: +@verbatim + Local display Graphical application + (X11 server) (X11 client) + ^ | + | V + SSH client <===== SSH server + +Legend: +----->: X11 connection through X11 display number +=====>: SSH tunnel +@endverbatim +The SSH tunnel is established by the client. + +How to establish X11 tunnels with libssh has already been described in +this tutorial. + +@see x11 + + +@subsection libssh_direct Doing direct port forwarding with libssh + +To do direct port forwarding, call function ssh_channel_open_forward(): + - you need a separate channel for the tunnel as first parameter; + - second and third parameters are the remote endpoint; + - fourth and fifth parameters are sent to the remote server + so that they can be logged on that server. + +If you don't plan to forward the data you will receive to any local port, +just put fake values like "localhost" and 5555 as your local host and port. + +The example below shows how to open a direct channel that would be +used to retrieve google's home page from the remote SSH server. + +@code +int direct_forwarding(ssh_session session) +{ + ssh_channel forwarding_channel; + int rc; + char *http_get = "GET / HTTP/1.1\nHost: www.google.com\n\n"; + int nbytes, nwritten; + + forwarding_channel = ssh_channel_new(session); + if (forwarding_channel == NULL) { + return rc; + } + + rc = ssh_channel_open_forward(forwarding_channel, + "www.google.com", 80, + "localhost", 5555); + if (rc != SSH_OK) + { + ssh_channel_free(forwarding_channel); + return rc; + } + + nbytes = strlen(http_get); + nwritten = ssh_channel_write(forwarding_channel, + http_get, + nbytes); + if (nbytes != nwritten) + { + ssh_channel_free(forwarding_channel); + return SSH_ERROR; + } + + ... + + ssh_channel_free(forwarding_channel); + return SSH_OK; +} +@endcode + +The data sent by Google can be retrieved for example with ssh_select() +and ssh_channel_read(). Goggle's home page can then be displayed on the +local SSH client, saved into a local file, made available on a local port, +or whatever use you have for it. + + +@subsection libssh_reverse Doing reverse port forwarding with libssh + +To do reverse port forwarding, call ssh_channel_listen_forward(), +then ssh_channel_accept_forward(). + +When you call ssh_channel_listen_forward(), you can let the remote server +chose the non-privileged port it should listen to. Otherwise, you can chose +your own privileged or non-privileged port. Beware that you should have +administrative privileges on the remote server to open a privileged port +(port number < 1024). + +Below is an example of a very rough web server waiting for connections on port +8080 of remote SSH server. The incoming connections are passed to the +local libssh application, which handles them: + +@code +int web_server(ssh_session session) +{ + int rc; + ssh_channel channel; + char buffer[256]; + int nbytes, nwritten; + int port = 0; + char *helloworld = "" +"HTTP/1.1 200 OK\n" +"Content-Type: text/html\n" +"Content-Length: 113\n" +"\n" +"\n" +" \n" +" Hello, World!\n" +" \n" +" \n" +"

Hello, World!

\n" +" \n" +"\n"; + + rc = ssh_channel_listen_forward(session, NULL, 8080, NULL); + if (rc != SSH_OK) + { + fprintf(stderr, "Error opening remote port: %s\n", + ssh_get_error(session)); + return rc; + } + + channel = ssh_channel_accept_forward(session, 60000, &port); + if (channel == NULL) + { + fprintf(stderr, "Error waiting for incoming connection: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + while (1) + { + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (nbytes < 0) + { + fprintf(stderr, "Error reading incoming data: %s\n", + ssh_get_error(session)); + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } + if (strncmp(buffer, "GET /", 5)) continue; + + nbytes = strlen(helloworld); + nwritten = ssh_channel_write(channel, helloworld, nbytes); + if (nwritten != nbytes) + { + fprintf(stderr, "Error sending answer: %s\n", + ssh_get_error(session)); + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } + printf("Sent answer\n"); + } + + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + return SSH_OK; +} +@endcode + +*/ diff --git a/doc/guided_tour.dox b/doc/guided_tour.dox new file mode 100644 index 0000000..69576f1 --- /dev/null +++ b/doc/guided_tour.dox @@ -0,0 +1,473 @@ +/** +@page libssh_tutor_guided_tour Chapter 1: A typical SSH session +@section ssh_session A typical SSH session + +A SSH session goes through the following steps: + + - Before connecting to the server, you can set up if you wish one or other + server public key authentication, i.e. DSA or RSA. You can choose + cryptographic algorithms you trust and compression algorithms if any. You + must of course set up the hostname. + + - The connection is established. A secure handshake is made, and resulting from + it, a public key from the server is gained. You MUST verify that the public + key is legitimate, using for instance the MD5 fingerprint or the known hosts + file. + + - The client must authenticate: the classical ways are password, or + public keys (from dsa and rsa key-pairs generated by openssh). + If a SSH agent is running, it is possible to use it. + + - Now that the user has been authenticated, you must open one or several + channels. Channels are different subways for information into a single ssh + connection. Each channel has a standard stream (stdout) and an error stream + (stderr). You can theoretically open an infinity of channels. + + - With the channel you opened, you can do several things: + - Execute a single command. + - Open a shell. You may want to request a pseudo-terminal before. + - Invoke the sftp subsystem to transfer files. + - Invoke the scp subsystem to transfer files. + - Invoke your own subsystem. This is outside the scope of this document, + but can be done. + + - When everything is finished, just close the channels, and then the connection. + +The sftp and scp subsystems use channels, but libssh hides them to +the programmer. If you want to use those subsystems, instead of a channel, +you'll usually open a "sftp session" or a "scp session". + + +@subsection setup Creating the session and setting options + +The most important object in a SSH connection is the SSH session. In order +to allocate a new SSH session, you use ssh_new(). Don't forget to +always verify that the allocation succeeded. +@code +#include +#include + +int main() +{ + ssh_session my_ssh_session = ssh_new(); + if (my_ssh_session == NULL) + exit(-1); + ... + ssh_free(my_ssh_session); +} +@endcode + +libssh follows the allocate-it-deallocate-it pattern. Each object that you allocate +using xxxxx_new() must be deallocated using xxxxx_free(). In this case, ssh_new() +does the allocation and ssh_free() does the contrary. + +The ssh_options_set() function sets the options of the session. The most important options are: + - SSH_OPTIONS_HOST: the name of the host you want to connect to + - SSH_OPTIONS_PORT: the used port (default is port 22) + - SSH_OPTIONS_USER: the system user under which you want to connect + - SSH_OPTIONS_LOG_VERBOSITY: the quantity of messages that are printed + +The complete list of options can be found in the documentation of ssh_options_set(). +The only mandatory option is SSH_OPTIONS_HOST. If you don't use SSH_OPTIONS_USER, +the local username of your account will be used. + +Here is a small example of how to use it: + +@code +#include +#include + +int main() +{ + ssh_session my_ssh_session; + int verbosity = SSH_LOG_PROTOCOL; + int port = 22; + + my_ssh_session = ssh_new(); + if (my_ssh_session == NULL) + exit(-1); + + ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(my_ssh_session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(my_ssh_session, SSH_OPTIONS_PORT, &port); + + ... + + ssh_free(my_ssh_session); +} +@endcode + +Please notice that all parameters are passed to ssh_options_set() as pointers, +even if you need to set an integer value. + +@see ssh_new +@see ssh_free +@see ssh_options_set +@see ssh_options_parse_config +@see ssh_options_copy +@see ssh_options_getopt + + +@subsection connect Connecting to the server + +Once all settings have been made, you can connect using ssh_connect(). That +function will return SSH_OK if the connection worked, SSH_ERROR otherwise. + +You can get the English error string with ssh_get_error() in order to show the +user what went wrong. Then, use ssh_disconnect() when you want to stop +the session. + +Here's an example: + +@code +#include +#include +#include + +int main() +{ + ssh_session my_ssh_session; + int rc; + + my_ssh_session = ssh_new(); + if (my_ssh_session == NULL) + exit(-1); + + ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost"); + + rc = ssh_connect(my_ssh_session); + if (rc != SSH_OK) + { + fprintf(stderr, "Error connecting to localhost: %s\n", + ssh_get_error(my_ssh_session)); + exit(-1); + } + + ... + + ssh_disconnect(my_ssh_session); + ssh_free(my_ssh_session); +} +@endcode + + +@subsection serverauth Authenticating the server + +Once you're connected, the following step is mandatory: you must check that the server +you just connected to is known and safe to use (remember, SSH is about security and +authentication). + +There are two ways of doing this: + - The first way (recommended) is to use the ssh_session_is_known_server() + function. This function will look into the known host file + (~/.ssh/known_hosts on UNIX), look for the server hostname's pattern, + and determine whether this host is present or not in the list. + - The second way is to use ssh_get_pubkey_hash() to get a binary version + of the public key hash value. You can then use your own database to check + if this public key is known and secure. + +You can also use the ssh_get_pubkey_hash() to show the public key hash +value to the user, in case he knows what the public key hash value is +(some paranoid people write their public key hash values on paper before +going abroad, just in case ...). + +If the remote host is being used to for the first time, you can ask the user whether +he/she trusts it. Once he/she concluded that the host is valid and worth being +added in the known hosts file, you use ssh_write_knownhost() to register it in +the known hosts file, or any other way if you use your own database. + +The following example is part of the examples suite available in the +examples/ directory: + +@code +#include +#include + +int verify_knownhost(ssh_session session) +{ + enum ssh_known_hosts_e state; + unsigned char *hash = NULL; + ssh_key srv_pubkey = NULL; + size_t hlen; + char buf[10]; + char *hexa; + char *p; + int cmp; + int rc; + + rc = ssh_get_server_publickey(session, &srv_pubkey); + if (rc < 0) { + return -1; + } + + rc = ssh_get_publickey_hash(srv_pubkey, + SSH_PUBLICKEY_HASH_SHA1, + &hash, + &hlen); + ssh_key_free(srv_pubkey); + if (rc < 0) { + return -1; + } + + state = ssh_session_is_known_server(session); + switch (state) { + case SSH_KNOWN_HOSTS_OK: + /* OK */ + + break; + case SSH_KNOWN_HOSTS_CHANGED: + fprintf(stderr, "Host key for server changed: it is now:\n"); + ssh_print_hexa("Public key hash", hash, hlen); + fprintf(stderr, "For security reasons, connection will be stopped\n"); + ssh_clean_pubkey_hash(&hash); + + return -1; + case SSH_KNOWN_HOSTS_OTHER: + fprintf(stderr, "The host key for this server was not found but an other" + "type of key exists.\n"); + fprintf(stderr, "An attacker might change the default server key to" + "confuse your client into thinking the key does not exist\n"); + ssh_clean_pubkey_hash(&hash); + + return -1; + case SSH_KNOWN_HOSTS_NOT_FOUND: + fprintf(stderr, "Could not find known host file.\n"); + fprintf(stderr, "If you accept the host key here, the file will be" + "automatically created.\n"); + + /* FALL THROUGH to SSH_SERVER_NOT_KNOWN behavior */ + + case SSH_KNOWN_HOSTS_UNKNOWN: + hexa = ssh_get_hexa(hash, hlen); + fprintf(stderr,"The server is unknown. Do you trust the host key?\n"); + fprintf(stderr, "Public key hash: %s\n", hexa); + ssh_string_free_char(hexa); + ssh_clean_pubkey_hash(&hash); + p = fgets(buf, sizeof(buf), stdin); + if (p == NULL) { + return -1; + } + + cmp = strncasecmp(buf, "yes", 3); + if (cmp != 0) { + return -1; + } + + rc = ssh_session_update_known_hosts(session); + if (rc < 0) { + fprintf(stderr, "Error %s\n", strerror(errno)); + return -1; + } + + break; + case SSH_KNOWN_HOSTS_ERROR: + fprintf(stderr, "Error %s", ssh_get_error(session)); + ssh_clean_pubkey_hash(&hash); + return -1; + } + + ssh_clean_pubkey_hash(&hash); + return 0; +} +@endcode + +@see ssh_connect +@see ssh_disconnect +@see ssh_get_error +@see ssh_get_error_code +@see ssh_get_server_publickey +@see ssh_get_publickey_hash +@see ssh_session_is_known_server +@see ssh_session_update_known_hosts + + +@subsection auth Authenticating the user + +The authentication process is the way a service provider can identify a +user and verify his/her identity. The authorization process is about enabling +the authenticated user the access to resources. In SSH, the two concepts +are linked. After authentication, the server can grant the user access to +several resources such as port forwarding, shell, sftp subsystem, and so on. + +libssh supports several methods of authentication: + - "none" method. This method allows to get the available authentications + methods. It also gives the server a chance to authenticate the user with + just his/her login. Some very old hardware uses this feature to fallback + the user on a "telnet over SSH" style of login. + - password method. A password is sent to the server, which accepts it or not. + - keyboard-interactive method. The server sends several challenges to the + user, who must answer correctly. This makes possible the authentication + via a codebook for instance ("give code at 23:R on page 3"). + - public key method. The host knows the public key of the user, and the + user must prove he knows the associated private key. This can be done + manually, or delegated to the SSH agent as we'll see later. + +All these methods can be combined. You can for instance force the user to +authenticate with at least two of the authentication methods. In that case, +one speaks of "Partial authentication". A partial authentication is a +response from authentication functions stating that your credential was +accepted, but yet another one is required to get in. + +The example below shows an authentication with password: + +@code +#include +#include +#include + +int main() +{ + ssh_session my_ssh_session; + int rc; + char *password; + + // Open session and set options + my_ssh_session = ssh_new(); + if (my_ssh_session == NULL) + exit(-1); + ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost"); + + // Connect to server + rc = ssh_connect(my_ssh_session); + if (rc != SSH_OK) + { + fprintf(stderr, "Error connecting to localhost: %s\n", + ssh_get_error(my_ssh_session)); + ssh_free(my_ssh_session); + exit(-1); + } + + // Verify the server's identity + // For the source code of verify_knownhost(), check previous example + if (verify_knownhost(my_ssh_session) < 0) + { + ssh_disconnect(my_ssh_session); + ssh_free(my_ssh_session); + exit(-1); + } + + // Authenticate ourselves + password = getpass("Password: "); + rc = ssh_userauth_password(my_ssh_session, NULL, password); + if (rc != SSH_AUTH_SUCCESS) + { + fprintf(stderr, "Error authenticating with password: %s\n", + ssh_get_error(my_ssh_session)); + ssh_disconnect(my_ssh_session); + ssh_free(my_ssh_session); + exit(-1); + } + + ... + + ssh_disconnect(my_ssh_session); + ssh_free(my_ssh_session); +} +@endcode + +@see @ref authentication_details + + +@subsection using_ssh Doing something + +At this point, the authenticity of both server and client is established. +Time has come to take advantage of the many possibilities offered by the SSH +protocol: execute a remote command, open remote shells, transfer files, +forward ports, etc. + +The example below shows how to execute a remote command: + +@code +int show_remote_processes(ssh_session session) +{ + ssh_channel channel; + int rc; + char buffer[256]; + int nbytes; + + channel = ssh_channel_new(session); + if (channel == NULL) + return SSH_ERROR; + + rc = ssh_channel_open_session(channel); + if (rc != SSH_OK) + { + ssh_channel_free(channel); + return rc; + } + + rc = ssh_channel_request_exec(channel, "ps aux"); + if (rc != SSH_OK) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return rc; + } + + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + while (nbytes > 0) + { + if (write(1, buffer, nbytes) != (unsigned int) nbytes) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + } + + if (nbytes < 0) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } + + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + + return SSH_OK; +} +@endcode + +Each ssh_channel_request_exec() needs to be run on freshly created +and connected (with ssh_channel_open_session()) channel. + +@see @ref opening_shell +@see @ref remote_command +@see @ref sftp_subsystem +@see @ref scp_subsystem + + +@subsection errors Handling the errors + +All the libssh functions which return an error value also set an English error message +describing the problem. + +Error values are typically SSH_ERROR for integer values, or NULL for pointers. + +The function ssh_get_error() returns a pointer to the static error message. + +ssh_error_code() returns the error code number : SSH_NO_ERROR, +SSH_REQUEST_DENIED, SSH_INVALID_REQUEST, SSH_CONNECTION_LOST, SSH_FATAL, +or SSH_INVALID_DATA. SSH_REQUEST_DENIED means the ssh server refused your +request, but the situation is recoverable. The others mean something happened +to the connection (some encryption problems, server problems, ...). +SSH_INVALID_REQUEST means the library got some garbage from server, but +might be recoverable. SSH_FATAL means the connection has an important +problem and isn't probably recoverable. + +Most of time, the error returned are SSH_FATAL, but some functions +(generally the ssh_request_xxx ones) may fail because of server denying request. +In these cases, SSH_REQUEST_DENIED is returned. + +For thread safety, errors are bound to ssh_session objects. +As long as your ssh_session object is not NULL, you can retrieve the last error +message and error code from the ssh_session using ssh_get_error() and +ssh_get_error_code() respectively. + +The SFTP subsystem has its own error codes, in addition to libssh ones. + + +*/ diff --git a/doc/introduction.dox b/doc/introduction.dox new file mode 100644 index 0000000..f2f3d3d --- /dev/null +++ b/doc/introduction.dox @@ -0,0 +1,49 @@ +/** +@page libssh_tutorial The Tutorial +@section introduction Introduction + +libssh is a C library that enables you to write a program that uses the +SSH protocol. With it, you can remotely execute programs, transfer +files, or use a secure and transparent tunnel for your remote programs. +The SSH protocol is encrypted, ensures data integrity, and provides strong +means of authenticating both the server of the client. The library hides +a lot of technical details from the SSH protocol, but this does not +mean that you should not try to know about and understand these details. + +libssh is a Free Software / Open Source project. The libssh library +is distributed under LGPL license. The libssh project has nothing to do with +"libssh2", which is a completely different and independent project. + +libssh can run on top of either libgcrypt or libcrypto, +two general-purpose cryptographic libraries. + +This tutorial concentrates for its main part on the "client" side of libssh. +To learn how to accept incoming SSH connections (how to write a SSH server), +you'll have to jump to the end of this document. + +This tutorial describes libssh version 0.5.0. This version is a little different +from the 0.4.X series. However, the examples should work with +little changes on versions like 0.4.2 and later. + + +Table of contents: + +@subpage libssh_tutor_guided_tour + +@subpage libssh_tutor_authentication + +@subpage libssh_tutor_shell + +@subpage libssh_tutor_command + +@subpage libssh_tutor_sftp + +@subpage libssh_tutor_scp + +@subpage libssh_tutor_forwarding + +@subpage libssh_tutor_threads + +@subpage libssh_tutor_todo + +*/ diff --git a/doc/linking.dox b/doc/linking.dox new file mode 100644 index 0000000..7ae0d31 --- /dev/null +++ b/doc/linking.dox @@ -0,0 +1,33 @@ +/** + +@page libssh_linking The Linking HowTo + +@section dynamic Dynamic Linking + +On UNIX and Windows systems its the same, you need at least the libssh.h +header file and the libssh shared library. + +@section static Static Linking + +@warning The libssh library is licensed under the LGPL! Make sure you +understand what this means to your codebase if you want to distribute +binaries and link statically against LGPL code! + +On UNIX systems linking against the static version of the library is the +same as linking against the shared library. Both have the same name. Some +build system require to use the full path to the static library. + +To be able to compile the application you're developing you need to either pass +LIBSSH_STATIC as a define in the compiler command line or define it before you +include libssh.h. This is required cause the dynamic library needs to specify +the dllimport attribute. + +@code +#define LIBSSH_STATIC 1 +#include +@endcode + +If you're are statically linking with OpenSSL, read the "Linking your +application" section in the NOTES.[OS] in the OpenSSL source tree! + +*/ diff --git a/doc/mainpage.dox b/doc/mainpage.dox new file mode 100644 index 0000000..f4ff4d8 --- /dev/null +++ b/doc/mainpage.dox @@ -0,0 +1,252 @@ +/** + +@mainpage + +This is the online reference for developing with the libssh library. It +documents the libssh C API and the C++ wrapper. + +@section main-linking Linking + +We created a small howto how to link libssh against your application, read +@subpage libssh_linking. + +@section main-tutorial Tutorial + +You should start by reading @subpage libssh_tutorial, then reading the documentation of +the interesting functions as you go. + +@section main-features Features + +The libssh library provides: + + - Key Exchange Methods: curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group1-sha1, diffie-hellman-group14-sha1 + - Public Key Algorithms: ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, ssh-rsa, rsa-sha2-512, rsa-sha2-256,ssh-dss + - Ciphers: aes256-ctr, aes192-ctr, aes128-ctr, aes256-cbc (rijndael-cbc@lysator.liu.se), aes192-cbc, aes128-cbc, 3des-cbc, blowfish-cbc, none + - Compression Schemes: zlib, zlib@openssh.com, none + - MAC hashes: hmac-sha1, hmac-sha2-256, hmac-sha2-512, hmac-md5, none + - Authentication: none, password, public-key, keyboard-interactive, gssapi-with-mic + - Channels: shell, exec (incl. SCP wrapper), direct-tcpip, subsystem, auth-agent-req@openssh.com + - Global Requests: tcpip-forward, forwarded-tcpip + - Channel Requests: x11, pty, exit-status, signal, exit-signal, keepalive@openssh.com, auth-agent-req@openssh.com + - Subsystems: sftp(version 3), OpenSSH Extensions + - SFTP: statvfs@openssh.com, fstatvfs@openssh.com + - Thread-safe: Just don't share sessions + - Non-blocking: it can be used both blocking and non-blocking + - Your sockets: the app hands over the socket, or uses libssh sockets + - OpenSSL or gcrypt: builds with either + +@section main-additional-features Additional Features + + - Client and server support + - SSHv2 and SSHv1 protocol support + - Supports Linux, UNIX, BSD, Solaris, OS/2 and Windows + - Automated test cases with nightly tests + - Event model based on poll(2), or a poll(2)-emulation. + +@section main-copyright Copyright Policy + +libssh is a project with distributed copyright ownership, which means we prefer +the copyright on parts of libssh to be held by individuals rather than +corporations if possible. There are historical legal reasons for this, but one +of the best ways to explain it is that it’s much easier to work with +individuals who have ownership than corporate legal departments if we ever need +to make reasonable compromises with people using and working with libssh. + +We track the ownership of every part of libssh via git, our source code control +system, so we know the provenance of every piece of code that is committed to +libssh. + +So if possible, if you’re doing libssh changes on behalf of a company who +normally owns all the work you do please get them to assign personal copyright +ownership of your changes to you as an individual, that makes things very easy +for us to work with and avoids bringing corporate legal departments into the +picture. + +If you can’t do this we can still accept patches from you owned by your +employer under a standard employment contract with corporate copyright +ownership. It just requires a simple set-up process first. + +We use a process very similar to the way things are done in the Linux Kernel +community, so it should be very easy to get a sign off from your corporate +legal department. The only changes we’ve made are to accommodate the license we +use, which is LGPLv2 (or later) whereas the Linux kernel uses GPLv2. + +The process is called signing. + +How to sign your work +---------------------- + +Once you have permission to contribute to libssh from your employer, simply +email a copy of the following text from your corporate email address to: + +contributing@libssh.org + +@verbatim +libssh Developer's Certificate of Origin. Version 1.0 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the appropriate + version of the GNU General Public License; or + +(b) The contribution is based upon previous work that, to the best of + my knowledge, is covered under an appropriate open source license + and I have the right under that license to submit that work with + modifications, whether created in whole or in part by me, under + the GNU General Public License, in the appropriate version; or + +(c) The contribution was provided directly to me by some other + person who certified (a) or (b) and I have not modified it. + +(d) I understand and agree that this project and the contribution are + public and that a record of the contribution (including all + metadata and personal information I submit with it, including my + sign-off) is maintained indefinitely and may be redistributed + consistent with the libssh Team's policies and the requirements of + the GNU GPL where they are relevant. + +(e) I am granting this work to this project 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 the option of the project) any later version. + +https://www.gnu.org/licenses/lgpl-2.1.html +@endverbatim + +We will maintain a copy of that email as a record that you have the rights to +contribute code to libssh under the required licenses whilst working for the +company where the email came from. + +Then when sending in a patch via the normal mechanisms described above, add a +line that states: + +@verbatim + Signed-off-by: Random J Developer +@endverbatim + +using your real name and the email address you sent the original email you used +to send the libssh Developer’s Certificate of Origin to us (sorry, no +pseudonyms or anonymous contributions.) + +That’s it! Such code can then quite happily contain changes that have copyright +messages such as: + +@verbatim + (c) Example Corporation. +@endverbatim + +and can be merged into the libssh codebase in the same way as patches from any +other individual. You don’t need to send in a copy of the libssh Developer’s +Certificate of Origin for each patch, or inside each patch. Just the sign-off +message is all that is required once we’ve received the initial email. + +Have fun and happy libssh hacking! + +The libssh Team + +@section main-rfc Internet standard + +@subsection main-rfc-secsh Secure Shell (SSH) + +The following RFC documents described SSH-2 protcol as an Internet standard. + + - RFC 4250, + The Secure Shell (SSH) Protocol Assigned Numbers + - RFC 4251, + The Secure Shell (SSH) Protocol Architecture + - RFC 4252, + The Secure Shell (SSH) Authentication Protocol + - RFC 4253, + The Secure Shell (SSH) Transport Layer Protocol + - RFC 4254, + The Secure Shell (SSH) Connection Protocol + - RFC 4255, + Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints + (not implemented in libssh) + - RFC 4256, + Generic Message Exchange Authentication for the Secure Shell Protocol (SSH) + - RFC 4335, + The Secure Shell (SSH) Session Channel Break Extension + - RFC 4344, + The Secure Shell (SSH) Transport Layer Encryption Modes + - RFC 4345, + Improved Arcfour Modes for the Secure Shell (SSH) Transport Layer Protocol + +It was later modified and expanded by the following RFCs. + + - RFC 4419, + Diffie-Hellman Group Exchange for the Secure Shell (SSH) Transport Layer + Protocol + - RFC 4432, + RSA Key Exchange for the Secure Shell (SSH) Transport Layer Protocol + (not implemented in libssh) + - RFC 4462, + Generic Security Service Application Program Interface (GSS-API) + Authentication and Key Exchange for the Secure Shell (SSH) Protocol + (only the authentication implemented in libssh) + - RFC 4716, + The Secure Shell (SSH) Public Key File Format + (not implemented in libssh) + - RFC 5647, + AES Galois Counter Mode for the Secure Shell Transport Layer Protocol + (the algorithm negotiation implemented according to openssh.com) + - RFC 5656, + Elliptic Curve Algorithm Integration in the Secure Shell Transport Layer + - RFC 6594, + Use of the SHA-256 Algorithm with RSA, DSA, and ECDSA in SSHFP Resource Records + (not implemented in libssh) + - RFC 6668, + SHA-2 Data Integrity Verification for the Secure Shell (SSH) Transport Layer Protocol + - RFC 7479, + Using Ed25519 in SSHFP Resource Records + (not implemented in libssh) + - RFC 8160, + IUTF8 Terminal Mode in Secure Shell (SSH) + (not handled in libssh) + - RFC 8270, + Increase the Secure Shell Minimum Recommended Diffie-Hellman Modulus Size to 2048 Bits + - RFC 8308, + Extension Negotiation in the Secure Shell (SSH) Protocol + (only the "server-sig-algs" extension implemented) + - RFC 8332, + Use of RSA Keys with SHA-256 and SHA-512 in the Secure Shell (SSH) Protocol + +There are also drafts that are being currently developed and followed. + + - draft-ietf-curdle-ssh-kex-sha2-10 + Key Exchange (KEX) Method Updates and Recommendations for Secure Shell (SSH) + - draft-miller-ssh-agent-03 + SSH Agent Protocol + - draft-ietf-curdle-ssh-curves-12 + Secure Shell (SSH) Key Exchange Method using Curve25519 and Curve448 + +Interesting cryptography documents: + + - PKCS #11, PKCS #11 reference documents, describing interface with smartcards. + +@subsection main-rfc-sftp Secure Shell File Transfer Protocol (SFTP) + +The protocol is not an Internet standard but it is still widely implemented. +OpenSSH and most other implementation implement Version 3 of the protocol. We +do the same in libssh. + + - + draft-ietf-secsh-filexfer-02.txt, + SSH File Transfer Protocol + +@subsection main-rfc-extensions Secure Shell Extensions + +The OpenSSH project has defined some extensions to the protocol. We support some of +them like the statvfs calls in SFTP or the ssh-agent. + + - + OpenSSH's deviations and extensions + - + OpenSSH's pubkey certificate authentication + - + chacha20-poly1305@openssh.com authenticated encryption mode + - + OpenSSH private key format (openssh-key-v1) + +*/ diff --git a/doc/scp.dox b/doc/scp.dox new file mode 100644 index 0000000..618857e --- /dev/null +++ b/doc/scp.dox @@ -0,0 +1,268 @@ +/** +@page libssh_tutor_scp Chapter 6: The SCP subsystem +@section scp_subsystem The SCP subsystem + +The SCP subsystem has far less functionality than the SFTP subsystem. +However, if you only need to copy files from and to the remote system, +it does its job. + + +@subsection scp_session Opening and closing a SCP session + +Like in the SFTP subsystem, you don't handle the SSH channels directly. +Instead, you open a "SCP session". + +When you open your SCP session, you have to choose between read or write mode. +You can't do both in the same session. So you specify either SSH_SCP_READ or +SSH_SCP_WRITE as the second parameter of function ssh_scp_new(). + +Another important mode flag for opening your SCP session is SSH_SCP_RECURSIVE. +When you use SSH_SCP_RECURSIVE, you declare that you are willing to emulate +the behaviour of "scp -r" command in your program, no matter it is for +reading or for writing. + +Once your session is created, you initialize it with ssh_scp_init(). When +you have finished transferring files, you terminate the SCP connection with +ssh_scp_close(). Finally, you can dispose the SCP connection with +ssh_scp_free(). + +The example below does the maintenance work to open a SCP connection for writing in +recursive mode: + +@code +int scp_write(ssh_session session) +{ + ssh_scp scp; + int rc; + + scp = ssh_scp_new + (session, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, "."); + if (scp == NULL) + { + fprintf(stderr, "Error allocating scp session: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + rc = ssh_scp_init(scp); + if (rc != SSH_OK) + { + fprintf(stderr, "Error initializing scp session: %s\n", + ssh_get_error(session)); + ssh_scp_free(scp); + return rc; + } + + ... + + ssh_scp_close(scp); + ssh_scp_free(scp); + return SSH_OK; +} +@endcode + +The example below shows how to open a connection to read a single file: + +@code +int scp_read(ssh_session session) +{ + ssh_scp scp; + int rc; + + scp = ssh_scp_new + (session, SSH_SCP_READ, "helloworld/helloworld.txt"); + if (scp == NULL) + { + fprintf(stderr, "Error allocating scp session: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + rc = ssh_scp_init(scp); + if (rc != SSH_OK) + { + fprintf(stderr, "Error initializing scp session: %s\n", + ssh_get_error(session)); + ssh_scp_free(scp); + return rc; + } + + ... + + ssh_scp_close(scp); + ssh_scp_free(scp); + return SSH_OK; +} + +@endcode + + +@subsection scp_write Creating files and directories + +You create directories with ssh_scp_push_directory(). In recursive mode, +you are placed in this directory once it is created. If the directory +already exists and if you are in recursive mode, you simply enter that +directory. + +Creating files is done in two steps. First, you prepare the writing with +ssh_scp_push_file(). Then, you write the data with ssh_scp_write(). +The length of the data to write must be identical between both function calls. +There's no need to "open" nor "close" the file, this is done automatically +on the remote end. If the file already exists, it is overwritten and truncated. + +The following example creates a new directory named "helloworld/", then creates +a file named "helloworld.txt" in that directory: + +@code +int scp_helloworld(ssh_session session, ssh_scp scp) +{ + int rc; + const char *helloworld = "Hello, world!\n"; + int length = strlen(helloworld); + + rc = ssh_scp_push_directory(scp, "helloworld", S_IRWXU); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't create remote directory: %s\n", + ssh_get_error(session)); + return rc; + } + + rc = ssh_scp_push_file + (scp, "helloworld.txt", length, S_IRUSR | S_IWUSR); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't open remote file: %s\n", + ssh_get_error(session)); + return rc; + } + + rc = ssh_scp_write(scp, helloworld, length); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't write to remote file: %s\n", + ssh_get_error(session)); + return rc; + } + + return SSH_OK; +} +@endcode + + +@subsection scp_recursive_write Copying full directory trees to the remote server + +Let's say you want to copy the following tree of files to the remote site: + +@verbatim + +-- file1 + +-- B --+ + | +-- file2 +-- A --+ + | +-- file3 + +-- C --+ + +-- file4 +@endverbatim + +You would do it that way: + - open the session in recursive mode + - enter directory A + - enter its subdirectory B + - create file1 in B + - create file2 in B + - leave directory B + - enter subdirectory C + - create file3 in C + - create file4 in C + - leave directory C + - leave directory A + +To leave a directory, call ssh_scp_leave_directory(). + + +@subsection scp_read Reading files and directories + + +To receive files, you pull requests from the other side with ssh_scp_pull_request(). +If this function returns SSH_SCP_REQUEST_NEWFILE, then you must get ready for +the reception. You can get the size of the data to receive with ssh_scp_request_get_size() +and allocate a buffer accordingly. When you are ready, you accept the request with +ssh_scp_accept_request(), then read the data with ssh_scp_read(). + +The following example receives a single file. The name of the file to +receive has been given earlier, when the scp session was opened: + +@code +int scp_receive(ssh_session session, ssh_scp scp) +{ + int rc; + int size, mode; + char *filename, *buffer; + + rc = ssh_scp_pull_request(scp); + if (rc != SSH_SCP_REQUEST_NEWFILE) + { + fprintf(stderr, "Error receiving information about file: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + size = ssh_scp_request_get_size(scp); + filename = strdup(ssh_scp_request_get_filename(scp)); + mode = ssh_scp_request_get_permissions(scp); + printf("Receiving file %s, size %d, permissions 0%o\n", + filename, size, mode); + free(filename); + + buffer = malloc(size); + if (buffer == NULL) + { + fprintf(stderr, "Memory allocation error\n"); + return SSH_ERROR; + } + + ssh_scp_accept_request(scp); + rc = ssh_scp_read(scp, buffer, size); + if (rc == SSH_ERROR) + { + fprintf(stderr, "Error receiving file data: %s\n", + ssh_get_error(session)); + free(buffer); + return rc; + } + printf("Done\n"); + + write(1, buffer, size); + free(buffer); + + rc = ssh_scp_pull_request(scp); + if (rc != SSH_SCP_REQUEST_EOF) + { + fprintf(stderr, "Unexpected request: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + return SSH_OK; +} +@endcode + +In this example, since we just requested a single file, we expect ssh_scp_request() +to return SSH_SCP_REQUEST_NEWFILE first, then SSH_SCP_REQUEST_EOF. That's quite a +naive approach; for example, the remote server might send a warning as well +(return code SSH_SCP_REQUEST_WARNING) and the example would fail. A more comprehensive +reception program would receive the requests in a loop and analyze them carefully +until SSH_SCP_REQUEST_EOF has been received. + + +@subsection scp_recursive_read Receiving full directory trees from the remote server + +If you opened the SCP session in recursive mode, the remote end will be +telling you when to change directory. + +In that case, when ssh_scp_pull_request() answers +SSH_SCP_REQUEST_NEWDIRECTORY, you should make that local directory (if +it does not exist yet) and enter it. When ssh_scp_pull_request() answers +SSH_SCP_REQUEST_ENDDIRECTORY, you should leave the current directory. + +*/ diff --git a/doc/sftp.dox b/doc/sftp.dox new file mode 100644 index 0000000..1f99cfd --- /dev/null +++ b/doc/sftp.dox @@ -0,0 +1,431 @@ +/** +@page libssh_tutor_sftp Chapter 5: The SFTP subsystem +@section sftp_subsystem The SFTP subsystem + +SFTP stands for "Secure File Transfer Protocol". It enables you to safely +transfer files between the local and the remote computer. It reminds a lot +of the old FTP protocol. + +SFTP is a rich protocol. It lets you do over the network almost everything +that you can do with local files: + - send files + - modify only a portion of a file + - receive files + - receive only a portion of a file + - get file owner and group + - get file permissions + - set file owner and group + - set file permissions + - remove files + - rename files + - create a directory + - remove a directory + - retrieve the list of files in a directory + - get the target of a symbolic link + - create symbolic links + - get information about mounted filesystems. + +The current implemented version of the SFTP protocol is version 3. All functions +aren't implemented yet, but the most important are. + + +@subsection sftp_session Opening and closing a SFTP session + +Unlike with remote shells and remote commands, when you use the SFTP subsystem, +you don't handle directly the SSH channels. Instead, you open a "SFTP session". + +The function sftp_new() creates a new SFTP session. The function sftp_init() +initializes it. The function sftp_free() deletes it. + +As you see, all the SFTP-related functions start with the "sftp_" prefix +instead of the usual "ssh_" prefix. + +The example below shows how to use these functions: + +@code +#include + +int sftp_helloworld(ssh_session session) +{ + sftp_session sftp; + int rc; + + sftp = sftp_new(session); + if (sftp == NULL) + { + fprintf(stderr, "Error allocating SFTP session: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + rc = sftp_init(sftp); + if (rc != SSH_OK) + { + fprintf(stderr, "Error initializing SFTP session: code %d.\n", + sftp_get_error(sftp)); + sftp_free(sftp); + return rc; + } + + ... + + sftp_free(sftp); + return SSH_OK; +} +@endcode + + +@subsection sftp_errors Analyzing SFTP errors + +In case of a problem, the function sftp_get_error() returns a SFTP-specific +error number, in addition to the regular SSH error number returned by +ssh_get_error_number(). + +Possible errors are: + - SSH_FX_OK: no error + - SSH_FX_EOF: end-of-file encountered + - SSH_FX_NO_SUCH_FILE: file does not exist + - SSH_FX_PERMISSION_DENIED: permission denied + - SSH_FX_FAILURE: generic failure + - SSH_FX_BAD_MESSAGE: garbage received from server + - SSH_FX_NO_CONNECTION: no connection has been set up + - SSH_FX_CONNECTION_LOST: there was a connection, but we lost it + - SSH_FX_OP_UNSUPPORTED: operation not supported by libssh yet + - SSH_FX_INVALID_HANDLE: invalid file handle + - SSH_FX_NO_SUCH_PATH: no such file or directory path exists + - SSH_FX_FILE_ALREADY_EXISTS: an attempt to create an already existing file or directory has been made + - SSH_FX_WRITE_PROTECT: write-protected filesystem + - SSH_FX_NO_MEDIA: no media was in remote drive + + +@subsection sftp_mkdir Creating a directory + +The function sftp_mkdir() takes the "SFTP session" we just created as +its first argument. It also needs the name of the file to create, and the +desired permissions. The permissions are the same as for the usual mkdir() +function. To get a comprehensive list of the available permissions, use the +"man 2 stat" command. The desired permissions are combined with the remote +user's mask to determine the effective permissions. + +The code below creates a directory named "helloworld" in the current directory that +can be read and written only by its owner: + +@code +#include +#include + +int sftp_helloworld(ssh_session session, sftp_session sftp) +{ + int rc; + + rc = sftp_mkdir(sftp, "helloworld", S_IRWXU); + if (rc != SSH_OK) + { + if (sftp_get_error(sftp) != SSH_FX_FILE_ALREADY_EXISTS) + { + fprintf(stderr, "Can't create directory: %s\n", + ssh_get_error(session)); + return rc; + } + } + + ... + + return SSH_OK; +} +@endcode + +Unlike its equivalent in the SCP subsystem, this function does NOT change the +current directory to the newly created subdirectory. + + +@subsection sftp_write Copying a file to the remote computer + +You handle the contents of a remote file just like you would do with a +local file: you open the file in a given mode, move the file pointer in it, +read or write data, and close the file. + +The sftp_open() function is very similar to the regular open() function, +excepted that it returns a file handle of type sftp_file. This file handle +is then used by the other file manipulation functions and remains valid +until you close the remote file with sftp_close(). + +The example below creates a new file named "helloworld.txt" in the +newly created "helloworld" directory. If the file already exists, it will +be truncated. It then writes the famous "Hello, World!" sentence to the +file, followed by a new line character. Finally, the file is closed: + +@code +#include +#include +#include + +int sftp_helloworld(ssh_session session, sftp_session sftp) +{ + int access_type = O_WRONLY | O_CREAT | O_TRUNC; + sftp_file file; + const char *helloworld = "Hello, World!\n"; + int length = strlen(helloworld); + int rc, nwritten; + + ... + + file = sftp_open(sftp, "helloworld/helloworld.txt", + access_type, S_IRWXU); + if (file == NULL) + { + fprintf(stderr, "Can't open file for writing: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + nwritten = sftp_write(file, helloworld, length); + if (nwritten != length) + { + fprintf(stderr, "Can't write data to file: %s\n", + ssh_get_error(session)); + sftp_close(file); + return SSH_ERROR; + } + + rc = sftp_close(file); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't close the written file: %s\n", + ssh_get_error(session)); + return rc; + } + + return SSH_OK; +} +@endcode + + +@subsection sftp_read Reading a file from the remote computer + +The nice thing with reading a file over the network through SFTP is that it +can be done both in a synchronous way or an asynchronous way. If you read the file +asynchronously, your program can do something else while it waits for the +results to come. + +Synchronous read is done with sftp_read(). + +Files are normally transferred in chunks. A good chunk size is 16 KB. The following +example transfers the remote file "/etc/profile" in 16 KB chunks. For each chunk we +request, sftp_read blocks till the data has been received: + +@code +// Good chunk size +#define MAX_XFER_BUF_SIZE 16384 + +int sftp_read_sync(ssh_session session, sftp_session sftp) +{ + int access_type; + sftp_file file; + char buffer[MAX_XFER_BUF_SIZE]; + int nbytes, nwritten, rc; + int fd; + + access_type = O_RDONLY; + file = sftp_open(sftp, "/etc/profile", + access_type, 0); + if (file == NULL) { + fprintf(stderr, "Can't open file for reading: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + fd = open("/path/to/profile", O_CREAT); + if (fd < 0) { + fprintf(stderr, "Can't open file for writing: %s\n", + strerror(errno)); + return SSH_ERROR; + } + + for (;;) { + nbytes = sftp_read(file, buffer, sizeof(buffer)); + if (nbytes == 0) { + break; // EOF + } else if (nbytes < 0) { + fprintf(stderr, "Error while reading file: %s\n", + ssh_get_error(session)); + sftp_close(file); + return SSH_ERROR; + } + + nwritten = write(fd, buffer, nbytes); + if (nwritten != nbytes) { + fprintf(stderr, "Error writing: %s\n", + strerror(errno)); + sftp_close(file); + return SSH_ERROR; + } + } + + rc = sftp_close(file); + if (rc != SSH_OK) { + fprintf(stderr, "Can't close the read file: %s\n", + ssh_get_error(session)); + return rc; + } + + return SSH_OK; +} +@endcode + +Asynchronous read is done in two steps, first sftp_async_read_begin(), which +returns a "request handle", and then sftp_async_read(), which uses that request handle. +If the file has been opened in nonblocking mode, then sftp_async_read() +might return SSH_AGAIN, which means that the request hasn't completed yet +and that the function should be called again later on. Otherwise, +sftp_async_read() waits for the data to come. To open a file in nonblocking mode, +call sftp_file_set_nonblocking() right after you opened it. Default is blocking mode. + +The example below reads a very big file in asynchronous, nonblocking, mode. Each +time the data is not ready yet, a counter is incremented. + +@code +// Good chunk size +#define MAX_XFER_BUF_SIZE 16384 + +int sftp_read_async(ssh_session session, sftp_session sftp) +{ + int access_type; + sftp_file file; + char buffer[MAX_XFER_BUF_SIZE]; + int async_request; + int nbytes; + long counter; + int rc; + + access_type = O_RDONLY; + file = sftp_open(sftp, "some_very_big_file", + access_type, 0); + if (file == NULL) { + fprintf(stderr, "Can't open file for reading: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + sftp_file_set_nonblocking(file); + + async_request = sftp_async_read_begin(file, sizeof(buffer)); + counter = 0L; + usleep(10000); + if (async_request >= 0) { + nbytes = sftp_async_read(file, buffer, sizeof(buffer), + async_request); + } else { + nbytes = -1; + } + + while (nbytes > 0 || nbytes == SSH_AGAIN) { + if (nbytes > 0) { + write(1, buffer, nbytes); + async_request = sftp_async_read_begin(file, sizeof(buffer)); + } else { + counter++; + } + usleep(10000); + + if (async_request >= 0) { + nbytes = sftp_async_read(file, buffer, sizeof(buffer), + async_request); + } else { + nbytes = -1; + } + } + + if (nbytes < 0) { + fprintf(stderr, "Error while reading file: %s\n", + ssh_get_error(session)); + sftp_close(file); + return SSH_ERROR; + } + + printf("The counter has reached value: %ld\n", counter); + + rc = sftp_close(file); + if (rc != SSH_OK) { + fprintf(stderr, "Can't close the read file: %s\n", + ssh_get_error(session)); + return rc; + } + + return SSH_OK; +} +@endcode + +@subsection sftp_ls Listing the contents of a directory + +The functions sftp_opendir(), sftp_readdir(), sftp_dir_eof(), +and sftp_closedir() enable to list the contents of a directory. +They use a new handle_type, "sftp_dir", which gives access to the +directory being read. + +In addition, sftp_readdir() returns a "sftp_attributes" which is a pointer +to a structure with information about a directory entry: + - name: the name of the file or directory + - size: its size in bytes + - etc. + +sftp_readdir() might return NULL under two conditions: + - when the end of the directory has been met + - when an error occurred + +To tell the difference, call sftp_dir_eof(). + +The attributes must be freed with sftp_attributes_free() when no longer +needed. + +The following example reads the contents of some remote directory: + +@code +int sftp_list_dir(ssh_session session, sftp_session sftp) +{ + sftp_dir dir; + sftp_attributes attributes; + int rc; + + dir = sftp_opendir(sftp, "/var/log"); + if (!dir) + { + fprintf(stderr, "Directory not opened: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + printf("Name Size Perms Owner\tGroup\n"); + + while ((attributes = sftp_readdir(sftp, dir)) != NULL) + { + printf("%-20s %10llu %.8o %s(%d)\t%s(%d)\n", + attributes->name, + (long long unsigned int) attributes->size, + attributes->permissions, + attributes->owner, + attributes->uid, + attributes->group, + attributes->gid); + + sftp_attributes_free(attributes); + } + + if (!sftp_dir_eof(dir)) + { + fprintf(stderr, "Can't list directory: %s\n", + ssh_get_error(session)); + sftp_closedir(dir); + return SSH_ERROR; + } + + rc = sftp_closedir(dir); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't close directory: %s\n", + ssh_get_error(session)); + return rc; + } +} +@endcode + +*/ diff --git a/doc/shell.dox b/doc/shell.dox new file mode 100644 index 0000000..0693bbc --- /dev/null +++ b/doc/shell.dox @@ -0,0 +1,361 @@ +/** +@page libssh_tutor_shell Chapter 3: Opening a remote shell +@section opening_shell Opening a remote shell + +We already mentioned that a single SSH connection can be shared +between several "channels". Channels can be used for different purposes. + +This chapter shows how to open one of these channels, and how to use it to +start a command interpreter on a remote computer. + + +@subsection open_channel Opening and closing a channel + +The ssh_channel_new() function creates a channel. It returns the channel as +a variable of type ssh_channel. + +Once you have this channel, you open a SSH session that uses it with +ssh_channel_open_session(). + +Once you don't need the channel anymore, you can send an end-of-file +to it with ssh_channel_close(). At this point, you can destroy the channel +with ssh_channel_free(). + +The code sample below achieves these tasks: + +@code +int shell_session(ssh_session session) +{ + ssh_channel channel; + int rc; + + channel = ssh_channel_new(session); + if (channel == NULL) + return SSH_ERROR; + + rc = ssh_channel_open_session(channel); + if (rc != SSH_OK) + { + ssh_channel_free(channel); + return rc; + } + + ... + + ssh_channel_close(channel); + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + + return SSH_OK; +} +@endcode + + +@subsection interactive Interactive and non-interactive sessions + +A "shell" is a command interpreter. It is said to be "interactive" +if there is a human user typing the commands, one after the +other. The contrary, a non-interactive shell, is similar to +the execution of commands in the background: there is no attached +terminal. + +If you plan using an interactive shell, you need to create a +pseud-terminal on the remote side. A remote terminal is usually referred +to as a "pty", for "pseudo-teletype". The remote processes won't see the +difference with a real text-oriented terminal. + +If needed, you request the pty with the function ssh_channel_request_pty(). +Then you define its dimensions (number of rows and columns) +with ssh_channel_change_pty_size(). + +Be your session interactive or not, the next step is to request a +shell with ssh_channel_request_shell(). + +@code +int interactive_shell_session(ssh_channel channel) +{ + int rc; + + rc = ssh_channel_request_pty(channel); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_change_pty_size(channel, 80, 24); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_shell(channel); + if (rc != SSH_OK) return rc; + + ... + + return rc; +} +@endcode + + +@subsection read_data Displaying the data sent by the remote computer + +In your program, you will usually need to receive all the data "displayed" +into the remote pty. You will usually analyse, log, or display this data. + +ssh_channel_read() and ssh_channel_read_nonblocking() are the simplest +way to read data from a channel. If you only need to read from a single +channel, they should be enough. + +The example below shows how to wait for remote data using ssh_channel_read(): + +@code +int interactive_shell_session(ssh_channel channel) +{ + int rc; + char buffer[256]; + int nbytes; + + rc = ssh_channel_request_pty(channel); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_change_pty_size(channel, 80, 24); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_shell(channel); + if (rc != SSH_OK) return rc; + + while (ssh_channel_is_open(channel) && + !ssh_channel_is_eof(channel)) + { + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (nbytes < 0) + return SSH_ERROR; + + if (nbytes > 0) + write(1, buffer, nbytes); + } + + return rc; +} +@endcode + +Unlike ssh_channel_read(), ssh_channel_read_nonblocking() never waits for +remote data to be ready. It returns immediately. + +If you plan to use ssh_channel_read_nonblocking() repeatedly in a loop, +you should use a "passive wait" function like usleep(3) in the same +loop. Otherwise, your program will consume all the CPU time, and your +computer might become unresponsive. + + +@subsection write_data Sending user input to the remote computer + +User's input is sent to the remote site with ssh_channel_write(). + +The following example shows how to combine a nonblocking read from a SSH +channel with a nonblocking read from the keyboard. The local input is then +sent to the remote computer: + +@code +/* Under Linux, this function determines whether a key has been pressed. + Under Windows, it is a standard function, so you need not redefine it. +*/ +int kbhit() +{ + struct timeval tv = { 0L, 0L }; + fd_set fds; + + FD_ZERO(&fds); + FD_SET(0, &fds); + + return select(1, &fds, NULL, NULL, &tv); +} + +/* A very simple terminal emulator: + - print data received from the remote computer + - send keyboard input to the remote computer +*/ +int interactive_shell_session(ssh_channel channel) +{ + /* Session and terminal initialization skipped */ + ... + + char buffer[256]; + int nbytes, nwritten; + + while (ssh_channel_is_open(channel) && + !ssh_channel_is_eof(channel)) + { + nbytes = ssh_channel_read_nonblocking(channel, buffer, sizeof(buffer), 0); + if (nbytes < 0) return SSH_ERROR; + if (nbytes > 0) + { + nwritten = write(1, buffer, nbytes); + if (nwritten != nbytes) return SSH_ERROR; + + if (!kbhit()) + { + usleep(50000L); // 0.05 second + continue; + } + + nbytes = read(0, buffer, sizeof(buffer)); + if (nbytes < 0) return SSH_ERROR; + if (nbytes > 0) + { + nwritten = ssh_channel_write(channel, buffer, nbytes); + if (nwritten != nbytes) return SSH_ERROR; + } + } + + return rc; +} +@endcode + +Of course, this is a poor terminal emulator, since the echo from the keys +pressed should not be done locally, but should be done by the remote side. +Also, user's input should not be sent once "Enter" key is pressed, but +immediately after each key is pressed. This can be accomplished +by setting the local terminal to "raw" mode with the cfmakeraw(3) function. +cfmakeraw() is a standard function under Linux, on other systems you can +recode it with: + +@code +static void cfmakeraw(struct termios *termios_p) +{ + termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); + termios_p->c_oflag &= ~OPOST; + termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); + termios_p->c_cflag &= ~(CSIZE|PARENB); + termios_p->c_cflag |= CS8; +} +@endcode + +If you are not using a local terminal, but some kind of graphical +environment, the solution to this kind of "echo" problems will be different. + + +@subsection select_loop A more elaborate way to get the remote data + +*** Warning: ssh_select() and ssh_channel_select() are not relevant anymore, + since libssh is about to provide an easier system for asynchronous + communications. This subsection should be removed then. *** + +ssh_channel_read() and ssh_channel_read_nonblocking() functions are simple, +but they are not adapted when you expect data from more than one SSH channel, +or from other file descriptors. Last example showed how getting data from +the standard input (the keyboard) at the same time as data from the SSH +channel was complicated. The functions ssh_select() and ssh_channel_select() +provide a more elegant way to wait for data coming from many sources. + +The functions ssh_select() and ssh_channel_select() remind of the standard +UNIX select(2) function. The idea is to wait for "something" to happen: +incoming data to be read, outgoing data to block, or an exception to +occur. Both these functions do a "passive wait", i.e. you can safely use +them repeatedly in a loop, it will not consume exaggerate processor time +and make your computer unresponsive. It is quite common to use these +functions in your application's main loop. + +The difference between ssh_select() and ssh_channel_select() is that +ssh_channel_select() is simpler, but allows you only to watch SSH channels. +ssh_select() is more complete and enables watching regular file descriptors +as well, in the same function call. + +Below is an example of a function that waits both for remote SSH data to come, +as well as standard input from the keyboard: + +@code +int interactive_shell_session(ssh_session session, ssh_channel channel) +{ + /* Session and terminal initialization skipped */ + ... + + char buffer[256]; + int nbytes, nwritten; + + while (ssh_channel_is_open(channel) && + !ssh_channel_is_eof(channel)) + { + struct timeval timeout; + ssh_channel in_channels[2], out_channels[2]; + fd_set fds; + int maxfd; + + timeout.tv_sec = 30; + timeout.tv_usec = 0; + in_channels[0] = channel; + in_channels[1] = NULL; + FD_ZERO(&fds); + FD_SET(0, &fds); + FD_SET(ssh_get_fd(session), &fds); + maxfd = ssh_get_fd(session) + 1; + + ssh_select(in_channels, out_channels, maxfd, &fds, &timeout); + + if (out_channels[0] != NULL) + { + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (nbytes < 0) return SSH_ERROR; + if (nbytes > 0) + { + nwritten = write(1, buffer, nbytes); + if (nwritten != nbytes) return SSH_ERROR; + } + } + + if (FD_ISSET(0, &fds)) + { + nbytes = read(0, buffer, sizeof(buffer)); + if (nbytes < 0) return SSH_ERROR; + if (nbytes > 0) + { + nwritten = ssh_channel_write(channel, buffer, nbytes); + if (nbytes != nwritten) return SSH_ERROR; + } + } + } + + return rc; +} +@endcode + + +@subsection x11 Using graphical applications on the remote side + +If your remote application is graphical, you can forward the X11 protocol to +your local computer. + +To do that, you first declare that you accept X11 connections with +ssh_channel_accept_x11(). Then you create the forwarding tunnel for +the X11 protocol with ssh_channel_request_x11(). + +The following code performs channel initialization and shell session +opening, and handles a parallel X11 connection: + +@code +int interactive_shell_session(ssh_channel channel) +{ + int rc; + ssh_channel x11channel; + + rc = ssh_channel_request_pty(channel); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_change_pty_size(channel, 80, 24); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_x11(channel, 0, NULL, NULL, 0); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_shell(channel); + if (rc != SSH_OK) return rc; + + /* Read the data sent by the remote computer here */ + ... +} +@endcode + +Don't forget to set the $DISPLAY environment variable on the remote +side, or the remote applications won't try using the X11 tunnel: + +@code +$ export DISPLAY=:0 +$ xclock & +@endcode + +*/ diff --git a/doc/tbd.dox b/doc/tbd.dox new file mode 100644 index 0000000..921337e --- /dev/null +++ b/doc/tbd.dox @@ -0,0 +1,14 @@ +/** +@page libssh_tutor_todo To be done + +*** To be written *** + +@section sshd Writing a libssh-based server + +*** To be written *** + +@section cpp The libssh C++ wrapper + +*** To be written *** + +*/ diff --git a/doc/that_style/LICENSE b/doc/that_style/LICENSE new file mode 100644 index 0000000..eac42e7 --- /dev/null +++ b/doc/that_style/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Jan-Lukas Wynen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/doc/that_style/README.md b/doc/that_style/README.md new file mode 100644 index 0000000..7572720 --- /dev/null +++ b/doc/that_style/README.md @@ -0,0 +1,22 @@ +# that style +A plain, more modern HTML style for Doxygen + +## Requirements +- Doxygen (tested with version 1.8.13) +- *optional*: a sass/scss compiler if you want to modify the style + +## Simple usage +Tell Doxygen about the files for that style as shown in [doxyfile.conf](doxyfile.conf). You might need to adjust the +paths depending on where you installed that style. +When you run Doxygen, all files are copied into to generated HTML folder. So you don't need to keep the originals around +unless you want to re-generate the documentation. + +## Advanced +that style uses a custom javascript to hack some nice stripes into some tables. It has to be loaded from HTML. Hence you need +to use the provided custom header. Since its default content may change when Doxygen is updated, there might be syntax error in +the generated HTML. If this is the case, you can remove the custom header (adjust your doxyfile.conf). This has no +disadvantages other than removing the stripes. + +[that_style.css](that_style.css) was generated from the scss files in the folder [sass](sass). If you want to change the style, +use those files in order to have better control. For instance, you can easily change most colors by modifying the variables +in the beginning of [that_style.scss](sass/that_style.scss). diff --git a/doc/that_style/header.html b/doc/that_style/header.html new file mode 100644 index 0000000..3da4639 --- /dev/null +++ b/doc/that_style/header.html @@ -0,0 +1,56 @@ + + + + + + + +$projectname: $title +$title + + + +$treeview +$search +$mathjax + + +$extrastylesheet + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
+
$projectname +  $projectnumber +
+
$projectbrief
+
+
$projectbrief
+
$searchbox
+
+ + diff --git a/doc/that_style/img/doc.svg b/doc/that_style/img/doc.svg new file mode 100644 index 0000000..68e1ba0 --- /dev/null +++ b/doc/that_style/img/doc.svg @@ -0,0 +1,97 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/doc/that_style/img/folderclosed.svg b/doc/that_style/img/folderclosed.svg new file mode 100644 index 0000000..e53ec90 --- /dev/null +++ b/doc/that_style/img/folderclosed.svg @@ -0,0 +1,77 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/doc/that_style/img/folderopen.svg b/doc/that_style/img/folderopen.svg new file mode 100644 index 0000000..1ab7b78 --- /dev/null +++ b/doc/that_style/img/folderopen.svg @@ -0,0 +1,83 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/doc/that_style/img/mag_glass.svg b/doc/that_style/img/mag_glass.svg new file mode 100644 index 0000000..e21a004 --- /dev/null +++ b/doc/that_style/img/mag_glass.svg @@ -0,0 +1,73 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/doc/that_style/img/nav_edge_inter.svg b/doc/that_style/img/nav_edge_inter.svg new file mode 100644 index 0000000..f04f10f --- /dev/null +++ b/doc/that_style/img/nav_edge_inter.svg @@ -0,0 +1,73 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/doc/that_style/img/nav_edge_left.svg b/doc/that_style/img/nav_edge_left.svg new file mode 100644 index 0000000..ca1adf4 --- /dev/null +++ b/doc/that_style/img/nav_edge_left.svg @@ -0,0 +1,73 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/doc/that_style/img/nav_edge_right.svg b/doc/that_style/img/nav_edge_right.svg new file mode 100644 index 0000000..bb33815 --- /dev/null +++ b/doc/that_style/img/nav_edge_right.svg @@ -0,0 +1,73 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/doc/that_style/img/splitbar_handle.svg b/doc/that_style/img/splitbar_handle.svg new file mode 100644 index 0000000..e0dc90d --- /dev/null +++ b/doc/that_style/img/splitbar_handle.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/doc/that_style/img/sync_off.png b/doc/that_style/img/sync_off.png new file mode 100644 index 0000000..9d286d9 Binary files /dev/null and b/doc/that_style/img/sync_off.png differ diff --git a/doc/that_style/img/sync_on.png b/doc/that_style/img/sync_on.png new file mode 100644 index 0000000..b8434e8 Binary files /dev/null and b/doc/that_style/img/sync_on.png differ diff --git a/doc/that_style/js/striped_bg.js b/doc/that_style/js/striped_bg.js new file mode 100644 index 0000000..97ae0a8 --- /dev/null +++ b/doc/that_style/js/striped_bg.js @@ -0,0 +1,32 @@ +// Adds extra CSS classes "even" and "odd" to .memberdecls to allow +// striped backgrounds. +function MemberDeclsStriper () { + var counter = 0; + + this.stripe = function() { + $(".memberdecls tbody").children().each(function(i) { + + // reset counter at every heading -> always start with even + if ($(this).is(".heading")) { + counter = 0; + } + + // add extra classes + if (counter % 2 == 1) { + $(this).addClass("odd"); + } + else { + $(this).addClass("even"); + } + + // advance counter at every separator + // this is the only way to reliably detect which table rows belong together + if ($(this).is('[class^="separator"]')) { + counter++; + } + }); + } +} + +// execute the function +$(document).ready(new MemberDeclsStriper().stripe); diff --git a/doc/that_style/that_style.css b/doc/that_style/that_style.css new file mode 100644 index 0000000..846353c --- /dev/null +++ b/doc/that_style/that_style.css @@ -0,0 +1,1431 @@ +@charset "UTF-8"; +/* + * My own little style + */ +body, table, div, p, dl { + font: 400 14px/22px Roboto,sans-serif; } + +h1.groupheader { + font-size: 150%; } + +.title { + font: 400 14px/28px Roboto,sans-serif; + font-size: 150%; + font-weight: bold; + margin: 10px 2px; } + +h2.groupheader { + border-bottom: 1px solid #555555; + color: black; + font-size: 200%; + font-weight: bold; + margin-top: 1.75em; + padding-top: 1em; + padding-bottom: 4px; + width: 100%; } + +tr.heading h2 { + border-bottom: 1px solid #a5a5a5; + font-size: 150%; + margin-top: 6px; + margin-bottom: 6px; + padding-top: 3px; + padding-bottom: 7px; } + +h2.groupheader a { + margin-left: 1%; } + +h3.groupheader { + font-size: 100%; } + +h1, h2, h3, h4, h5, h6 { + -webkit-transition: none; + -moz-transition: none; + -o-transition: none; + transition: none; + margin-right: 15px; } + h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow { + text-shadow: none; + color: #5f082b; } + +dt { + font-weight: bold; } + +div.multicol { + -moz-column-gap: 1em; + -webkit-column-gap: 1em; + -moz-column-count: 3; + -webkit-column-count: 3; } + +p.startli, p.startdd { + margin-top: 2px; } + +p.starttd { + margin-top: 0px; } + +p.endli { + margin-bottom: 0px; } + +p.enddd { + margin-bottom: 4px; } + +p.endtd { + margin-bottom: 2px; } + +#top { + border: none; + position: relative; + z-index: 100; + -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.4), 0 0 8px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.4), 0 0 8px rgba(0, 0, 0, 0.3); + -o-box-shadow: 0 0 4px rgba(0, 0, 0, 0.4), 0 0 8px rgba(0, 0, 0, 0.3); + box-shadow: 0 0 4px rgba(0, 0, 0, 0.4), 0 0 8px rgba(0, 0, 0, 0.3); } + +caption { + font-weight: bold; } + +span.legend { + font-size: 70%; + text-align: center; } + +h3.version { + font-size: 90%; + text-align: center; } + +div.qindex, div.navtab { + background-color: #ffffff; + border: none; + text-align: center; } + +div.qindex, div.navpath { + width: 100%; + line-height: 140%; } + +div.navtab { + margin-right: 15px; } + +a, a:visited { + color: #00549f; + font-weight: normal; + text-decoration: none; } + +.contents a:visited { + color: #4665A2; } + +a:hover { + text-decoration: underline; } + +a.qindex { + font-weight: bold; + text-transform: uppercase; } + +a.qindexHL { + font-weight: bold; + background-color: #9CAFD4; + color: #ffffff; + border: 1px double #869DCA; } + +.contents a.qindexHL:visited { + color: #ffffff; } + +a.el, a.el:visited { + font-weight: normal; + color: #00549f; } + +a.elRef, a.elRef:visited { + font-family: monospace; + color: #006bc8; } + +/* + * The main menu at the top + */ +#main-menu { + background-image: none; + background: #414141; + padding: 0; } + +.sm-dox > li:not(:last-child) > a { + background-image: none; + text-shadow: none; + color: white; + font-weight: normal; + letter-spacing: 1px; + font-size: 11pt; + text-transform: uppercase; } + +.sm-dox > li:not(:last-child) > a:hover, +.sm-dox > li:not(:last-child) > a.highlighted { + background-color: #5f082b; } + +.sm-dox a span.sub-arrow { + border-color: white transparent transparent; } + +.sm-dox ul { + border: none; + -moz-border-radius: 0 !important; + -webkit-border-radius: 0 !important; + border-radius: 0 !important; + padding: 0; + background: #414141; + -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + -o-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); } + .sm-dox ul a { + background: inherit; + color: white; + font-weight: normal; + letter-spacing: 1px; + font-size: 11pt; } + .sm-dox ul a:hover { + background: #5f082b; + color: white; + font-weight: normal; + letter-spacing: 1px; + font-size: 11pt; } + .sm-dox ul a.highlighted { + background: #5f082b; + color: white; + font-weight: normal; + letter-spacing: 1px; + font-size: 11pt; } + .sm-dox ul a span.sub-arrow { + /* this sets the color of the arrow */ + border-color: white transparent transparent; } + +dl.el { + margin-left: -1cm; } + +div.ah, span.ah { + background: none; + color: black; + margin-bottom: 3px; + margin-top: 3px; + padding: 0.2em; + border: none; + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; + font: 14pt monospace; + font-weight: bold; + text-transform: uppercase; } + +div.classindex ul { + list-style: none; + padding-left: 0; } + +div.classindex span.ai { + display: inline-block; } + +div.groupHeader { + margin-left: 16px; + margin-top: 12px; + font-weight: bold; } + +div.groupText { + margin-left: 16px; + font-style: italic; } + +body { + background-color: #ffffff; + color: black; + margin: 0; } + +#doc-content { + background-color: #111; + color: #ffffff; } + +div.contents { + color: black; + background-color: #ffffff; + padding: 0; + margin: 5pt; } + div.contents hr { + display: none; + visibility: hidden; } + +div.header + div.contents { + padding: 1ex; + margin: 0 5pt 5pt 5pt; } + +div.textblock { + padding: 1ex 1ex 0 1ex; } + +div.textblock + ul { + padding-bottom: 1%; } + +img.footer { + border: 0px; + vertical-align: middle; } + +/* + Basic styling for fragments shared by all themes. +*/ +div.fragment { + padding: 4px; + margin: 4px 8px 4px 2px; + color: #bebebe; + background-color: #323232; + border: 3px solid #e8e8e8; + border-radius: 2px; + overflow-y: hidden; + overflow-x: auto; + position: relative; } + +div.line { + font-family: monospace, fixed; + font-size: 13px; + min-height: 13px; + line-height: 1.0; + text-indent: -53px; + margin: 0px; + padding: 1px 0 1px 53px; + white-space: pre; + -webkit-transition: background-color; + -moz-transition: background-color; + -o-transition: background-color; + transition: background-color; + -webkit-duration: 0s; + -moz-duration: 0s; + -o-duration: 0s; + duration: 0s; } + div.line:hover { + background-color: #1a1a1a; } + div.line::after { + content: "\000A"; + white-space: pre; } + +span.lineno { + padding-right: 4px; + text-align: right; + color: black; + height: 100px; + white-space: pre; + border-right: 3px solid #1d7567; + background-color: #323232; } + +span.lineno a, span.lineno a:visited { + background-color: inherit; + color: #1e595a; } + +span.lineno a:hover { + background-color: #C8C8C8; + text-decoration: none; } + +.lineno { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + +div.fragment { + color: #bebebe; + background-color: #323232; } + +div.fragment::before { + background-color: #1a1a1a; + border-right: 1px solid #3e3e3e; } + +div.line:hover { + background-color: #1a1a1a; } + +span.lineno { + color: #969696; + background-color: #323232; + border-right: 1px solid #3e3e3e; } + +span.lineno a, span.lineno a:visited { + background-color: inherit; + color: #dcdcdc; } + +span.lineno a:hover { + background-color: #323232; } + +a.code, a.code:visited { + color: #6cc7eb; } + +a.codeRef, a.codeRef:visited { + color: #3d95e6; } + +span.keyword { + color: #98f77a; + font-weight: bold; } + +span.keywordtype { + color: #ffa0a0; } + +span.keywordflow { + color: #98f77a; + font-weight: bold; } + +span.comment { + color: #999; + font-style: oblique; } + +span.preprocessor { + color: #cd5c57; } + +span.stringliteral { + color: #64b041; } + +span.charliteral { + color: #64b041; } + +blockquote { + background-color: #F7F8FB; + border-left: 2px solid #9CAFD4; + margin: 0 24px 0 4px; + padding: 0 12px 0 16px; } + +/* + * The search box + */ +.sm-dox > li:last-child { + margin-right: 10pt; } + +#MSearchBox { + border: 2px inset black; + display: table; + width: 350px; + height: 26px; + background: white; + margin-top: 5px; } + #MSearchBox .left { + background-image: none; + display: table-cell; + width: 100%; + height: inherit; + left: 0; } + #MSearchBox .right { + background-image: none; + width: 0; + display: none; + visibility: hidden; } + +nav > #MSearchBox { + border: 2px solid #666666; + margin: 5px 10pt 0 0; + height: 22px; } + +#MSearchSelect, .left #MSearchSelect { + left: 0; + background-image: url("mag_glass.svg"); + width: 22px; + height: 22px; + padding: 22px 22px 0 0; + margin: 0 4px 0 4px; + box-sizing: border-box; } + +#MSearchField { + background-image: none; + display: table-cell; + margin: 0; + margin-left: 30px; + width: calc(100% - 34px); + height: 22px; + font: 11pt sans-serif; } + +#MSearchSelectWindow { + background-color: #414141; + padding: 0; + border: solid 1px black; + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; + -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + -o-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); } + +a.SelectItem { + color: white; + padding: 3px 4px; + font: 10pt sans-serif; + letter-spacing: 1px; } + a.SelectItem:hover { + background-color: #5f082b; + color: white; } + a.SelectItem:focus, a.SelectItem:active { + color: white; } + +#MSearchResultsWindow { + background-color: white; + -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + -o-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); } + +table.memberdecls { + width: 100%; + border-spacing: 0px; + padding: 0px; + margin-top: 7px; + background-color: #ffffff; + -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + -o-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); } + +.memberdecls tbody { + background-color: #ffffff; } + +.memberdecls .odd { + background: #f6f6f6; } + +/* all but last separator show a line */ +.memberdecls tr[class^="separator"]:not(:last-child) .memSeparator { + border-bottom: 1px solid #c5c5c5; + line-height: 1px; + margin: 0; + padding: 0; } + +.memberdecls tr[class^="separator"]:last-child .memSeparator { + border-bottom: none; + line-height: 0; + margin: 0; + padding: 0; } + +table.fieldtable { + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; } + +.memberdecls td, .fieldtable tr { + background-color: inherit; } + +.fieldtable th { + display: none; + height: 0; + visibility: hidden; } + +td.fieldname { + color: #820a32; + font-family: monospace; + font-weight: bold; } + +th.markdownTableHeadLeft, th.markdownTableHeadRight, +th.markdownTableHeadCenter, th.markdownTableHeadNone { + background-color: #414141; + color: white; } + +.memItemLeft, .memItemRight, +.memTemplItemLeft, .memTemplItemRight, .memTemplParams { + font-family: monospace; + background-color: #ffffff; + border: none; + margin: 4px; + padding: 1px 0 0 8px; } + +.mdescLeft, .mdescRight { + background-color: #ffffff; + border: none; + margin: 4px; + padding: 1px 0 0 8px; } + +.mdescLeft, .mdescRight { + padding: 0px 8px 4px 8px; + color: #555; } + +.memItemLeft, .memTemplItemLeft { + white-space: nowrap; } + +.memItemRight { + width: 100%; } + +.memTemplParams { + color: black; + white-space: nowrap; + font-size: 100%; } + +/* Styles for detailed member documentation */ +.memtitle { + padding: 8px; + border: none; + margin-bottom: -1px; + background-image: none; + background-color: #f6f6f6; + line-height: 1.25; + font-weight: bold; + color: black; + float: left; + z-index: 0; + position: relative; + -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + -o-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); } + +.permalink { + font-size: 100%; + display: inline-block; + vertical-align: middle; } + +/* replace content of permalinks */ +.permalink a { + visibility: hidden; } + +.permalink a:after { + content: "§"; + visibility: visible; + display: block; + position: absolute; + color: black; + top: 20%; } + +.permalink a:visited { + color: black; } + +.memtemplate { + font-size: 100%; + color: black; + font-family: monospace; + font-weight: normal; + margin-left: 9px; } + +.memnav { + background-color: #EBEFF6; + border: 1px solid #A3B4D7; + text-align: center; + margin: 2px; + margin-right: 15px; + padding: 2px; } + +.mempage { + width: 100%; } + +.memitem { + font-family: monospace; + padding: 0; + margin-bottom: 10px; + margin-right: 5px; + -webkit-transition: none; + -moz-transition: none; + -ms-transition: none; + -o-transition: none; + transition: none; + display: table !important; + width: 100%; + background-color: #f6f6f6; + -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + -o-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); } + +.memitem.glow { + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; } + +.memname { + font-family: monospace; + font-weight: 400; + margin-left: 6px; } + +.memname td { + vertical-align: bottom; } + +.memproto, dl.reflist dt { + border: none; + padding: 6px 0px 6px 0px; + color: black; + font-weight: bold; + text-shadow: none; + background-color: #f6f6f6; + position: relative; + z-index: 1; + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; } + +.overload { + font-family: "courier new",courier,monospace; + font-size: 65%; } + +.memdoc, dl.reflist dd { + border: none; + border-left: 4px solid #5f082b; + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; + -webkit-border-bottom-left-radius: 0px; + -webkit-border-bottom-right-radius: 0px; + -moz-border-bottom-left-radius: 0px; + -moz-border-bottom-right-radius: 0px; + padding: 2px 1% 2px 1%; + margin: 1%; + background-color: #ffffff; + background-image: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; + /* allow movement of elements inside */ + display: flex; + flex-direction: column; } + +/* overrides for docs on individual pages */ +.memtitle:nth-child(2) { + width: 0; + height: 0; + display: none; + visibility: hidden; } + +.memitem:nth-child(3) { + margin: 0; + margin-top: 0.5%; + background-color: #ffffff; + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; } + +.memitem:nth-child(3) .memproto { + padding: 10px; + background-color: #ffffff; + margin-bottom: 10px; } + +.memitem:nth-child(3) .memproto::after { + content: ""; + width: 99%; + height: 1px; + position: absolute; + bottom: -10px; + left: 0.5%; + background: #666; } + +.memitem:nth-child(3) .memdoc { + border: none; + padding: 0; } + +.memitem:nth-child(3) table.memname { + background-color: #f6f6f6; + border-collapse: collapse; + border-spacing: initial; + border: 1px solid #aaa; } + +.memitem:nth-child(3) table.memname tr:not(:last-child) { + border-bottom: 1px dashed #aaa; } + +dl.reflist dt { + padding: 5px; + z-index: 0; + /* cover the top shadow of dd */ + position: relative; + -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + -o-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); + box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); } + +/* cover up the shadow at the bottom */ +dl.reflist dt::after { + content: " "; + width: 100%; + display: block; + height: 8px; + position: absolute; + background-color: #f6f6f6; + left: 0; + bottom: -8px; } + +dl.reflist dd { + border-left: 4px solid #5f082b; + padding: 2px 1% 2px 1%; + margin: 8px 8px 24px 8px; + outline: 8px solid #f6f6f6; + -moz-box-shadow: 0 0 4px 8px rgba(0, 0, 0, 0.35), 0 0 8px 8px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 0 4px 8px rgba(0, 0, 0, 0.35), 0 0 8px 8px rgba(0, 0, 0, 0.2); + -o-box-shadow: 0 0 4px 8px rgba(0, 0, 0, 0.35), 0 0 8px 8px rgba(0, 0, 0, 0.2); + box-shadow: 0 0 4px 8px rgba(0, 0, 0, 0.35), 0 0 8px 8px rgba(0, 0, 0, 0.2); } + +dl.reflist dd p::before { + font-size: 85%; + content: "\25B6\00A0\00A0"; + display: inline-block; + width: 12pt; } + +dl.reflist dd p { + margin-top: 4px; + margin-bottom: 4px; } + +.paramkey { + text-align: right; } + +.paramtype { + font-family: monospace; + white-space: nowrap; + color: #002546; } + +.paramname { + color: black; + font-family: monospace; + white-space: nowrap; } + +.paramname em { + color: #820a32; + font-style: normal; } + +.paramname code { + color: #404040; + line-height: 14px; } + +.params, .retval, .exception, .tparams { + margin-left: 0; + padding-left: 0; + margin-bottom: -0.25em; } + +.params dt, .tparams dt { + margin-bottom: 0.5em; } + +.params .paramname, .tparams .paramname, .retval .paramname, .exception .paramname { + color: #820a32; + font-family: monospace; + font-weight: bold; + vertical-align: top; } + +.params .paramtype, .tparams .paramtype { + font-family: monospace; + font-style: italic; + vertical-align: top; } + +.params .paramdir, .tparams .paramdir { + font-family: "courier new",courier,monospace; + vertical-align: top; } + +/* line over parameters docs */ +.params, .tparams { + border-collapse: collapse; } + +.params tr, .tparams tr { + -moz-box-shadow: 0 -2px 0 -1px #606060; + -webkit-box-shadow: 0 -2px 0 -1px #606060; + -o-box-shadow: 0 -2px 0 -1px #606060; + box-shadow: 0 -2px 0 -1px #606060; } + +.params .paramname, .tparams .paramname { + border-top: 2px solid #5f082b; + padding-right: 5pt; } + +.params td, .tparams td { + padding-bottom: 1em; } + +table.mlabels { + border-spacing: 0px; } + +td.mlabels-left { + width: 100%; + padding: 0px; } + +td.mlabels-right { + vertical-align: bottom; + padding: 0px; + white-space: nowrap; } + +span.mlabels { + margin-left: 8px; } + +span.mlabel { + background-color: #444444; + border: none; + border-radius: 3px; + text-shadow: none; + color: white; + margin-right: 4px; + padding: 3px 5px; + font-size: 8pt; + white-space: nowrap; + vertical-align: middle; } + +.memdoc .definition { + position: relative; + padding-top: 0.5em; + /* move definition line to bottom of memdoc */ + order: 3; } + .memdoc .definition::before { + content: ""; + width: 33%; + height: 1px; + border-top: 1px solid black; + position: absolute; + top: 0; } + +table.directory { + border-top: 1px solid #c5c5c5; + border-bottom: 1px solid #c5c5c5; + border-collapse: collapse; + width: 100%; + font: 400 14px Roboto,sans-serif; } + table.directory tr { + background-color: white !important; } + table.directory tr.even { + background-color: #f6f6f6 !important; } + table.directory tr:hover { + background-color: #e6e6e6 !important; } + table.directory td.entry { + padding: 1.5pt 3pt 1.5pt 3pt; + white-space: normal; } + +div.directory { + border: none; } + div.directory table.directory tr { + line-height: 17pt; } + div.directory table.directory td { + margin: 0; + padding: 0.5pt 6pt 0.5pt 0; + vertical-align: middle; } + div.directory table.directory td.entry { + white-space: nowrap; } + div.directory table.directory td.desc { + width: 100%; + padding-left: 6pt; + border-left: 1px solid rgba(0, 0, 0, 0.05); } + +.directory .levels { + white-space: nowrap; + width: 100%; + text-align: right; + font-size: 9pt; } + .directory .levels span { + cursor: pointer; + padding-left: 2px; + padding-right: 2px; + color: #00549f; } + .directory .levels span:hover { + text-decoration: underline; } + +/* + * The tree view on the left + */ +.arrow { + color: black; + cursor: pointer; + font-size: 80%; + display: inline-block; + width: 16px; + height: 22px; + margin-left: 4px; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + .arrow:hover { + color: black; } + +#selected .arrow { + color: white; } + #selected .arrow:hover { + color: #d2d2d2; } + +#nav-tree { + background-image: none; + background-color: white; } + #nav-tree .item { + margin: 0; } + #nav-tree .item:hover { + background-color: #d2d2d2; } + #nav-tree .selected { + background-image: none; + background-color: #5f082b; + color: white; + text-shadow: none; } + #nav-tree .selected:hover { + background-image: none; + background-color: #5f082b; + color: white; + text-shadow: none; } + #nav-tree a { + color: black; } + +.ui-resizable-e { + background: #808080 url("splitbar_handle.svg") no-repeat center; + border-right: solid 1px #c0c0c0; + border-left: solid 1px black; } + .ui-resizable-e:hover { + background-color: #606060; } + +.icon { + font-family: monospace; + font-weight: bold; + font-size: 12px; + height: 15px; + width: 15px; + display: inline-block; + background-color: #444444; + color: white; + text-align: center; + border-radius: 3px; + margin: 0; + padding-top: 1px; + text-indent: -1px; } + +.icona { + width: 0; + height: 0; + display: none; + visibility: hidden; } + +.iconfopen { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image: url("folderopen.svg"); + background-position: 0; + background-repeat: no-repeat; + vertical-align: top; + display: inline-block; } + +.iconfclosed { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image: url("folderclosed.svg"); + background-position: 0; + background-repeat: no-repeat; + vertical-align: top; + display: inline-block; } + +.icondoc { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image: url("doc.svg"); + background-position: 0; + background-repeat: no-repeat; + vertical-align: top; + display: inline-block; } + +div.dynheader { + margin-top: 8px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + +address { + font-style: normal; + color: #2A3D61; } + +table.doxtable caption { + caption-side: top; } + +table.doxtable { + border-collapse: collapse; + margin-top: 4px; + margin-bottom: 4px; } + +table.doxtable td, table.doxtable th { + border: 1px solid #444444; + padding: 3px 7px 2px; } + +table.doxtable th { + background-color: #444444; + color: #FFFFFF; + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; } + +/* + * The line at the bottom + */ +.navpath { + /* intermediate navelems */ + /* first navelem */ + /* last navelem */ } + .navpath ul { + font-size: 11px; + background-image: none; + height: 30px; + line-height: 30px; + color: black; + border: none; + border-top: 1px solid #808080; + overflow: hidden; + margin: 0px; + padding: 0px; } + .navpath li:not(:first-child) { + list-style-type: none; + float: left; + padding-left: 18px; + padding-right: 10px; + color: black; + background-color: white; + background-image: url("nav_edge_inter.svg"); + background-repeat: no-repeat; + background-position: left -1px; + background-size: auto 100%; } + .navpath li:first-child { + list-style-type: none; + float: left; + padding-left: 15px; + padding-right: 10px; + color: black; + background-color: white; + background-image: none; } + .navpath li:nth-last-child(2) { + list-style-type: none; + float: left; + padding-left: 10px; + padding-right: 15px; + color: white; + background-color: #5f082b; + background-image: url("nav_edge_right.svg"); + background-repeat: no-repeat; + background-position: right -1px; + background-size: auto 100%; } + .navpath li:nth-last-child(2):not(:first-child) { + list-style-type: none; + float: left; + padding-left: 15px; + padding-right: 15px; + color: white; + background-color: #5f082b; + background-image: url("nav_edge_left.svg"), url("nav_edge_right.svg"); + background-repeat: no-repeat; + background-position: -1px -1px, right -1px; + background-size: auto 100%; } + .navpath li.navelem a, .navpath .navpath li.navelem b { + height: 32px; + display: block; + text-decoration: none; + outline: none; + color: inherit; + font-family: Roboto,sans-serif; + text-shadow: none; + text-decoration: none; + font-weight: normal; } + .navpath li.navelem a:hover { + color: inherit; + text-decoration: underline; } + .navpath li.footer { + list-style-type: none; + float: right; + padding-left: 0; + padding-right: 10px; + background-color: #d5d5d5; + background-image: none; + color: black; + font-size: 8pt; } + .navpath li.footer:before { + content: ""; + width: 13px; + height: 30px; + display: inline-block; + float: left; + background-image: url("nav_edge_right.svg"); + background-repeat: no-repeat; + background-position: right 0; + background-size: auto 100%; + /* flip the element horizontally */ + -moz-transform: scaleX(-1); + -o-transform: scaleX(-1); + -webkit-transform: scaleX(-1); + transform: scaleX(-1); + filter: FlipH; + -ms-filter: "FlipH"; } + +div.summary { + -webkit-order: 2; + order: 2; + float: right; + font-size: 8pt; + padding-right: 5px; + width: 50%; + text-align: right; + margin-right: 0.5%; } + +div.summary a { + white-space: nowrap; } + +table.classindex { + margin: 10px; + white-space: nowrap; + margin-left: 1%; + margin-right: 1%; + width: 98%; + border: none; + border-top: 1px solid black; + border-bottom: 1px solid black; + border-spacing: 0.5em; + padding: 0; } + +div.ingroups { + font-size: 8pt; + width: 50%; + text-align: left; } + +div.ingroups a { + white-space: nowrap; } + +div.header { + display: -webkit-flex; + display: flex; + justify-content: space-between; + background-image: none; + background-color: #ffffff; + color: black; + margin: 5pt 5pt 0 5pt; + padding: 0 1ex 0 1ex; + align-items: center; + justify-content: center; + border-bottom: none; + position: relative; } + +div.header::after { + content: ""; + height: 2px; + width: 99%; + position: absolute; + bottom: -5px; + left: 0.5%; + background: #666; } + +div.headertitle { + -webkit-order: 1; + order: 1; + margin-right: auto; + text-align: center; } + +dl { + padding: 0; } + +dl.section { + margin-left: 0px; + padding-left: 0px; } + +dl.section > dt { + font-weight: bold; + font-family: sans-serif; } + +dl.note, dl.warning, dl.attention, dl.pre, dl.post, dl.invariant, +dl.deprecated, dl.todo, dl.test, dl.bug { + margin-left: -7px; + padding-left: 3px; } + +dl.note { + padding-left: 7px; + border: none; } + +dl.warning { + background-color: #ffe6ea; + border: 1px solid #ff0728; + border-left: 4px solid #ff0728; + padding-top: 4px; + padding-bottom: 3px; } + +dl.attention { + border-left: 4px solid #ff0728; } + +dl.pre, dl.post, dl.invariant { + background-color: #f0ffe6; + border: 1px solid #5eb82a; + border-left: 4px solid #5eb82a; } + +dl.deprecated { + background-color: #f6f6f6; + border: 1px solid black; } + +dl.todo { + border-left: 4px solid #e8d500; } + +dl.test { + border-left: 4px solid #00549f; } + +dl.bug { + background-color: #f6f6f6; + border: 1px solid #cc071e; + border-left: 4px solid #cc071e; } + +dl.section dd { + margin-bottom: 6px; } + +.memdoc dl dt a.el { + font-weight: bold; + color: black; } + +#projectlogo { + text-align: center; + vertical-align: bottom; + border-collapse: separate; } + +#projectlogo img { + border: 0px none; } + +#projectalign { + vertical-align: middle; } + +#projectname { + font: 300% Tahoma, Arial,sans-serif; + margin: 0px; + padding: 2px 0px; } + +#projectbrief { + font: 120% Tahoma, Arial,sans-serif; + margin: 0px; + padding: 0px; } + +#projectnumber { + font: 50% Roboto,sans-serif; + margin: 0px; + padding: 0px; } + +#titlearea { + padding: 0px; + margin: 0px; + width: 100%; + border-bottom: none; } + #titlearea:nth-last-child(2) { + border-bottom: 2px solid #444444; } + +.image { + text-align: center; } + +.dotgraph, .mscgraph, .diagraph { + text-align: center; } + +.caption { + font-weight: bold; } + +div.zoom { + border: 1px solid #90A5CE; } + +dl.citelist { + margin-bottom: 5ex; } + dl.citelist dt { + color: black; + float: left; + font-weight: bold; + padding: 5px 0; + margin: 2px 10pt 2px 0; } + dl.citelist dd { + margin: 2px 0; + padding: 5px 0; } + dl.citelist .startdd { + margin-top: 0; } + +div.toc { + background-color: transparent; + border: 1px solid #414141; + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; + float: right; + height: auto; + margin: 0 8px 10px 10px; + padding: 10px 15px 5px 25px; + width: auto; } + div.toc li { + background: transparent; + font: 10pt Roboto,DejaVu Sans,sans-serif; + padding-left: 0; + padding-top: 0.5ex; } + div.toc li .level1 { + margin-left: 10pt; } + div.toc li .level2 { + margin-left: 10pt; } + div.toc li .level3 { + margin-left: 10pt; } + div.toc li .level4 { + margin-left: 10pt; } + div.toc h3 { + font: bold 12px/1.2 Roboto,DejaVu Sans,sans-serif; + color: black; + border-bottom: none; + margin: 0; + letter-spacing: 1px; } + div.toc ul { + list-style: disc; + border: none; + padding: 0; } + +.inherit_header { + font-weight: bold; + color: gray; + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + +.inherit_header td { + padding: 6px 0px 2px 5px; } + +.inherit { + display: none; } + +/* tooltip related style info */ +.ttc { + position: absolute; + display: none; } + +#powerTip { + cursor: default; + white-space: nowrap; + background-color: #ffffff; + border: 1px solid #323232; + border-radius: 0; + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; + display: none; + font-size: smaller; + max-width: 80%; + opacity: 0.9; + padding: 1ex 1em 1em 1em; + position: absolute; + z-index: 2147483647; } + #powerTip div.ttdoc { + color: grey; + font-style: italic; } + #powerTip div.ttname a { + font-weight: bold; } + #powerTip div.ttname { + font-weight: bold; } + #powerTip div.ttdeci { + color: #006318; } + #powerTip div { + margin: 0px; + padding: 0px; + font: 12px/16px Roboto,sans-serif; } + #powerTip:before, #powerTip:after { + content: ""; + position: absolute; + margin: 0px; } + +@media print { + #top { + display: none; } + #side-nav { + display: none; } + #nav-path { + display: none; } + body { + overflow: visible; } + h1, h2, h3, h4, h5, h6 { + page-break-after: avoid; } + .summary { + display: none; } + .memitem { + page-break-inside: avoid; } + #doc-content { + margin-left: 0 !important; + height: auto !important; + width: auto !important; + overflow: inherit; + display: inline; + background-color: white; } } diff --git a/doc/threading.dox b/doc/threading.dox new file mode 100644 index 0000000..87d096b --- /dev/null +++ b/doc/threading.dox @@ -0,0 +1,52 @@ +/** +@page libssh_tutor_threads Chapter 8: Threads with libssh +@section threads_with_libssh How to use libssh with threads + +libssh may be used in multithreaded applications, but under several conditions : + - Your system must support libpthread or, in Windows environment, + CriticalSection based mutex control. + - Since version 0.8.0, threads initialization is called automatically in the + library constructor if libssh is dynamically linked. This means it is no + longer necessary to call ssh_init()/ssh_finalize(). + - If libssh is statically linked, threading must be initialized by calling + ssh_init() before using any of libssh provided functions. This initialization + must be done outside of any threading context. Don't forget to call + ssh_finalize() to avoid memory leak + - At all times, you may use different sessions inside threads, make parallel + connections, read/write on different sessions and so on. You *cannot* use a + single session (or channels for a single session) in several threads at the same + time. This will most likely lead to internal state corruption. This limitation is + being worked out and will maybe disappear later. + +@subsection threads_init Initialization of threads + +Since version 0.8.0, it is no longer necessary to call ssh_init()/ssh_finalize() +if libssh is dynamically linked. + +If libssh is statically linked, call ssh_init() before using any of libssh +provided functions. + +@subsection threads_pthread Using libpthread with libssh + +Since version 0.8.0, libpthread is the default threads library used by libssh. + +To use libpthread, simply link it to you application. + +If you are using libssh statically linked, don't forget to call ssh_init() +before using any of libssh provided functions (and ssh_finalize() in the end). + +@subsection threads_other Using another threading library + +Since version 0.8.0, libssh does not support custom threading libraries. +The change makes sense since the newer versions for libcrypto (OpenSSL) and +libgcrypt don't support custom threading libraries. + +The default used threading library is libpthread. +Alternatively, in Windows environment, CriticalSection based mutex control can +be used. + +If your system does not support libpthread nor CriticalSection based mutex +control, unfortunately, you cannot use libssh in multithreaded scenarios. + +Good luck ! +*/ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..70a296c --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,82 @@ +project(libssh-examples C CXX) + +set(examples_SRCS + authentication.c + knownhosts.c + connect_ssh.c +) + +include_directories(${libssh_BINARY_DIR}) + +if (ARGP_INCLUDE_DIR) + include_directories(${ARGP_INCLUDE_DIR}) +endif() + +if (UNIX AND NOT WIN32) + add_executable(libssh_scp libssh_scp.c ${examples_SRCS}) + target_compile_options(libssh_scp PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(libssh_scp ssh::ssh) + + add_executable(scp_download scp_download.c ${examples_SRCS}) + target_compile_options(scp_download PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(scp_download ssh::ssh) + + add_executable(sshnetcat sshnetcat.c ${examples_SRCS}) + target_compile_options(sshnetcat PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(sshnetcat ssh::ssh) + + if (WITH_SFTP) + add_executable(samplesftp samplesftp.c ${examples_SRCS}) + target_compile_options(samplesftp PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(samplesftp ssh::ssh) + endif (WITH_SFTP) + + add_executable(ssh-client ssh_client.c ${examples_SRCS}) + target_compile_options(ssh-client PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(ssh-client ssh::ssh) + + if (WITH_SERVER AND (ARGP_LIBRARY OR HAVE_ARGP_H)) + if (HAVE_LIBUTIL) + add_executable(ssh_server_fork ssh_server_fork.c) + target_compile_options(ssh_server_fork PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(ssh_server_fork ssh::ssh ${ARGP_LIBRARY} util) + endif (HAVE_LIBUTIL) + + if (WITH_GSSAPI AND GSSAPI_FOUND) + add_executable(samplesshd-cb samplesshd-cb.c) + target_compile_options(samplesshd-cb PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(samplesshd-cb ssh::ssh ${ARGP_LIBRARY}) + + add_executable(proxy proxy.c) + target_compile_options(proxy PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(proxy ssh::ssh ${ARGP_LIBRARY}) + + add_executable(sshd_direct-tcpip sshd_direct-tcpip.c) + target_compile_options(sshd_direct-tcpip PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(sshd_direct-tcpip ssh::ssh ${ARGP_LIBRARY}) + endif (WITH_GSSAPI AND GSSAPI_FOUND) + + add_executable(samplesshd-kbdint samplesshd-kbdint.c) + target_compile_options(samplesshd-kbdint PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(samplesshd-kbdint ssh::ssh ${ARGP_LIBRARY}) + + endif() +endif (UNIX AND NOT WIN32) + +add_executable(exec exec.c ${examples_SRCS}) +target_compile_options(exec PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) +target_link_libraries(exec ssh::ssh) + +add_executable(senddata senddata.c ${examples_SRCS}) +target_compile_options(senddata PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) +target_link_libraries(senddata ssh::ssh) + +add_executable(keygen keygen.c) +target_compile_options(keygen PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) +target_link_libraries(keygen ssh::ssh) + +add_executable(libsshpp libsshpp.cpp) +target_link_libraries(libsshpp ssh::ssh) + +add_executable(libsshpp_noexcept libsshpp_noexcept.cpp) +target_link_libraries(libsshpp_noexcept ssh::ssh) diff --git a/examples/authentication.c b/examples/authentication.c new file mode 100644 index 0000000..7c47c8b --- /dev/null +++ b/examples/authentication.c @@ -0,0 +1,241 @@ +/* + * authentication.c + * This file contains an example of how to do an authentication to a + * SSH server using libssh + */ + +/* +Copyright 2003-2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. + */ + +#include +#include +#include + +#include +#include "examples_common.h" + +int authenticate_kbdint(ssh_session session, const char *password) +{ + int err; + + err = ssh_userauth_kbdint(session, NULL, NULL); + while (err == SSH_AUTH_INFO) { + const char *instruction; + const char *name; + char buffer[128]; + int i, n; + + name = ssh_userauth_kbdint_getname(session); + instruction = ssh_userauth_kbdint_getinstruction(session); + n = ssh_userauth_kbdint_getnprompts(session); + + if (name && strlen(name) > 0) { + printf("%s\n", name); + } + + if (instruction && strlen(instruction) > 0) { + printf("%s\n", instruction); + } + + for (i = 0; i < n; i++) { + const char *answer; + const char *prompt; + char echo; + + prompt = ssh_userauth_kbdint_getprompt(session, i, &echo); + if (prompt == NULL) { + break; + } + + if (echo) { + char *p; + + printf("%s", prompt); + + if (fgets(buffer, sizeof(buffer), stdin) == NULL) { + return SSH_AUTH_ERROR; + } + + buffer[sizeof(buffer) - 1] = '\0'; + if ((p = strchr(buffer, '\n'))) { + *p = '\0'; + } + + if (ssh_userauth_kbdint_setanswer(session, i, buffer) < 0) { + return SSH_AUTH_ERROR; + } + + memset(buffer, 0, strlen(buffer)); + } else { + if (password && strstr(prompt, "Password:")) { + answer = password; + } else { + buffer[0] = '\0'; + + if (ssh_getpass(prompt, buffer, sizeof(buffer), 0, 0) < 0) { + return SSH_AUTH_ERROR; + } + answer = buffer; + } + err = ssh_userauth_kbdint_setanswer(session, i, answer); + memset(buffer, 0, sizeof(buffer)); + if (err < 0) { + return SSH_AUTH_ERROR; + } + } + } + err=ssh_userauth_kbdint(session,NULL,NULL); + } + + return err; +} + +static int auth_keyfile(ssh_session session, char* keyfile) +{ + ssh_key key = NULL; + char pubkey[132] = {0}; // +".pub" + int rc; + + snprintf(pubkey, sizeof(pubkey), "%s.pub", keyfile); + + rc = ssh_pki_import_pubkey_file( pubkey, &key); + + if (rc != SSH_OK) + return SSH_AUTH_DENIED; + + rc = ssh_userauth_try_publickey(session, NULL, key); + + ssh_key_free(key); + + if (rc!=SSH_AUTH_SUCCESS) + return SSH_AUTH_DENIED; + + rc = ssh_pki_import_privkey_file(keyfile, NULL, NULL, NULL, &key); + + if (rc != SSH_OK) + return SSH_AUTH_DENIED; + + rc = ssh_userauth_publickey(session, NULL, key); + + ssh_key_free(key); + + return rc; +} + + +static void error(ssh_session session) +{ + fprintf(stderr,"Authentication failed: %s\n",ssh_get_error(session)); +} + +int authenticate_console(ssh_session session) +{ + int rc; + int method; + char password[128] = {0}; + char *banner; + + // Try to authenticate + rc = ssh_userauth_none(session, NULL); + if (rc == SSH_AUTH_ERROR) { + error(session); + return rc; + } + + method = ssh_userauth_list(session, NULL); + while (rc != SSH_AUTH_SUCCESS) { + if (method & SSH_AUTH_METHOD_GSSAPI_MIC){ + rc = ssh_userauth_gssapi(session); + if(rc == SSH_AUTH_ERROR) { + error(session); + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + // Try to authenticate with public key first + if (method & SSH_AUTH_METHOD_PUBLICKEY) { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + if (rc == SSH_AUTH_ERROR) { + error(session); + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + { + char buffer[128] = {0}; + char *p = NULL; + + printf("Automatic pubkey failed. " + "Do you want to try a specific key? (y/n)\n"); + if (fgets(buffer, sizeof(buffer), stdin) == NULL) { + break; + } + if ((buffer[0]=='Y') || (buffer[0]=='y')) { + printf("private key filename: "); + + if (fgets(buffer, sizeof(buffer), stdin) == NULL) { + return SSH_AUTH_ERROR; + } + + buffer[sizeof(buffer) - 1] = '\0'; + if ((p = strchr(buffer, '\n'))) { + *p = '\0'; + } + + rc = auth_keyfile(session, buffer); + + if(rc == SSH_AUTH_SUCCESS) { + break; + } + fprintf(stderr, "failed with key\n"); + } + } + + // Try to authenticate with keyboard interactive"; + if (method & SSH_AUTH_METHOD_INTERACTIVE) { + rc = authenticate_kbdint(session, NULL); + if (rc == SSH_AUTH_ERROR) { + error(session); + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + + if (ssh_getpass("Password: ", password, sizeof(password), 0, 0) < 0) { + return SSH_AUTH_ERROR; + } + + // Try to authenticate with password + if (method & SSH_AUTH_METHOD_PASSWORD) { + rc = ssh_userauth_password(session, NULL, password); + if (rc == SSH_AUTH_ERROR) { + error(session); + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + memset(password, 0, sizeof(password)); + } + + banner = ssh_get_issue_banner(session); + if (banner) { + printf("%s\n",banner); + SSH_STRING_FREE_CHAR(banner); + } + + return rc; +} diff --git a/examples/connect_ssh.c b/examples/connect_ssh.c new file mode 100644 index 0000000..c9e4ef6 --- /dev/null +++ b/examples/connect_ssh.c @@ -0,0 +1,67 @@ +/* + * connect_ssh.c + * This file contains an example of how to connect to a + * SSH server using libssh + */ + +/* +Copyright 2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. + */ + +#include +#include "examples_common.h" +#include + +ssh_session connect_ssh(const char *host, const char *user,int verbosity){ + ssh_session session; + int auth=0; + + session=ssh_new(); + if (session == NULL) { + return NULL; + } + + if(user != NULL){ + if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) { + ssh_free(session); + return NULL; + } + } + + if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) { + ssh_free(session); + return NULL; + } + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + if(ssh_connect(session)){ + fprintf(stderr,"Connection failed : %s\n",ssh_get_error(session)); + ssh_disconnect(session); + ssh_free(session); + return NULL; + } + if(verify_knownhost(session)<0){ + ssh_disconnect(session); + ssh_free(session); + return NULL; + } + auth=authenticate_console(session); + if(auth==SSH_AUTH_SUCCESS){ + return session; + } else if(auth==SSH_AUTH_DENIED){ + fprintf(stderr,"Authentication failed\n"); + } else { + fprintf(stderr,"Error while authenticating : %s\n",ssh_get_error(session)); + } + ssh_disconnect(session); + ssh_free(session); + return NULL; +} diff --git a/examples/examples_common.h b/examples/examples_common.h new file mode 100644 index 0000000..d3f4e1a --- /dev/null +++ b/examples/examples_common.h @@ -0,0 +1,26 @@ +/* +Copyright 2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ +#ifndef EXAMPLES_COMMON_H_ +#define EXAMPLES_COMMON_H_ + +#include + +/** Zero a structure */ +#define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x)) + +int authenticate_console(ssh_session session); +int authenticate_kbdint(ssh_session session, const char *password); +int verify_knownhost(ssh_session session); +ssh_session connect_ssh(const char *hostname, const char *user, int verbosity); + +#endif /* EXAMPLES_COMMON_H_ */ diff --git a/examples/exec.c b/examples/exec.c new file mode 100644 index 0000000..4d5e0c1 --- /dev/null +++ b/examples/exec.c @@ -0,0 +1,66 @@ +/* simple exec example */ +#include + +#include +#include "examples_common.h" + +int main(void) { + ssh_session session; + ssh_channel channel; + char buffer[256]; + int nbytes; + int rc; + + session = connect_ssh("localhost", NULL, 0); + if (session == NULL) { + ssh_finalize(); + return 1; + } + + channel = ssh_channel_new(session);; + if (channel == NULL) { + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); + return 1; + } + + rc = ssh_channel_open_session(channel); + if (rc < 0) { + goto failed; + } + + rc = ssh_channel_request_exec(channel, "lsof"); + if (rc < 0) { + goto failed; + } + + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + while (nbytes > 0) { + if (fwrite(buffer, 1, nbytes, stdout) != (unsigned int) nbytes) { + goto failed; + } + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + } + + if (nbytes < 0) { + goto failed; + } + + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); + + return 0; +failed: + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); + + return 1; +} diff --git a/examples/keygen.c b/examples/keygen.c new file mode 100644 index 0000000..2ab0011 --- /dev/null +++ b/examples/keygen.c @@ -0,0 +1,41 @@ +/* keygen.c + * Sample implementation of ssh-keygen using libssh + */ + +/* +Copyright 2019 Red Hat, Inc. + +Author: Jakub Jelen + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. + */ + +#include +#include + +int main(void) +{ + ssh_key key = NULL; + int rv; + + /* Generate a new ED25519 private key file */ + rv = ssh_pki_generate(SSH_KEYTYPE_ED25519, 0, &key); + if (rv != SSH_OK) { + fprintf(stderr, "Failed to generate private key"); + return -1; + } + + /* Write it to a file testkey in the current dirrectory */ + rv = ssh_pki_export_privkey_file(key, NULL, NULL, NULL, "testkey"); + if (rv != SSH_OK) { + fprintf(stderr, "Failed to write private key file"); + return -1; + } + + return 0; +} diff --git a/examples/knownhosts.c b/examples/knownhosts.c new file mode 100644 index 0000000..0726bfa --- /dev/null +++ b/examples/knownhosts.c @@ -0,0 +1,117 @@ +/* + * knownhosts.c + * This file contains an example of how verify the identity of a + * SSH server using libssh + */ + +/* +Copyright 2003-2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "libssh/priv.h" +#include +#include "examples_common.h" + +#ifdef _WIN32 +#define strncasecmp _strnicmp +#endif + +int verify_knownhost(ssh_session session) +{ + enum ssh_known_hosts_e state; + char buf[10]; + unsigned char *hash = NULL; + size_t hlen; + ssh_key srv_pubkey; + int rc; + + rc = ssh_get_server_publickey(session, &srv_pubkey); + if (rc < 0) { + return -1; + } + + rc = ssh_get_publickey_hash(srv_pubkey, + SSH_PUBLICKEY_HASH_SHA256, + &hash, + &hlen); + ssh_key_free(srv_pubkey); + if (rc < 0) { + return -1; + } + + state = ssh_session_is_known_server(session); + + switch(state) { + case SSH_KNOWN_HOSTS_CHANGED: + fprintf(stderr,"Host key for server changed : server's one is now :\n"); + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); + ssh_clean_pubkey_hash(&hash); + fprintf(stderr,"For security reason, connection will be stopped\n"); + return -1; + case SSH_KNOWN_HOSTS_OTHER: + fprintf(stderr,"The host key for this server was not found but an other type of key exists.\n"); + fprintf(stderr,"An attacker might change the default server key to confuse your client" + "into thinking the key does not exist\n" + "We advise you to rerun the client with -d or -r for more safety.\n"); + return -1; + case SSH_KNOWN_HOSTS_NOT_FOUND: + fprintf(stderr,"Could not find known host file. If you accept the host key here,\n"); + fprintf(stderr,"the file will be automatically created.\n"); + /* fallback to SSH_SERVER_NOT_KNOWN behavior */ + FALL_THROUGH; + case SSH_SERVER_NOT_KNOWN: + fprintf(stderr, + "The server is unknown. Do you trust the host key (yes/no)?\n"); + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); + + if (fgets(buf, sizeof(buf), stdin) == NULL) { + ssh_clean_pubkey_hash(&hash); + return -1; + } + if(strncasecmp(buf,"yes",3)!=0){ + ssh_clean_pubkey_hash(&hash); + return -1; + } + fprintf(stderr,"This new key will be written on disk for further usage. do you agree ?\n"); + if (fgets(buf, sizeof(buf), stdin) == NULL) { + ssh_clean_pubkey_hash(&hash); + return -1; + } + if(strncasecmp(buf,"yes",3)==0){ + rc = ssh_session_update_known_hosts(session); + if (rc != SSH_OK) { + ssh_clean_pubkey_hash(&hash); + fprintf(stderr, "error %s\n", strerror(errno)); + return -1; + } + } + + break; + case SSH_KNOWN_HOSTS_ERROR: + ssh_clean_pubkey_hash(&hash); + fprintf(stderr,"%s",ssh_get_error(session)); + return -1; + case SSH_KNOWN_HOSTS_OK: + break; /* ok */ + } + + ssh_clean_pubkey_hash(&hash); + + return 0; +} diff --git a/examples/libssh_scp.c b/examples/libssh_scp.c new file mode 100644 index 0000000..7a30b37 --- /dev/null +++ b/examples/libssh_scp.c @@ -0,0 +1,453 @@ +/* libssh_scp.c + * Sample implementation of a SCP client + */ + +/* +Copyright 2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. + */ + +#include +#include +#include +#include +#include + +#include +#include "examples_common.h" + +static char **sources; +static int nsources; +static char *destination; +static int verbosity = 0; + +struct location { + int is_ssh; + char *user; + char *host; + char *path; + ssh_session session; + ssh_scp scp; + FILE *file; +}; + +enum { + READ, + WRITE +}; + +static void usage(const char *argv0) { + fprintf(stderr, "Usage : %s [options] [[user@]host1:]file1 ... \n" + " [[user@]host2:]destination\n" + "sample scp client - libssh-%s\n", + // "Options :\n", + // " -r : use RSA to verify host public key\n", + argv0, + ssh_version(0)); + exit(0); +} + +static int opts(int argc, char **argv) { + int i; + + while((i = getopt(argc, argv, "v")) != -1) { + switch(i) { + case 'v': + verbosity++; + break; + default: + fprintf(stderr, "unknown option %c\n", optopt); + usage(argv[0]); + return -1; + } + } + + nsources = argc - optind - 1; + if (nsources < 1) { + usage(argv[0]); + return -1; + } + + sources = malloc((nsources + 1) * sizeof(char *)); + if (sources == NULL) { + return -1; + } + + for(i = 0; i < nsources; ++i) { + sources[i] = argv[optind]; + optind++; + } + + sources[i] = NULL; + destination = argv[optind]; + return 0; +} + +static void location_free(struct location *loc) +{ + if (loc) { + if (loc->path) { + free(loc->path); + } + loc->path = NULL; + if (loc->is_ssh) { + if (loc->host) { + free(loc->host); + } + loc->host = NULL; + if (loc->user) { + free(loc->user); + } + loc->user = NULL; + if (loc->host) { + free(loc->host); + } + loc->host = NULL; + } + free(loc); + } +} + +static struct location *parse_location(char *loc) { + struct location *location; + char *ptr; + + location = malloc(sizeof(struct location)); + if (location == NULL) { + return NULL; + } + memset(location, 0, sizeof(struct location)); + + location->host = location->user = NULL; + ptr = strchr(loc, ':'); + + if (ptr != NULL) { + location->is_ssh = 1; + location->path = strdup(ptr+1); + *ptr = '\0'; + ptr = strchr(loc, '@'); + + if (ptr != NULL) { + location->host = strdup(ptr+1); + *ptr = '\0'; + location->user = strdup(loc); + } else { + location->host = strdup(loc); + } + } else { + location->is_ssh = 0; + location->path = strdup(loc); + } + return location; +} + +static void close_location(struct location *loc) { + int rc; + + if (loc) { + if (loc->is_ssh) { + if (loc->scp) { + rc = ssh_scp_close(loc->scp); + if (rc == SSH_ERROR) { + fprintf(stderr, + "Error closing scp: %s\n", + ssh_get_error(loc->session)); + } + ssh_scp_free(loc->scp); + loc->scp = NULL; + } + if (loc->session) { + ssh_disconnect(loc->session); + ssh_free(loc->session); + loc->session = NULL; + } + } else { + if (loc->file) { + fclose(loc->file); + loc->file = NULL; + } + } + } +} + +static int open_location(struct location *loc, int flag) { + if (loc->is_ssh && flag == WRITE) { + loc->session = connect_ssh(loc->host, loc->user, verbosity); + if (!loc->session) { + fprintf(stderr, "Couldn't connect to %s\n", loc->host); + return -1; + } + + loc->scp = ssh_scp_new(loc->session, SSH_SCP_WRITE, loc->path); + if (!loc->scp) { + fprintf(stderr, "error : %s\n", ssh_get_error(loc->session)); + ssh_disconnect(loc->session); + ssh_free(loc->session); + loc->session = NULL; + return -1; + } + + if (ssh_scp_init(loc->scp) == SSH_ERROR) { + fprintf(stderr, "error : %s\n", ssh_get_error(loc->session)); + ssh_scp_free(loc->scp); + loc->scp = NULL; + ssh_disconnect(loc->session); + ssh_free(loc->session); + loc->session = NULL; + return -1; + } + return 0; + } else if (loc->is_ssh && flag == READ) { + loc->session = connect_ssh(loc->host, loc->user, verbosity); + if (!loc->session) { + fprintf(stderr, "Couldn't connect to %s\n", loc->host); + return -1; + } + + loc->scp = ssh_scp_new(loc->session, SSH_SCP_READ, loc->path); + if (!loc->scp) { + fprintf(stderr, "error : %s\n", ssh_get_error(loc->session)); + ssh_disconnect(loc->session); + ssh_free(loc->session); + loc->session = NULL; + return -1; + } + + if (ssh_scp_init(loc->scp) == SSH_ERROR) { + fprintf(stderr, "error : %s\n", ssh_get_error(loc->session)); + ssh_scp_free(loc->scp); + loc->scp = NULL; + ssh_disconnect(loc->session); + ssh_free(loc->session); + loc->session = NULL; + return -1; + } + return 0; + } else { + loc->file = fopen(loc->path, flag == READ ? "r":"w"); + if (!loc->file) { + if (errno == EISDIR) { + if (chdir(loc->path)) { + fprintf(stderr, + "Error changing directory to %s: %s\n", + loc->path, strerror(errno)); + return -1; + } + return 0; + } + fprintf(stderr, + "Error opening %s: %s\n", + loc->path, strerror(errno)); + return -1; + } + return 0; + } + return -1; +} + +/** @brief copies files from source location to destination + * @param src source location + * @param dest destination location + * @param recursive Copy also directories + */ +static int do_copy(struct location *src, struct location *dest, int recursive) { + size_t size; + socket_t fd; + struct stat s; + int w, r; + char buffer[16384]; + size_t total = 0; + mode_t mode; + char *filename = NULL; + + /* recursive mode doesn't work yet */ + (void)recursive; + /* Get the file name and size*/ + if (!src->is_ssh) { + fd = fileno(src->file); + if (fd < 0) { + fprintf(stderr, + "Invalid file pointer, error: %s\n", + strerror(errno)); + return -1; + } + r = fstat(fd, &s); + if (r < 0) { + return -1; + } + size = s.st_size; + mode = s.st_mode & ~S_IFMT; + filename = ssh_basename(src->path); + } else { + size = 0; + do { + r = ssh_scp_pull_request(src->scp); + if (r == SSH_SCP_REQUEST_NEWDIR) { + ssh_scp_deny_request(src->scp, "Not in recursive mode"); + continue; + } + if (r == SSH_SCP_REQUEST_NEWFILE) { + size = ssh_scp_request_get_size(src->scp); + filename = strdup(ssh_scp_request_get_filename(src->scp)); + mode = ssh_scp_request_get_permissions(src->scp); + //ssh_scp_accept_request(src->scp); + break; + } + if (r == SSH_ERROR) { + fprintf(stderr, + "Error: %s\n", + ssh_get_error(src->session)); + SSH_STRING_FREE_CHAR(filename); + return -1; + } + } while(r != SSH_SCP_REQUEST_NEWFILE); + } + + if (dest->is_ssh) { + r = ssh_scp_push_file(dest->scp, src->path, size, mode); + // snprintf(buffer, sizeof(buffer), "C0644 %d %s\n", size, src->path); + if (r == SSH_ERROR) { + fprintf(stderr, + "error: %s\n", + ssh_get_error(dest->session)); + SSH_STRING_FREE_CHAR(filename); + ssh_scp_free(dest->scp); + dest->scp = NULL; + return -1; + } + } else { + if (!dest->file) { + dest->file = fopen(filename, "w"); + if (!dest->file) { + fprintf(stderr, + "Cannot open %s for writing: %s\n", + filename, strerror(errno)); + if (src->is_ssh) { + ssh_scp_deny_request(src->scp, "Cannot open local file"); + } + SSH_STRING_FREE_CHAR(filename); + return -1; + } + } + if (src->is_ssh) { + ssh_scp_accept_request(src->scp); + } + } + + do { + if (src->is_ssh) { + r = ssh_scp_read(src->scp, buffer, sizeof(buffer)); + if (r == SSH_ERROR) { + fprintf(stderr, + "Error reading scp: %s\n", + ssh_get_error(src->session)); + SSH_STRING_FREE_CHAR(filename); + return -1; + } + + if (r == 0) { + break; + } + } else { + r = fread(buffer, 1, sizeof(buffer), src->file); + if (r == 0) { + break; + } + + if (r < 0) { + fprintf(stderr, + "Error reading file: %s\n", + strerror(errno)); + SSH_STRING_FREE_CHAR(filename); + return -1; + } + } + + if (dest->is_ssh) { + w = ssh_scp_write(dest->scp, buffer, r); + if (w == SSH_ERROR) { + fprintf(stderr, + "Error writing in scp: %s\n", + ssh_get_error(dest->session)); + ssh_scp_free(dest->scp); + dest->scp = NULL; + SSH_STRING_FREE_CHAR(filename); + return -1; + } + } else { + w = fwrite(buffer, r, 1, dest->file); + if (w <= 0) { + fprintf(stderr, + "Error writing in local file: %s\n", + strerror(errno)); + SSH_STRING_FREE_CHAR(filename); + return -1; + } + } + total += r; + + } while(total < size); + + SSH_STRING_FREE_CHAR(filename); + printf("wrote %zu bytes\n", total); + return 0; +} + +int main(int argc, char **argv) { + struct location *dest, *src; + int i; + int r; + if (opts(argc, argv) < 0) { + r = EXIT_FAILURE; + goto end; + } + + dest = parse_location(destination); + if (dest == NULL) { + r = EXIT_FAILURE; + goto end; + } + + if (open_location(dest, WRITE) < 0) { + location_free(dest); + r = EXIT_FAILURE; + goto end; + } + + for (i = 0; i < nsources; ++i) { + src = parse_location(sources[i]); + if (src == NULL) { + r = EXIT_FAILURE; + goto close_dest; + } + + if (open_location(src, READ) < 0) { + location_free(src); + r = EXIT_FAILURE; + goto close_dest; + } + + if (do_copy(src, dest, 0) < 0) { + close_location(src); + location_free(src); + break; + } + + close_location(src); + location_free(src); + } + + r = 0; + +close_dest: + close_location(dest); + location_free(dest); +end: + return r; +} diff --git a/examples/libsshpp.cpp b/examples/libsshpp.cpp new file mode 100644 index 0000000..8f042a4 --- /dev/null +++ b/examples/libsshpp.cpp @@ -0,0 +1,33 @@ +/* +Copyright 2010 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +*/ + +/* This file demonstrates the use of the C++ wrapper to libssh */ + +#include +#include +#include + +int main(int argc, const char **argv){ + ssh::Session session; + try { + if(argc>1) + session.setOption(SSH_OPTIONS_HOST,argv[1]); + else + session.setOption(SSH_OPTIONS_HOST,"localhost"); + session.connect(); + session.userauthPublickeyAuto(); + session.disconnect(); + } catch (ssh::SshException e){ + std::cout << "Error during connection : "; + std::cout << e.getError() << std::endl; + } + return 0; +} diff --git a/examples/libsshpp_noexcept.cpp b/examples/libsshpp_noexcept.cpp new file mode 100644 index 0000000..eff8cc1 --- /dev/null +++ b/examples/libsshpp_noexcept.cpp @@ -0,0 +1,41 @@ +/* +Copyright 2010 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +*/ + +/* This file demonstrates the use of the C++ wrapper to libssh + * specifically, without C++ exceptions + */ + +#include +#define SSH_NO_CPP_EXCEPTIONS +#include + +int main(int argc, const char **argv){ + ssh::Session session,s2; + int err; + if(argc>1) + err=session.setOption(SSH_OPTIONS_HOST,argv[1]); + else + err=session.setOption(SSH_OPTIONS_HOST,"localhost"); + if(err==SSH_ERROR) + goto error; + err=session.connect(); + if(err==SSH_ERROR) + goto error; + err=session.userauthPublickeyAuto(); + if(err==SSH_ERROR) + goto error; + + return 0; + error: + std::cout << "Error during connection : "; + std::cout << session.getError() << std::endl; + return 1; +} diff --git a/examples/proxy.c b/examples/proxy.c new file mode 100644 index 0000000..dcf4d0d --- /dev/null +++ b/examples/proxy.c @@ -0,0 +1,347 @@ +/* This is a sample implementation of a libssh based SSH proxy */ +/* +Copyright 2003-2013 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif +#include +#include +#include + +#define USER "myuser" +#define PASSWORD "mypassword" + +static int authenticated=0; +static int tries = 0; +static int error = 0; +static ssh_channel chan=NULL; +static char *username; +static ssh_gssapi_creds client_creds = NULL; + +static int auth_password(ssh_session session, const char *user, + const char *password, void *userdata){ + + (void)userdata; + + printf("Authenticating user %s pwd %s\n",user, password); + if(strcmp(user,USER) == 0 && strcmp(password, PASSWORD) == 0){ + authenticated = 1; + printf("Authenticated\n"); + return SSH_AUTH_SUCCESS; + } + if (tries >= 3){ + printf("Too many authentication tries\n"); + ssh_disconnect(session); + error = 1; + return SSH_AUTH_DENIED; + } + tries++; + return SSH_AUTH_DENIED; +} + +static int auth_gssapi_mic(ssh_session session, const char *user, const char *principal, void *userdata){ + (void)userdata; + client_creds = ssh_gssapi_get_creds(session); + printf("Authenticating user %s with gssapi principal %s\n",user, principal); + if (client_creds != NULL) + printf("Received some gssapi credentials\n"); + else + printf("Not received any forwardable creds\n"); + printf("authenticated\n"); + authenticated = 1; + username = strdup(principal); + return SSH_AUTH_SUCCESS; +} + +static int pty_request(ssh_session session, ssh_channel channel, const char *term, + int x,int y, int px, int py, void *userdata){ + (void) session; + (void) channel; + (void) term; + (void) x; + (void) y; + (void) px; + (void) py; + (void) userdata; + printf("Allocated terminal\n"); + return 0; +} + +static int shell_request(ssh_session session, ssh_channel channel, void *userdata){ + (void)session; + (void)channel; + (void)userdata; + printf("Allocated shell\n"); + return 0; +} +struct ssh_channel_callbacks_struct channel_cb = { + .channel_pty_request_function = pty_request, + .channel_shell_request_function = shell_request +}; + +static ssh_channel new_session_channel(ssh_session session, void *userdata){ + (void) session; + (void) userdata; + if(chan != NULL) + return NULL; + printf("Allocated session channel\n"); + chan = ssh_channel_new(session); + ssh_callbacks_init(&channel_cb); + ssh_set_channel_callbacks(chan, &channel_cb); + return chan; +} + + +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh proxy example " +SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "dsakey", + .key = 'd', + .arg = "FILE", + .flags = 0, + .doc = "Set the dsa key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key.", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + break; + case 'd': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, arg); + break; + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'r': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, arg); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage (state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage (state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +int main(int argc, char **argv){ + ssh_session session; + ssh_bind sshbind; + ssh_event mainloop; + ssh_session client_session; + + struct ssh_server_callbacks_struct cb = { + .userdata = NULL, + .auth_password_function = auth_password, + .auth_gssapi_mic_function = auth_gssapi_mic, + .channel_open_request_session_function = new_session_channel + }; + + char buf[2048]; + char host[128]=""; + char *ptr; + int i,r, rc; + + sshbind=ssh_bind_new(); + session=ssh_new(); + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, "sshd_rsa"); + +#ifdef HAVE_ARGP_H + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ + argp_parse (&argp, argc, argv, 0, 0, sshbind); +#else + (void) argc; + (void) argv; +#endif + + if(ssh_bind_listen(sshbind)<0){ + printf("Error listening to socket: %s\n",ssh_get_error(sshbind)); + return 1; + } + r=ssh_bind_accept(sshbind,session); + if(r==SSH_ERROR){ + printf("error accepting a connection : %s\n",ssh_get_error(sshbind)); + return 1; + } + ssh_callbacks_init(&cb); + ssh_set_server_callbacks(session, &cb); + + if (ssh_handle_key_exchange(session)) { + printf("ssh_handle_key_exchange: %s\n", ssh_get_error(session)); + return 1; + } + ssh_set_auth_methods(session,SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_GSSAPI_MIC); + mainloop = ssh_event_new(); + ssh_event_add_session(mainloop, session); + + while (!(authenticated && chan != NULL)){ + if(error) + break; + r = ssh_event_dopoll(mainloop, -1); + if (r == SSH_ERROR){ + printf("Error : %s\n",ssh_get_error(session)); + ssh_disconnect(session); + return 1; + } + } + if(error){ + printf("Error, exiting loop\n"); + return 1; + } else + printf("Authenticated and got a channel\n"); + if (!client_creds){ + snprintf(buf,sizeof(buf), "Sorry, but you do not have forwardable tickets. Try again with -K\r\n"); + ssh_channel_write(chan,buf,strlen(buf)); + printf("%s",buf); + ssh_disconnect(session); + return 1; + } + snprintf(buf,sizeof(buf), "Hello %s, welcome to the Sample SSH proxy.\r\nPlease select your destination: ", username); + ssh_channel_write(chan, buf, strlen(buf)); + do{ + i=ssh_channel_read(chan,buf, 2048, 0); + if(i>0) { + ssh_channel_write(chan, buf, i); + if(strlen(host) + i < sizeof(host)){ + strncat(host, buf, i); + } + if (strchr(host, '\x0d')) { + *strchr(host, '\x0d')='\0'; + ssh_channel_write(chan, "\n", 1); + break; + } + } else { + printf ("Error: %s\n", ssh_get_error(session) ); + return 1; + } + } while (i>0); + snprintf(buf,sizeof(buf),"Trying to connect to \"%s\"\r\n", host); + ssh_channel_write(chan, buf, strlen(buf)); + printf("%s",buf); + + client_session = ssh_new(); + + /* ssh servers expect username without realm */ + ptr = strchr(username,'@'); + if(ptr) + *ptr= '\0'; + ssh_options_set(client_session, SSH_OPTIONS_HOST, host); + ssh_options_set(client_session, SSH_OPTIONS_USER, username); + ssh_gssapi_set_creds(client_session, client_creds); + rc = ssh_connect(client_session); + if (rc != SSH_OK){ + printf("Error connecting to %s: %s", host, ssh_get_error(client_session)); + return 1; + } + rc = ssh_userauth_none(client_session, NULL); + if(rc == SSH_AUTH_SUCCESS){ + printf("Authenticated using method none\n"); + } else { + rc = ssh_userauth_gssapi(client_session); + if(rc != SSH_AUTH_SUCCESS){ + printf("GSSAPI Authentication failed: %s\n",ssh_get_error(client_session)); + return 1; + } + } + snprintf(buf,sizeof(buf), "Authentication success\r\n"); + printf("%s",buf); + ssh_channel_write(chan,buf,strlen(buf)); + ssh_disconnect(client_session); + ssh_disconnect(session); + ssh_bind_free(sshbind); + ssh_finalize(); + return 0; +} + diff --git a/examples/samplesftp.c b/examples/samplesftp.c new file mode 100644 index 0000000..a862441 --- /dev/null +++ b/examples/samplesftp.c @@ -0,0 +1,291 @@ +/* +Copyright 2003-2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include + +#include "examples_common.h" +#ifdef WITH_SFTP + +static int verbosity; +static char *destination; + +#define DATALEN 65536 + +static void do_sftp(ssh_session session) { + sftp_session sftp = sftp_new(session); + sftp_dir dir; + sftp_attributes file; + sftp_statvfs_t sftpstatvfs; + struct statvfs sysstatvfs; + sftp_file fichier; + sftp_file to; + int len = 1; + unsigned int i; + char data[DATALEN] = {0}; + char *lnk; + + unsigned int count; + + if (!sftp) { + fprintf(stderr, "sftp error initialising channel: %s\n", + ssh_get_error(session)); + goto end; + } + + if (sftp_init(sftp)) { + fprintf(stderr, "error initialising sftp: %s\n", + ssh_get_error(session)); + goto end; + } + + printf("Additional SFTP extensions provided by the server:\n"); + count = sftp_extensions_get_count(sftp); + for (i = 0; i < count; i++) { + printf("\t%s, version: %s\n", + sftp_extensions_get_name(sftp, i), + sftp_extensions_get_data(sftp, i)); + } + + /* test symlink and readlink */ + if (sftp_symlink(sftp, "/tmp/this_is_the_link", + "/tmp/sftp_symlink_test") < 0) + { + fprintf(stderr, "Could not create link (%s)\n", + ssh_get_error(session)); + goto end; + } + + lnk = sftp_readlink(sftp, "/tmp/sftp_symlink_test"); + if (lnk == NULL) { + fprintf(stderr, "Could not read link (%s)\n", ssh_get_error(session)); + goto end; + } + printf("readlink /tmp/sftp_symlink_test: %s\n", lnk); + + sftp_unlink(sftp, "/tmp/sftp_symlink_test"); + + if (sftp_extension_supported(sftp, "statvfs@openssh.com", "2")) { + sftpstatvfs = sftp_statvfs(sftp, "/tmp"); + if (sftpstatvfs == NULL) { + fprintf(stderr, "statvfs failed (%s)\n", ssh_get_error(session)); + goto end; + } + + printf("sftp statvfs:\n" + "\tfile system block size: %llu\n" + "\tfundamental fs block size: %llu\n" + "\tnumber of blocks (unit f_frsize): %llu\n" + "\tfree blocks in file system: %llu\n" + "\tfree blocks for non-root: %llu\n" + "\ttotal file inodes: %llu\n" + "\tfree file inodes: %llu\n" + "\tfree file inodes for to non-root: %llu\n" + "\tfile system id: %llu\n" + "\tbit mask of f_flag values: %llu\n" + "\tmaximum filename length: %llu\n", + (unsigned long long) sftpstatvfs->f_bsize, + (unsigned long long) sftpstatvfs->f_frsize, + (unsigned long long) sftpstatvfs->f_blocks, + (unsigned long long) sftpstatvfs->f_bfree, + (unsigned long long) sftpstatvfs->f_bavail, + (unsigned long long) sftpstatvfs->f_files, + (unsigned long long) sftpstatvfs->f_ffree, + (unsigned long long) sftpstatvfs->f_favail, + (unsigned long long) sftpstatvfs->f_fsid, + (unsigned long long) sftpstatvfs->f_flag, + (unsigned long long) sftpstatvfs->f_namemax); + + sftp_statvfs_free(sftpstatvfs); + + if (statvfs("/tmp", &sysstatvfs) < 0) { + fprintf(stderr, "statvfs failed (%s)\n", strerror(errno)); + goto end; + } + + printf("sys statvfs:\n" + "\tfile system block size: %llu\n" + "\tfundamental fs block size: %llu\n" + "\tnumber of blocks (unit f_frsize): %llu\n" + "\tfree blocks in file system: %llu\n" + "\tfree blocks for non-root: %llu\n" + "\ttotal file inodes: %llu\n" + "\tfree file inodes: %llu\n" + "\tfree file inodes for to non-root: %llu\n" + "\tfile system id: %llu\n" + "\tbit mask of f_flag values: %llu\n" + "\tmaximum filename length: %llu\n", + (unsigned long long) sysstatvfs.f_bsize, + (unsigned long long) sysstatvfs.f_frsize, + (unsigned long long) sysstatvfs.f_blocks, + (unsigned long long) sysstatvfs.f_bfree, + (unsigned long long) sysstatvfs.f_bavail, + (unsigned long long) sysstatvfs.f_files, + (unsigned long long) sysstatvfs.f_ffree, + (unsigned long long) sysstatvfs.f_favail, + (unsigned long long) sysstatvfs.f_fsid, + (unsigned long long) sysstatvfs.f_flag, + (unsigned long long) sysstatvfs.f_namemax); + } + + /* the connection is made */ + /* opening a directory */ + dir = sftp_opendir(sftp, "./"); + if (!dir) { + fprintf(stderr, "Directory not opened(%s)\n", ssh_get_error(session)); + goto end; + } + + /* reading the whole directory, file by file */ + while ((file = sftp_readdir(sftp, dir))) { + fprintf(stderr, "%30s(%.8o) : %s(%.5d) %s(%.5d) : %.10llu bytes\n", + file->name, + file->permissions, + file->owner, + file->uid, + file->group, + file->gid, + (long long unsigned int) file->size); + sftp_attributes_free(file); + } + + /* when file = NULL, an error has occured OR the directory listing is end of + * file */ + if (!sftp_dir_eof(dir)) { + fprintf(stderr, "Error: %s\n", ssh_get_error(session)); + goto end; + } + + if (sftp_closedir(dir)) { + fprintf(stderr, "Error: %s\n", ssh_get_error(session)); + goto end; + } + /* this will open a file and copy it into your /home directory */ + /* the small buffer size was intended to stress the library. of course, you + * can use a buffer till 20kbytes without problem */ + + fichier = sftp_open(sftp, "/usr/bin/ssh", O_RDONLY, 0); + if (!fichier) { + fprintf(stderr, "Error opening /usr/bin/ssh: %s\n", + ssh_get_error(session)); + goto end; + } + + /* open a file for writing... */ + to = sftp_open(sftp, "ssh-copy", O_WRONLY | O_CREAT, 0700); + if (!to) { + fprintf(stderr, "Error opening ssh-copy for writing: %s\n", + ssh_get_error(session)); + sftp_close(fichier); + goto end; + } + + while ((len = sftp_read(fichier, data, 4096)) > 0) { + if (sftp_write(to, data, len) != len) { + fprintf(stderr, "Error writing %d bytes: %s\n", + len, ssh_get_error(session)); + sftp_close(to); + sftp_close(fichier); + goto end; + } + } + + printf("finished\n"); + if (len < 0) { + fprintf(stderr, "Error reading file: %s\n", ssh_get_error(session)); + } + + sftp_close(fichier); + sftp_close(to); + printf("fichiers ferm\n"); + to = sftp_open(sftp, "/tmp/grosfichier", O_WRONLY|O_CREAT, 0644); + + for (i = 0; i < 1000; ++i) { + len = sftp_write(to, data, DATALEN); + printf("wrote %d bytes\n", len); + if (len != DATALEN) { + printf("chunk %d : %d (%s)\n", i, len, ssh_get_error(session)); + } + } + + sftp_close(to); +end: + /* close the sftp session */ + sftp_free(sftp); + printf("sftp session terminated\n"); +} + +static void usage(const char *argv0) { + fprintf(stderr, "Usage : %s [-v] remotehost\n" + "sample sftp test client - libssh-%s\n" + "Options :\n" + " -v : increase log verbosity\n", + argv0, + ssh_version(0)); + exit(0); +} + +static int opts(int argc, char **argv) { + int i; + + while ((i = getopt(argc, argv, "v")) != -1) { + switch(i) { + case 'v': + verbosity++; + break; + default: + fprintf(stderr, "unknown option %c\n", optopt); + usage(argv[0]); + return -1; + } + } + + destination = argv[optind]; + if (destination == NULL) { + usage(argv[0]); + return -1; + } + return 0; +} + +int main(int argc, char **argv) { + ssh_session session; + + if (opts(argc, argv) < 0) { + return EXIT_FAILURE; + } + + session = connect_ssh(destination, NULL, verbosity); + if (session == NULL) { + return EXIT_FAILURE; + } + + do_sftp(session); + ssh_disconnect(session); + ssh_free(session); + return 0; +} + +#endif diff --git a/examples/samplesshd-cb.c b/examples/samplesshd-cb.c new file mode 100644 index 0000000..f93ab4b --- /dev/null +++ b/examples/samplesshd-cb.c @@ -0,0 +1,306 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2003-2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif +#include +#include +#include + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#define USER "myuser" +#define PASSWORD "mypassword" + +static int authenticated=0; +static int tries = 0; +static int error = 0; +static ssh_channel chan=NULL; + +static int auth_password(ssh_session session, const char *user, + const char *password, void *userdata){ + (void)userdata; + printf("Authenticating user %s pwd %s\n",user, password); + if(strcmp(user,USER) == 0 && strcmp(password, PASSWORD) == 0){ + authenticated = 1; + printf("Authenticated\n"); + return SSH_AUTH_SUCCESS; + } + if (tries >= 3){ + printf("Too many authentication tries\n"); + ssh_disconnect(session); + error = 1; + return SSH_AUTH_DENIED; + } + tries++; + return SSH_AUTH_DENIED; +} + +static int auth_gssapi_mic(ssh_session session, const char *user, const char *principal, void *userdata){ + ssh_gssapi_creds creds = ssh_gssapi_get_creds(session); + (void)userdata; + printf("Authenticating user %s with gssapi principal %s\n",user, principal); + if (creds != NULL) + printf("Received some gssapi credentials\n"); + else + printf("Not received any forwardable creds\n"); + printf("authenticated\n"); + authenticated = 1; + return SSH_AUTH_SUCCESS; +} + +static int pty_request(ssh_session session, ssh_channel channel, const char *term, + int x,int y, int px, int py, void *userdata){ + (void) session; + (void) channel; + (void) term; + (void) x; + (void) y; + (void) px; + (void) py; + (void) userdata; + printf("Allocated terminal\n"); + return 0; +} + +static int shell_request(ssh_session session, ssh_channel channel, void *userdata){ + (void)session; + (void)channel; + (void)userdata; + printf("Allocated shell\n"); + return 0; +} +struct ssh_channel_callbacks_struct channel_cb = { + .channel_pty_request_function = pty_request, + .channel_shell_request_function = shell_request +}; + +static ssh_channel new_session_channel(ssh_session session, void *userdata){ + (void) session; + (void) userdata; + if(chan != NULL) + return NULL; + printf("Allocated session channel\n"); + chan = ssh_channel_new(session); + ssh_callbacks_init(&channel_cb); + ssh_set_channel_callbacks(chan, &channel_cb); + return chan; +} + + +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh server example " +SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "dsakey", + .key = 'd', + .arg = "FILE", + .flags = 0, + .doc = "Set the dsa key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key.", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + break; + case 'd': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, arg); + break; + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'r': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, arg); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage (state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage (state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +int main(int argc, char **argv){ + ssh_session session; + ssh_bind sshbind; + ssh_event mainloop; + struct ssh_server_callbacks_struct cb = { + .userdata = NULL, + .auth_password_function = auth_password, + .auth_gssapi_mic_function = auth_gssapi_mic, + .channel_open_request_session_function = new_session_channel + }; + + char buf[2048]; + int i; + int r; + + sshbind=ssh_bind_new(); + session=ssh_new(); + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, KEYS_FOLDER "ssh_host_dsa_key"); + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, KEYS_FOLDER "ssh_host_rsa_key"); + +#ifdef HAVE_ARGP_H + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ + argp_parse (&argp, argc, argv, 0, 0, sshbind); +#else + (void) argc; + (void) argv; +#endif + + if(ssh_bind_listen(sshbind)<0){ + printf("Error listening to socket: %s\n",ssh_get_error(sshbind)); + return 1; + } + r=ssh_bind_accept(sshbind,session); + if(r==SSH_ERROR){ + printf("error accepting a connection : %s\n",ssh_get_error(sshbind)); + return 1; + } + ssh_callbacks_init(&cb); + ssh_set_server_callbacks(session, &cb); + + if (ssh_handle_key_exchange(session)) { + printf("ssh_handle_key_exchange: %s\n", ssh_get_error(session)); + return 1; + } + ssh_set_auth_methods(session,SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_GSSAPI_MIC); + mainloop = ssh_event_new(); + ssh_event_add_session(mainloop, session); + + while (!(authenticated && chan != NULL)){ + if(error) + break; + r = ssh_event_dopoll(mainloop, -1); + if (r == SSH_ERROR){ + printf("Error : %s\n",ssh_get_error(session)); + ssh_disconnect(session); + return 1; + } + } + if(error){ + printf("Error, exiting loop\n"); + } else + printf("Authenticated and got a channel\n"); + do{ + i=ssh_channel_read(chan,buf, 2048, 0); + if(i>0) { + ssh_channel_write(chan, buf, i); + if (write(1,buf,i) < 0) { + printf("error writing to buffer\n"); + return 1; + } + if (buf[0] == '\x0d') { + if (write(1, "\n", 1) < 0) { + printf("error writing to buffer\n"); + return 1; + } + ssh_channel_write(chan, "\n", 1); + } + } + } while (i>0); + ssh_disconnect(session); + ssh_bind_free(sshbind); + ssh_finalize(); + return 0; +} + diff --git a/examples/samplesshd-kbdint.c b/examples/samplesshd-kbdint.c new file mode 100644 index 0000000..522aebd --- /dev/null +++ b/examples/samplesshd-kbdint.c @@ -0,0 +1,425 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2003-2011 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif +#include +#include +#include +#include + +#define SSHD_USER "libssh" +#define SSHD_PASSWORD "libssh" + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +static int port = 22; +static bool authenticated = false; + +#ifdef WITH_PCAP +static const char *pcap_file = "debug.server.pcap"; +static ssh_pcap_file pcap; + +static void set_pcap(ssh_session session){ + if(!pcap_file) + return; + pcap=ssh_pcap_file_new(); + if(ssh_pcap_file_open(pcap,pcap_file) == SSH_ERROR){ + printf("Error opening pcap file\n"); + ssh_pcap_file_free(pcap); + pcap=NULL; + return; + } + ssh_set_pcap_file(session,pcap); +} + +static void cleanup_pcap(void) { + ssh_pcap_file_free(pcap); + pcap=NULL; +} +#endif + + +static int auth_password(const char *user, const char *password) +{ + int cmp; + + cmp = strcmp(user, SSHD_USER); + if (cmp != 0) { + return 0; + } + cmp = strcmp(password, SSHD_PASSWORD); + if (cmp != 0) { + return 0; + } + + authenticated = true; + return 1; // authenticated +} +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh server example " + SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "dsakey", + .key = 'd', + .arg = "FILE", + .flags = 0, + .doc = "Set the dsa key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key.", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + {NULL, 0, 0, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + port = atoi(arg); + break; + case 'd': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, arg); + break; + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'r': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, arg); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage (state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage (state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +static const char *name; +static const char *instruction; +static const char *prompts[2]; +static char echo[] = { 1, 0 }; + +static int kbdint_check_response(ssh_session session) { + int count; + + count = ssh_userauth_kbdint_getnanswers(session); + if(count != 2) { + instruction = "Something weird happened :("; + return 0; + } + if(strcasecmp("Arthur Dent", + ssh_userauth_kbdint_getanswer(session, 0)) != 0) { + instruction = "OK, this is not YOUR name, " + "but it's a reference to the HGTG..."; + prompts[0] = "The main character's full name: "; + return 0; + } + if(strcmp("42", ssh_userauth_kbdint_getanswer(session, 1)) != 0) { + instruction = "Make an effort !!! What is the Answer to the Ultimate " + "Question of Life, the Universe, and Everything ?"; + prompts[1] = "Answer to the Ultimate Question of Life, the Universe, " + "and Everything: "; + return 0; + } + + authenticated = true; + return 1; +} + +static int authenticate(ssh_session session) { + ssh_message message; + + name = "\n\nKeyboard-Interactive Fancy Authentication\n"; + instruction = "Please enter your real name and your password"; + prompts[0] = "Real name: "; + prompts[1] = "Password: "; + + do { + message=ssh_message_get(session); + if(!message) + break; + switch(ssh_message_type(message)){ + case SSH_REQUEST_AUTH: + switch(ssh_message_subtype(message)){ + case SSH_AUTH_METHOD_PASSWORD: + printf("User %s wants to auth with pass %s\n", + ssh_message_auth_user(message), + ssh_message_auth_password(message)); + if(auth_password(ssh_message_auth_user(message), + ssh_message_auth_password(message))){ + ssh_message_auth_reply_success(message,0); + ssh_message_free(message); + return 1; + } + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + // not authenticated, send default message + ssh_message_reply_default(message); + break; + + case SSH_AUTH_METHOD_INTERACTIVE: + if(!ssh_message_auth_kbdint_is_response(message)) { + printf("User %s wants to auth with kbdint\n", + ssh_message_auth_user(message)); + ssh_message_auth_interactive_request(message, name, + instruction, 2, prompts, echo); + } else { + if(kbdint_check_response(session)) { + ssh_message_auth_reply_success(message,0); + ssh_message_free(message); + return 1; + } + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + } + break; + case SSH_AUTH_METHOD_NONE: + default: + printf("User %s wants to auth with unknown auth %d\n", + ssh_message_auth_user(message), + ssh_message_subtype(message)); + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + break; + } + break; + default: + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + } + ssh_message_free(message); + } while (1); + return 0; +} + +int main(int argc, char **argv){ + ssh_session session; + ssh_bind sshbind; + ssh_message message; + ssh_channel chan=0; + char buf[2048]; + int auth=0; + int shell=0; + int i; + int r; + + sshbind=ssh_bind_new(); + session=ssh_new(); + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, + KEYS_FOLDER "ssh_host_dsa_key"); + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, + KEYS_FOLDER "ssh_host_rsa_key"); + +#ifdef HAVE_ARGP_H + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ + argp_parse (&argp, argc, argv, 0, 0, sshbind); +#else + (void) argc; + (void) argv; +#endif +#ifdef WITH_PCAP + set_pcap(session); +#endif + + if(ssh_bind_listen(sshbind)<0){ + printf("Error listening to socket: %s\n", ssh_get_error(sshbind)); + return 1; + } + printf("Started sample libssh sshd on port %d\n", port); + printf("You can login as the user %s with the password %s\n", SSHD_USER, + SSHD_PASSWORD); + r = ssh_bind_accept(sshbind, session); + if(r==SSH_ERROR){ + printf("Error accepting a connection: %s\n", ssh_get_error(sshbind)); + return 1; + } + if (ssh_handle_key_exchange(session)) { + printf("ssh_handle_key_exchange: %s\n", ssh_get_error(session)); + return 1; + } + + /* proceed to authentication */ + auth = authenticate(session); + if (!auth || !authenticated) { + printf("Authentication error: %s\n", ssh_get_error(session)); + ssh_disconnect(session); + return 1; + } + + + /* wait for a channel session */ + do { + message = ssh_message_get(session); + if(message){ + if(ssh_message_type(message) == SSH_REQUEST_CHANNEL_OPEN && + ssh_message_subtype(message) == SSH_CHANNEL_SESSION) { + chan = ssh_message_channel_request_open_reply_accept(message); + ssh_message_free(message); + break; + } else { + ssh_message_reply_default(message); + ssh_message_free(message); + } + } else { + break; + } + } while(!chan); + + if(!chan) { + printf("Error: cleint did not ask for a channel session (%s)\n", + ssh_get_error(session)); + ssh_finalize(); + return 1; + } + + + /* wait for a shell */ + do { + message = ssh_message_get(session); + if(message != NULL) { + if(ssh_message_type(message) == SSH_REQUEST_CHANNEL && + ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_SHELL) { + shell = 1; + ssh_message_channel_request_reply_success(message); + ssh_message_free(message); + break; + } + ssh_message_reply_default(message); + ssh_message_free(message); + } else { + break; + } + } while(!shell); + + if(!shell) { + printf("Error: No shell requested (%s)\n", ssh_get_error(session)); + return 1; + } + + + printf("it works !\n"); + do{ + i=ssh_channel_read(chan,buf, 2048, 0); + if(i>0) { + if(*buf == '' || *buf == '') + break; + if(i == 1 && *buf == '\r') + ssh_channel_write(chan, "\r\n", 2); + else + ssh_channel_write(chan, buf, i); + if (write(1,buf,i) < 0) { + printf("error writing to buffer\n"); + return 1; + } + } + } while (i>0); + ssh_channel_close(chan); + ssh_disconnect(session); + ssh_bind_free(sshbind); +#ifdef WITH_PCAP + cleanup_pcap(); +#endif + ssh_finalize(); + return 0; +} + diff --git a/examples/scp_download.c b/examples/scp_download.c new file mode 100644 index 0000000..85ccf92 --- /dev/null +++ b/examples/scp_download.c @@ -0,0 +1,178 @@ +/* scp_download.c + * Sample implementation of a tiny SCP downloader client + */ + +/* +Copyright 2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. + */ + +#include +#include +#include +#include +#include + +#include +#include "examples_common.h" + +static int verbosity = 0; +static const char *createcommand = + "rm -fr /tmp/libssh_tests && mkdir /tmp/libssh_tests && " + "cd /tmp/libssh_tests && date > a && date > b && mkdir c && date > d"; +static char *host = NULL; + +static void usage(const char *argv0){ + fprintf(stderr,"Usage : %s [options] host\n" + "sample tiny scp downloader client - libssh-%s\n" + "This program will create files in /tmp and try to fetch them\n", +// "Options :\n", +// " -r : use RSA to verify host public key\n", + argv0, + ssh_version(0)); + exit(0); +} + +static int opts(int argc, char **argv){ + int i; + while((i=getopt(argc,argv,"v"))!=-1){ + switch(i){ + case 'v': + verbosity++; + break; + default: + fprintf(stderr,"unknown option %c\n",optopt); + usage(argv[0]); + return -1; + } + } + host = argv[optind]; + if(host == NULL) + usage(argv[0]); + return 0; +} + +static void create_files(ssh_session session){ + ssh_channel channel=ssh_channel_new(session); + char buffer[1]; + int rc; + + if(channel == NULL){ + fprintf(stderr,"Error creating channel: %s\n",ssh_get_error(session)); + exit(EXIT_FAILURE); + } + if(ssh_channel_open_session(channel) != SSH_OK){ + fprintf(stderr,"Error creating channel: %s\n",ssh_get_error(session)); + ssh_channel_free(channel); + exit(EXIT_FAILURE); + } + if(ssh_channel_request_exec(channel,createcommand) != SSH_OK){ + fprintf(stderr,"Error executing command: %s\n",ssh_get_error(session)); + ssh_channel_close(channel); + ssh_channel_free(channel); + exit(EXIT_FAILURE); + } + while(!ssh_channel_is_eof(channel)){ + rc = ssh_channel_read(channel,buffer,1,1); + if (rc != 1) { + fprintf(stderr, "Error reading from channel\n"); + ssh_channel_close(channel); + ssh_channel_free(channel); + return; + } + + rc = write(1, buffer, 1); + if (rc < 0) { + fprintf(stderr, "Error writing to buffer\n"); + ssh_channel_close(channel); + ssh_channel_free(channel); + return; + } + } + ssh_channel_close(channel); + ssh_channel_free(channel); +} + + +static int fetch_files(ssh_session session){ + int size; + char buffer[16384]; + int mode; + char *filename; + int r; + ssh_scp scp=ssh_scp_new(session, SSH_SCP_READ | SSH_SCP_RECURSIVE, "/tmp/libssh_tests/*"); + if(ssh_scp_init(scp) != SSH_OK){ + fprintf(stderr,"error initializing scp: %s\n",ssh_get_error(session)); + ssh_scp_free(scp); + return -1; + } + printf("Trying to download 3 files (a,b,d) and 1 directory (c)\n"); + do { + + r=ssh_scp_pull_request(scp); + switch(r){ + case SSH_SCP_REQUEST_NEWFILE: + size=ssh_scp_request_get_size(scp); + filename=strdup(ssh_scp_request_get_filename(scp)); + mode=ssh_scp_request_get_permissions(scp); + printf("downloading file %s, size %d, perms 0%o\n",filename,size,mode); + free(filename); + ssh_scp_accept_request(scp); + r=ssh_scp_read(scp,buffer,sizeof(buffer)); + if(r==SSH_ERROR){ + fprintf(stderr,"Error reading scp: %s\n",ssh_get_error(session)); + ssh_scp_close(scp); + ssh_scp_free(scp); + return -1; + } + printf("done\n"); + break; + case SSH_ERROR: + fprintf(stderr,"Error: %s\n",ssh_get_error(session)); + ssh_scp_close(scp); + ssh_scp_free(scp); + return -1; + case SSH_SCP_REQUEST_WARNING: + fprintf(stderr,"Warning: %s\n",ssh_scp_request_get_warning(scp)); + break; + case SSH_SCP_REQUEST_NEWDIR: + filename=strdup(ssh_scp_request_get_filename(scp)); + mode=ssh_scp_request_get_permissions(scp); + printf("downloading directory %s, perms 0%o\n",filename,mode); + free(filename); + ssh_scp_accept_request(scp); + break; + case SSH_SCP_REQUEST_ENDDIR: + printf("End of directory\n"); + break; + case SSH_SCP_REQUEST_EOF: + printf("End of requests\n"); + goto end; + } + } while (1); + end: + ssh_scp_close(scp); + ssh_scp_free(scp); + return 0; +} + +int main(int argc, char **argv){ + ssh_session session; + if(opts(argc,argv)<0) + return EXIT_FAILURE; + session=connect_ssh(host,NULL,verbosity); + if(session == NULL) + return EXIT_FAILURE; + create_files(session); + fetch_files(session); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); + return 0; +} diff --git a/examples/senddata.c b/examples/senddata.c new file mode 100644 index 0000000..042b462 --- /dev/null +++ b/examples/senddata.c @@ -0,0 +1,64 @@ +#include + +#include +#include "examples_common.h" + +#define LIMIT 0x100000000UL + +int main(void) { + ssh_session session; + ssh_channel channel; + char buffer[1024*1024]; + int rc; + uint64_t total=0; + uint64_t lastshown=4096; + session = connect_ssh("localhost", NULL, 0); + if (session == NULL) { + return 1; + } + + channel = ssh_channel_new(session);; + if (channel == NULL) { + ssh_disconnect(session); + return 1; + } + + rc = ssh_channel_open_session(channel); + if (rc < 0) { + ssh_channel_close(channel); + ssh_disconnect(session); + return 1; + } + + rc = ssh_channel_request_exec(channel, "cat > /dev/null"); + if (rc < 0) { + ssh_channel_close(channel); + ssh_disconnect(session); + return 1; + } + + + while ((rc = ssh_channel_write(channel, buffer, sizeof(buffer))) > 0) { + total += rc; + if(total/2 >= lastshown){ + printf("written %llx\n", (long long unsigned int) total); + lastshown=total; + } + if(total > LIMIT) + break; + } + + if (rc < 0) { + printf("error : %s\n",ssh_get_error(session)); + ssh_channel_close(channel); + ssh_disconnect(session); + return 1; + } + + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + + ssh_disconnect(session); + + return 0; +} diff --git a/examples/ssh_client.c b/examples/ssh_client.c new file mode 100644 index 0000000..54ecd30 --- /dev/null +++ b/examples/ssh_client.c @@ -0,0 +1,425 @@ +/* ssh_client.c */ + +/* + * Copyright 2003-2015 Aris Adamantiadis + * + * This file is part of the SSH Library + * + * You are free to copy this file, modify it in any way, consider it being public + * domain. This does not apply to the rest of the library though, but it is + * allowed to cut-and-paste working code from this file to any license of + * program. + * The goal is to show the API in action. It's not a reference on how terminal + * clients must be made or how a client should react. + */ + +#include "config.h" +#include +#include +#include + +#include +#include + +#ifdef HAVE_TERMIOS_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_PTY_H +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include + + +#include "examples_common.h" +#define MAXCMD 10 + +static char *host; +static char *user; +static char *cmds[MAXCMD]; +static struct termios terminal; + +static char *pcap_file = NULL; + +static char *proxycommand; + +static int auth_callback(const char *prompt, + char *buf, + size_t len, + int echo, + int verify, + void *userdata) +{ + (void) verify; + (void) userdata; + + return ssh_getpass(prompt, buf, len, echo, verify); +} + +struct ssh_callbacks_struct cb = { + .auth_function = auth_callback, + .userdata = NULL, +}; + +static void add_cmd(char *cmd) +{ + int n; + + for (n = 0; (n < MAXCMD) && cmds[n] != NULL; n++); + + if (n == MAXCMD) { + return; + } + + cmds[n] = strdup(cmd); +} + +static void usage(void) +{ + fprintf(stderr, + "Usage : ssh [options] [login@]hostname\n" + "sample client - libssh-%s\n" + "Options :\n" + " -l user : log in as user\n" + " -p port : connect to port\n" + " -d : use DSS to verify host public key\n" + " -r : use RSA to verify host public key\n" +#ifdef WITH_PCAP + " -P file : create a pcap debugging file\n" +#endif +#ifndef _WIN32 + " -T proxycommand : command to execute as a socket proxy\n" +#endif + "\n", + ssh_version(0)); + + exit(0); +} + +static int opts(int argc, char **argv) +{ + int i; + + while((i = getopt(argc,argv,"T:P:")) != -1) { + switch(i){ + case 'P': + pcap_file = optarg; + break; +#ifndef _WIN32 + case 'T': + proxycommand = optarg; + break; +#endif + default: + fprintf(stderr, "Unknown option %c\n", optopt); + usage(); + } + } + if (optind < argc) { + host = argv[optind++]; + } + + while(optind < argc) { + add_cmd(argv[optind++]); + } + + if (host == NULL) { + usage(); + } + + return 0; +} + +#ifndef HAVE_CFMAKERAW +static void cfmakeraw(struct termios *termios_p) +{ + termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); + termios_p->c_oflag &= ~OPOST; + termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); + termios_p->c_cflag &= ~(CSIZE|PARENB); + termios_p->c_cflag |= CS8; +} +#endif + + +static void do_cleanup(int i) +{ + /* unused variable */ + (void) i; + + tcsetattr(0, TCSANOW, &terminal); +} + +static void do_exit(int i) +{ + /* unused variable */ + (void) i; + + do_cleanup(0); + exit(0); +} + +static ssh_channel chan; +static int signal_delayed = 0; + +static void sigwindowchanged(int i) +{ + (void) i; + signal_delayed = 1; +} + +static void setsignal(void) +{ + signal(SIGWINCH, sigwindowchanged); + signal_delayed = 0; +} + +static void sizechanged(void) +{ + struct winsize win = { + .ws_row = 0, + }; + + ioctl(1, TIOCGWINSZ, &win); + ssh_channel_change_pty_size(chan,win.ws_col, win.ws_row); + setsignal(); +} + +static void select_loop(ssh_session session,ssh_channel channel) +{ + ssh_connector connector_in, connector_out, connector_err; + int rc; + + ssh_event event = ssh_event_new(); + + /* stdin */ + connector_in = ssh_connector_new(session); + ssh_connector_set_out_channel(connector_in, channel, SSH_CONNECTOR_STDINOUT); + ssh_connector_set_in_fd(connector_in, 0); + ssh_event_add_connector(event, connector_in); + + /* stdout */ + connector_out = ssh_connector_new(session); + ssh_connector_set_out_fd(connector_out, 1); + ssh_connector_set_in_channel(connector_out, channel, SSH_CONNECTOR_STDINOUT); + ssh_event_add_connector(event, connector_out); + + /* stderr */ + connector_err = ssh_connector_new(session); + ssh_connector_set_out_fd(connector_err, 2); + ssh_connector_set_in_channel(connector_err, channel, SSH_CONNECTOR_STDERR); + ssh_event_add_connector(event, connector_err); + + while (ssh_channel_is_open(channel)) { + if (signal_delayed) { + sizechanged(); + } + rc = ssh_event_dopoll(event, 60000); + if (rc == SSH_ERROR) { + fprintf(stderr, "Error in ssh_event_dopoll()\n"); + break; + } + } + ssh_event_remove_connector(event, connector_in); + ssh_event_remove_connector(event, connector_out); + ssh_event_remove_connector(event, connector_err); + + ssh_connector_free(connector_in); + ssh_connector_free(connector_out); + ssh_connector_free(connector_err); + + ssh_event_free(event); +} + +static void shell(ssh_session session) +{ + ssh_channel channel; + struct termios terminal_local; + int interactive=isatty(0); + + channel = ssh_channel_new(session); + if (channel == NULL) { + return; + } + + if (interactive) { + tcgetattr(0, &terminal_local); + memcpy(&terminal, &terminal_local, sizeof(struct termios)); + } + + if (ssh_channel_open_session(channel)) { + printf("Error opening channel : %s\n", ssh_get_error(session)); + ssh_channel_free(channel); + return; + } + chan = channel; + if (interactive) { + ssh_channel_request_pty(channel); + sizechanged(); + } + + if (ssh_channel_request_shell(channel)) { + printf("Requesting shell : %s\n", ssh_get_error(session)); + ssh_channel_free(channel); + return; + } + + if (interactive) { + cfmakeraw(&terminal_local); + tcsetattr(0, TCSANOW, &terminal_local); + setsignal(); + } + signal(SIGTERM, do_cleanup); + select_loop(session, channel); + if (interactive) { + do_cleanup(0); + } + ssh_channel_free(channel); +} + +static void batch_shell(ssh_session session) +{ + ssh_channel channel; + char buffer[1024]; + size_t i; + int s = 0; + + for (i = 0; i < MAXCMD && cmds[i]; ++i) { + s += snprintf(buffer + s, sizeof(buffer) - s, "%s ", cmds[i]); + free(cmds[i]); + cmds[i] = NULL; + } + + channel = ssh_channel_new(session); + if (channel == NULL) { + return; + } + + ssh_channel_open_session(channel); + if (ssh_channel_request_exec(channel, buffer)) { + printf("Error executing '%s' : %s\n", buffer, ssh_get_error(session)); + ssh_channel_free(channel); + return; + } + select_loop(session, channel); + ssh_channel_free(channel); +} + +static int client(ssh_session session) +{ + int auth = 0; + char *banner; + int state; + + if (user) { + if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) { + return -1; + } + } + if (ssh_options_set(session, SSH_OPTIONS_HOST ,host) < 0) { + return -1; + } + if (proxycommand != NULL) { + if (ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, proxycommand)) { + return -1; + } + } + ssh_options_parse_config(session, NULL); + + if (ssh_connect(session)) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + return -1; + } + + state = verify_knownhost(session); + if (state != 0) { + return -1; + } + + ssh_userauth_none(session, NULL); + banner = ssh_get_issue_banner(session); + if (banner) { + printf("%s\n", banner); + free(banner); + } + auth = authenticate_console(session); + if (auth != SSH_AUTH_SUCCESS) { + return -1; + } + if (cmds[0] == NULL) { + shell(session); + } else { + batch_shell(session); + } + + return 0; +} + +static ssh_pcap_file pcap; +static void set_pcap(ssh_session session) +{ + if (pcap_file == NULL) { + return; + } + + pcap = ssh_pcap_file_new(); + if (pcap == NULL) { + return; + } + + if (ssh_pcap_file_open(pcap, pcap_file) == SSH_ERROR) { + printf("Error opening pcap file\n"); + ssh_pcap_file_free(pcap); + pcap = NULL; + return; + } + ssh_set_pcap_file(session, pcap); +} + +static void cleanup_pcap(void) +{ + if (pcap != NULL) { + ssh_pcap_file_free(pcap); + } + pcap = NULL; +} + +int main(int argc, char **argv) +{ + ssh_session session; + + session = ssh_new(); + + ssh_callbacks_init(&cb); + ssh_set_callbacks(session,&cb); + + if (ssh_options_getopt(session, &argc, argv)) { + fprintf(stderr, + "Error parsing command line: %s\n", + ssh_get_error(session)); + usage(); + } + opts(argc, argv); + signal(SIGTERM, do_exit); + + set_pcap(session); + client(session); + + ssh_disconnect(session); + ssh_free(session); + cleanup_pcap(); + + ssh_finalize(); + + return 0; +} diff --git a/examples/ssh_server_fork.c b/examples/ssh_server_fork.c new file mode 100644 index 0000000..c9892d8 --- /dev/null +++ b/examples/ssh_server_fork.c @@ -0,0 +1,775 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2014 Audrius Butkevicius + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. +*/ + +#include "config.h" + +#include +#include + +#include +#ifdef HAVE_ARGP_H +#include +#endif +#include +#ifdef HAVE_LIBUTIL_H +#include +#endif +#ifdef HAVE_PTY_H +#include +#endif +#include +#include +#ifdef HAVE_UTMP_H +#include +#endif +#ifdef HAVE_UTIL_H +#include +#endif +#include +#include +#include +#include + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#define USER "myuser" +#define PASS "mypassword" +#define BUF_SIZE 1048576 +#define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) +#define SFTP_SERVER_PATH "/usr/lib/sftp-server" + +static void set_default_keys(ssh_bind sshbind, + int rsa_already_set, + int dsa_already_set, + int ecdsa_already_set) { + if (!rsa_already_set) { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, + KEYS_FOLDER "ssh_host_rsa_key"); + } + if (!dsa_already_set) { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, + KEYS_FOLDER "ssh_host_dsa_key"); + } + if (!ecdsa_already_set) { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_ECDSAKEY, + KEYS_FOLDER "ssh_host_ecdsa_key"); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, + KEYS_FOLDER "ssh_host_ed25519_key"); +} +#define DEF_STR_SIZE 1024 +char authorizedkeys[DEF_STR_SIZE] = {0}; +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh server example " +SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set a host key. Can be used multiple times. " + "Implies no default keys.", + .group = 0 + }, + { + .name = "dsakey", + .key = 'd', + .arg = "FILE", + .flags = 0, + .doc = "Set the dsa key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key.", + .group = 0 + }, + { + .name = "ecdsakey", + .key = 'e', + .arg = "FILE", + .flags = 0, + .doc = "Set the ecdsa key.", + .group = 0 + }, + { + .name = "authorizedkeys", + .key = 'a', + .arg = "FILE", + .flags = 0, + .doc = "Set the authorized keys file.", + .group = 0 + }, + { + .name = "no-default-keys", + .key = 'n', + .arg = NULL, + .flags = 0, + .doc = "Do not set default key locations.", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. */ + ssh_bind sshbind = state->input; + static int no_default_keys = 0; + static int rsa_already_set = 0, dsa_already_set = 0, ecdsa_already_set = 0; + + switch (key) { + case 'n': + no_default_keys = 1; + break; + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + break; + case 'd': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, arg); + dsa_already_set = 1; + break; + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + /* We can't track the types of keys being added with this + option, so let's ensure we keep the keys we're adding + by just not setting the default keys */ + no_default_keys = 1; + break; + case 'r': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, arg); + rsa_already_set = 1; + break; + case 'e': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_ECDSAKEY, arg); + ecdsa_already_set = 1; + break; + case 'a': + strncpy(authorizedkeys, arg, DEF_STR_SIZE-1); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, + "3"); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage (state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage (state); + } + + if (!no_default_keys) { + set_default_keys(sshbind, + rsa_already_set, + dsa_already_set, + ecdsa_already_set); + } + + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +/* A userdata struct for channel. */ +struct channel_data_struct { + /* pid of the child process the channel will spawn. */ + pid_t pid; + /* For PTY allocation */ + socket_t pty_master; + socket_t pty_slave; + /* For communication with the child process. */ + socket_t child_stdin; + socket_t child_stdout; + /* Only used for subsystem and exec requests. */ + socket_t child_stderr; + /* Event which is used to poll the above descriptors. */ + ssh_event event; + /* Terminal size struct. */ + struct winsize *winsize; +}; + +/* A userdata struct for session. */ +struct session_data_struct { + /* Pointer to the channel the session will allocate. */ + ssh_channel channel; + int auth_attempts; + int authenticated; +}; + +static int data_function(ssh_session session, ssh_channel channel, void *data, + uint32_t len, int is_stderr, void *userdata) { + struct channel_data_struct *cdata = (struct channel_data_struct *) userdata; + + (void) session; + (void) channel; + (void) is_stderr; + + if (len == 0 || cdata->pid < 1 || kill(cdata->pid, 0) < 0) { + return 0; + } + + return write(cdata->child_stdin, (char *) data, len); +} + +static int pty_request(ssh_session session, ssh_channel channel, + const char *term, int cols, int rows, int py, int px, + void *userdata) { + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + + (void) session; + (void) channel; + (void) term; + + cdata->winsize->ws_row = rows; + cdata->winsize->ws_col = cols; + cdata->winsize->ws_xpixel = px; + cdata->winsize->ws_ypixel = py; + + if (openpty(&cdata->pty_master, &cdata->pty_slave, NULL, NULL, + cdata->winsize) != 0) { + fprintf(stderr, "Failed to open pty\n"); + return SSH_ERROR; + } + return SSH_OK; +} + +static int pty_resize(ssh_session session, ssh_channel channel, int cols, + int rows, int py, int px, void *userdata) { + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + + (void) session; + (void) channel; + + cdata->winsize->ws_row = rows; + cdata->winsize->ws_col = cols; + cdata->winsize->ws_xpixel = px; + cdata->winsize->ws_ypixel = py; + + if (cdata->pty_master != -1) { + return ioctl(cdata->pty_master, TIOCSWINSZ, cdata->winsize); + } + + return SSH_ERROR; +} + +static int exec_pty(const char *mode, const char *command, + struct channel_data_struct *cdata) { + switch(cdata->pid = fork()) { + case -1: + close(cdata->pty_master); + close(cdata->pty_slave); + fprintf(stderr, "Failed to fork\n"); + return SSH_ERROR; + case 0: + close(cdata->pty_master); + if (login_tty(cdata->pty_slave) != 0) { + exit(1); + } + execl("/bin/sh", "sh", mode, command, NULL); + exit(0); + default: + close(cdata->pty_slave); + /* pty fd is bi-directional */ + cdata->child_stdout = cdata->child_stdin = cdata->pty_master; + } + return SSH_OK; +} + +static int exec_nopty(const char *command, struct channel_data_struct *cdata) { + int in[2], out[2], err[2]; + + /* Do the plumbing to be able to talk with the child process. */ + if (pipe(in) != 0) { + goto stdin_failed; + } + if (pipe(out) != 0) { + goto stdout_failed; + } + if (pipe(err) != 0) { + goto stderr_failed; + } + + switch(cdata->pid = fork()) { + case -1: + goto fork_failed; + case 0: + /* Finish the plumbing in the child process. */ + close(in[1]); + close(out[0]); + close(err[0]); + dup2(in[0], STDIN_FILENO); + dup2(out[1], STDOUT_FILENO); + dup2(err[1], STDERR_FILENO); + close(in[0]); + close(out[1]); + close(err[1]); + /* exec the requested command. */ + execl("/bin/sh", "sh", "-c", command, NULL); + exit(0); + } + + close(in[0]); + close(out[1]); + close(err[1]); + + cdata->child_stdin = in[1]; + cdata->child_stdout = out[0]; + cdata->child_stderr = err[0]; + + return SSH_OK; + +fork_failed: + close(err[0]); + close(err[1]); +stderr_failed: + close(out[0]); + close(out[1]); +stdout_failed: + close(in[0]); + close(in[1]); +stdin_failed: + return SSH_ERROR; +} + +static int exec_request(ssh_session session, ssh_channel channel, + const char *command, void *userdata) { + struct channel_data_struct *cdata = (struct channel_data_struct *) userdata; + + + (void) session; + (void) channel; + + if(cdata->pid > 0) { + return SSH_ERROR; + } + + if (cdata->pty_master != -1 && cdata->pty_slave != -1) { + return exec_pty("-c", command, cdata); + } + return exec_nopty(command, cdata); +} + +static int shell_request(ssh_session session, ssh_channel channel, + void *userdata) { + struct channel_data_struct *cdata = (struct channel_data_struct *) userdata; + + (void) session; + (void) channel; + + if(cdata->pid > 0) { + return SSH_ERROR; + } + + if (cdata->pty_master != -1 && cdata->pty_slave != -1) { + return exec_pty("-l", NULL, cdata); + } + /* Client requested a shell without a pty, let's pretend we allow that */ + return SSH_OK; +} + +static int subsystem_request(ssh_session session, ssh_channel channel, + const char *subsystem, void *userdata) { + /* subsystem requests behave simillarly to exec requests. */ + if (strcmp(subsystem, "sftp") == 0) { + return exec_request(session, channel, SFTP_SERVER_PATH, userdata); + } + return SSH_ERROR; +} + +static int auth_password(ssh_session session, const char *user, + const char *pass, void *userdata) { + struct session_data_struct *sdata = (struct session_data_struct *) userdata; + + (void) session; + + if (strcmp(user, USER) == 0 && strcmp(pass, PASS) == 0) { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + + sdata->auth_attempts++; + return SSH_AUTH_DENIED; +} + +static int auth_publickey(ssh_session session, + const char *user, + struct ssh_key_struct *pubkey, + char signature_state, + void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *) userdata; + + (void) user; + (void) session; + + if (signature_state == SSH_PUBLICKEY_STATE_NONE) { + return SSH_AUTH_SUCCESS; + } + + if (signature_state != SSH_PUBLICKEY_STATE_VALID) { + return SSH_AUTH_DENIED; + } + + // valid so far. Now look through authorized keys for a match + if (authorizedkeys[0]) { + ssh_key key = NULL; + int result; + struct stat buf; + + if (stat(authorizedkeys, &buf) == 0) { + result = ssh_pki_import_pubkey_file( authorizedkeys, &key ); + if ((result != SSH_OK) || (key==NULL)) { + fprintf(stderr, + "Unable to import public key file %s\n", + authorizedkeys); + } else { + result = ssh_key_cmp( key, pubkey, SSH_KEY_CMP_PUBLIC ); + ssh_key_free(key); + if (result == 0) { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + } + } + } + + // no matches + sdata->authenticated = 0; + return SSH_AUTH_DENIED; +} + +static ssh_channel channel_open(ssh_session session, void *userdata) { + struct session_data_struct *sdata = (struct session_data_struct *) userdata; + + sdata->channel = ssh_channel_new(session); + return sdata->channel; +} + +static int process_stdout(socket_t fd, int revents, void *userdata) { + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write(channel, buf, n); + } + } + + return n; +} + +static int process_stderr(socket_t fd, int revents, void *userdata) { + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write_stderr(channel, buf, n); + } + } + + return n; +} + +static void handle_session(ssh_event event, ssh_session session) { + int n; + int rc = 0; + + /* Structure for storing the pty size. */ + struct winsize wsize = { + .ws_row = 0, + .ws_col = 0, + .ws_xpixel = 0, + .ws_ypixel = 0 + }; + + /* Our struct holding information about the channel. */ + struct channel_data_struct cdata = { + .pid = 0, + .pty_master = -1, + .pty_slave = -1, + .child_stdin = -1, + .child_stdout = -1, + .child_stderr = -1, + .event = NULL, + .winsize = &wsize + }; + + /* Our struct holding information about the session. */ + struct session_data_struct sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = 0 + }; + + struct ssh_channel_callbacks_struct channel_cb = { + .userdata = &cdata, + .channel_pty_request_function = pty_request, + .channel_pty_window_change_function = pty_resize, + .channel_shell_request_function = shell_request, + .channel_exec_request_function = exec_request, + .channel_data_function = data_function, + .channel_subsystem_request_function = subsystem_request + }; + + struct ssh_server_callbacks_struct server_cb = { + .userdata = &sdata, + .auth_password_function = auth_password, + .channel_open_request_session_function = channel_open, + }; + + if (authorizedkeys[0]) { + server_cb.auth_pubkey_function = auth_publickey; + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY); + } else + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD); + + ssh_callbacks_init(&server_cb); + ssh_callbacks_init(&channel_cb); + + ssh_set_server_callbacks(session, &server_cb); + + if (ssh_handle_key_exchange(session) != SSH_OK) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + return; + } + + ssh_event_add_session(event, session); + + n = 0; + while (sdata.authenticated == 0 || sdata.channel == NULL) { + /* If the user has used up all attempts, or if he hasn't been able to + * authenticate in 10 seconds (n * 100ms), disconnect. */ + if (sdata.auth_attempts >= 3 || n >= 100) { + return; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + return; + } + n++; + } + + ssh_set_channel_callbacks(sdata.channel, &channel_cb); + + do { + /* Poll the main event which takes care of the session, the channel and + * even our child process's stdout/stderr (once it's started). */ + if (ssh_event_dopoll(event, -1) == SSH_ERROR) { + ssh_channel_close(sdata.channel); + } + + /* If child process's stdout/stderr has been registered with the event, + * or the child process hasn't started yet, continue. */ + if (cdata.event != NULL || cdata.pid == 0) { + continue; + } + /* Executed only once, once the child process starts. */ + cdata.event = event; + /* If stdout valid, add stdout to be monitored by the poll event. */ + if (cdata.child_stdout != -1) { + if (ssh_event_add_fd(event, cdata.child_stdout, POLLIN, process_stdout, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stdout to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + + /* If stderr valid, add stderr to be monitored by the poll event. */ + if (cdata.child_stderr != -1){ + if (ssh_event_add_fd(event, cdata.child_stderr, POLLIN, process_stderr, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stderr to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + } while(ssh_channel_is_open(sdata.channel) && + (cdata.pid == 0 || waitpid(cdata.pid, &rc, WNOHANG) == 0)); + + close(cdata.pty_master); + close(cdata.child_stdin); + close(cdata.child_stdout); + close(cdata.child_stderr); + + /* Remove the descriptors from the polling context, since they are now + * closed, they will always trigger during the poll calls. */ + ssh_event_remove_fd(event, cdata.child_stdout); + ssh_event_remove_fd(event, cdata.child_stderr); + + /* If the child process exited. */ + if (kill(cdata.pid, 0) < 0 && WIFEXITED(rc)) { + rc = WEXITSTATUS(rc); + ssh_channel_request_send_exit_status(sdata.channel, rc); + /* If client terminated the channel or the process did not exit nicely, + * but only if something has been forked. */ + } else if (cdata.pid > 0) { + kill(cdata.pid, SIGKILL); + } + + ssh_channel_send_eof(sdata.channel); + ssh_channel_close(sdata.channel); + + /* Wait up to 5 seconds for the client to terminate the session. */ + for (n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) { + ssh_event_dopoll(event, 100); + } +} + +/* SIGCHLD handler for cleaning up dead children. */ +static void sigchld_handler(int signo) { + (void) signo; + while (waitpid(-1, NULL, WNOHANG) > 0); +} + +int main(int argc, char **argv) { + ssh_bind sshbind; + ssh_session session; + ssh_event event; + struct sigaction sa; + int rc; + + /* Set up SIGCHLD handler. */ + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + if (sigaction(SIGCHLD, &sa, NULL) != 0) { + fprintf(stderr, "Failed to register SIGCHLD handler\n"); + return 1; + } + + rc = ssh_init(); + if (rc < 0) { + fprintf(stderr, "ssh_init failed\n"); + return 1; + } + + sshbind = ssh_bind_new(); + if (sshbind == NULL) { + fprintf(stderr, "ssh_bind_new failed\n"); + return 1; + } + +#ifdef HAVE_ARGP_H + argp_parse(&argp, argc, argv, 0, 0, sshbind); +#else + (void) argc; + (void) argv; + + set_default_keys(sshbind, 0, 0, 0); +#endif /* HAVE_ARGP_H */ + + if(ssh_bind_listen(sshbind) < 0) { + fprintf(stderr, "%s\n", ssh_get_error(sshbind)); + return 1; + } + + while (1) { + session = ssh_new(); + if (session == NULL) { + fprintf(stderr, "Failed to allocate session\n"); + continue; + } + + /* Blocks until there is a new incoming connection. */ + if(ssh_bind_accept(sshbind, session) != SSH_ERROR) { + switch(fork()) { + case 0: + /* Remove the SIGCHLD handler inherited from parent. */ + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + /* Remove socket binding, which allows us to restart the + * parent process, without terminating existing sessions. */ + ssh_bind_free(sshbind); + + event = ssh_event_new(); + if (event != NULL) { + /* Blocks until the SSH session ends by either + * child process exiting, or client disconnecting. */ + handle_session(event, session); + ssh_event_free(event); + } else { + fprintf(stderr, "Could not create polling context\n"); + } + ssh_disconnect(session); + ssh_free(session); + + exit(0); + case -1: + fprintf(stderr, "Failed to fork\n"); + } + } else { + fprintf(stderr, "%s\n", ssh_get_error(sshbind)); + } + /* Since the session has been passed to a child fork, do some cleaning + * up at the parent process. */ + ssh_disconnect(session); + ssh_free(session); + } + + ssh_bind_free(sshbind); + ssh_finalize(); + return 0; +} diff --git a/examples/sshd_direct-tcpip.c b/examples/sshd_direct-tcpip.c new file mode 100644 index 0000000..36a15a5 --- /dev/null +++ b/examples/sshd_direct-tcpip.c @@ -0,0 +1,681 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2003-2009 Aris Adamantiadis +Copyright 2018 T. Wimmer + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +/* + Example: + ./sshd_direct-tcpip -v -p 2022 -d serverkey.dsa -r serverkey.rsa 127.0.0.1 +*/ + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif +#include +#include +#include +#include +#include + +#define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x=NULL;} } while(0) + +#ifndef __unused__ +# ifdef HAVE_UNUSED_ATTRIBUTE +# define __unused__ __attribute__((unused)) +# else /* HAVE_UNUSED_ATTRIBUTE */ +# define __unused__ +# endif /* HAVE_UNUSED_ATTRIBUTE */ +#endif /* __unused__ */ + +#ifndef UNUSED_PARAM +#define UNUSED_PARAM(param) param __unused__ +#endif /* UNUSED_PARAM */ + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#define USER "user" +#define PASSWORD "pwd" + +struct event_fd_data_struct { + int *p_fd; + ssh_channel channel; + struct ssh_channel_callbacks_struct *cb_chan; + int stacked; +}; + +struct cleanup_node_struct { + struct event_fd_data_struct *data; + struct cleanup_node_struct *next; +}; + +static bool authenticated = false; +static int tries = 0; +static bool error_set = false; +static int sockets_cnt = 0; +static ssh_event mainloop = NULL; +static struct cleanup_node_struct *cleanup_stack = NULL; + +static void _close_socket(struct event_fd_data_struct event_fd_data); + +static void cleanup_push(struct cleanup_node_struct** head_ref, struct event_fd_data_struct *new_data) { + // Allocate memory for node + struct cleanup_node_struct *new_node = malloc(sizeof *new_node); + + new_node->next = (*head_ref); + + // Copy new_data + new_node->data = new_data; + + // Change head pointer as new node is added at the beginning + (*head_ref) = new_node; +} + +static void do_cleanup(struct cleanup_node_struct **head_ref) { + struct cleanup_node_struct *current = (*head_ref); + struct cleanup_node_struct *previous = NULL, *gone = NULL; + + while (current != NULL) { + if (ssh_channel_is_closed(current->data->channel)) { + if (current == (*head_ref)) { + (*head_ref) = current->next; + } + if (previous != NULL) { + previous->next = current->next; + } + gone = current; + current = current->next; + + if (gone->data->channel) { + _close_socket(*gone->data); + ssh_remove_channel_callbacks(gone->data->channel, gone->data->cb_chan); + ssh_channel_free(gone->data->channel); + gone->data->channel = NULL; + + SAFE_FREE(gone->data->p_fd); + SAFE_FREE(gone->data->cb_chan); + SAFE_FREE(gone->data); + SAFE_FREE(gone); + } + else { + fprintf(stderr, "channel already freed!\n"); + } + _ssh_log(SSH_LOG_FUNCTIONS, "=== do_cleanup", "Freed."); + } + else { + ssh_channel_close(current->data->channel); + previous = current; + current = current->next; + } + } +} + +static int auth_password(ssh_session session, const char *user, + const char *password, void *userdata) { + (void)userdata; + _ssh_log(SSH_LOG_PROTOCOL, "=== auth_password", "Authenticating user %s pwd %s",user, password); + if (strcmp(user,USER) == 0 && strcmp(password, PASSWORD) == 0){ + authenticated = true; + printf("Authenticated\n"); + return SSH_AUTH_SUCCESS; + } + if (tries >= 3){ + printf("Too many authentication tries\n"); + ssh_disconnect(session); + error_set = true; + return SSH_AUTH_DENIED; + } + tries++; + return SSH_AUTH_DENIED; +} + +static int auth_gssapi_mic(ssh_session session, const char *user, const char *principal, void *userdata) { + ssh_gssapi_creds creds = ssh_gssapi_get_creds(session); + (void)userdata; + printf("Authenticating user %s with gssapi principal %s\n", user, principal); + if (creds != NULL) + printf("Received some gssapi credentials\n"); + else + printf("Not received any forwardable creds\n"); + printf("authenticated\n"); + authenticated = true; + return SSH_AUTH_SUCCESS; +} + +static int subsystem_request(ssh_session session, ssh_channel channel, const char *subsystem, void *userdata) { + (void)session; + (void)channel; + //(void)subsystem; + (void)userdata; + _ssh_log(SSH_LOG_PROTOCOL, "=== subsystem_request", "Channel subsystem reqeuest: %s", subsystem); + return 0; +} + +struct ssh_channel_callbacks_struct channel_cb = { + .channel_subsystem_request_function = subsystem_request +}; + +static ssh_channel new_session_channel(ssh_session session, void *userdata) { + (void)session; + (void)userdata; + _ssh_log(SSH_LOG_PROTOCOL, "=== subsystem_request", "Session channel request"); + /* For TCP forward only there seems to be no need for a session channel */ + /*if(chan != NULL) + return NULL; + printf("Session channel request\n"); + chan = ssh_channel_new(session); + ssh_callbacks_init(&channel_cb); + ssh_set_channel_callbacks(chan, &channel_cb); + return chan;*/ + return NULL; +} + +static void stack_socket_close(UNUSED_PARAM(ssh_session session), + struct event_fd_data_struct *event_fd_data) +{ + if (event_fd_data->stacked != 1) { + _ssh_log(SSH_LOG_FUNCTIONS, "=== stack_socket_close", "Closing fd = %d sockets_cnt = %d", *event_fd_data->p_fd, sockets_cnt); + event_fd_data->stacked = 1; + cleanup_push(&cleanup_stack, event_fd_data); + } +} + +static void _close_socket(struct event_fd_data_struct event_fd_data) { + _ssh_log(SSH_LOG_FUNCTIONS, "=== close_socket", "Closing fd = %d sockets_cnt = %d", *event_fd_data.p_fd, sockets_cnt); + ssh_event_remove_fd(mainloop, *event_fd_data.p_fd); + sockets_cnt--; +#ifdef _WIN32 + closesocket(*event_fd_data.p_fd); +#else + close(*event_fd_data.p_fd); +#endif // _WIN32 + (*event_fd_data.p_fd) = SSH_INVALID_SOCKET; +} + +static int service_request(ssh_session session, const char *service, void *userdata) { + (void)session; + //(void)service; + (void)userdata; + _ssh_log(SSH_LOG_PROTOCOL, "=== service_request", "Service request: %s", service); + return 0; +} + +static void global_request(ssh_session session, ssh_message message, void *userdata) { + (void)session; + (void)userdata; + _ssh_log(SSH_LOG_PROTOCOL, "=== global_request", "Global request, message type: %d", ssh_message_type(message)); +} + +static void my_channel_close_function(ssh_session session, ssh_channel channel, void *userdata) { + struct event_fd_data_struct *event_fd_data = (struct event_fd_data_struct *)userdata; + (void)session; + + _ssh_log(SSH_LOG_PROTOCOL, + "=== my_channel_close_function", + "Channel closed by remote."); + + stack_socket_close(session, event_fd_data); +} + +static void my_channel_eof_function(ssh_session session, ssh_channel channel, void *userdata) { + struct event_fd_data_struct *event_fd_data = (struct event_fd_data_struct *)userdata; + (void)session; + + _ssh_log(SSH_LOG_PROTOCOL, + "=== my_channel_eof_function", + "Got EOF on channel. Shuting down write on socket (fd = %d).", + *event_fd_data->p_fd); + + stack_socket_close(session, event_fd_data); +} + +static void my_channel_exit_status_function(ssh_session session, ssh_channel channel, int exit_status, void *userdata) { + struct event_fd_data_struct *event_fd_data = (struct event_fd_data_struct *)userdata; + (void)session; + + _ssh_log(SSH_LOG_PROTOCOL, + "=== my_channel_exit_status_function", + "Got exit status %d on channel fd = %d.", + exit_status, *event_fd_data->p_fd); +} + +static int my_channel_data_function(ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + UNUSED_PARAM(int is_stderr), + void *userdata) +{ + int i = 0; + struct event_fd_data_struct *event_fd_data = (struct event_fd_data_struct *)userdata; + + if (event_fd_data->channel == NULL) { + fprintf(stderr, "Why we're here? Stacked = %d\n", event_fd_data->stacked); + } + + _ssh_log(SSH_LOG_PROTOCOL, + "=== my_channel_data_function", + "%d bytes waiting on channel for reading. Fd = %d", + len, + *event_fd_data->p_fd); + if (len > 0) { + i = send(*event_fd_data->p_fd, data, len, 0); + } + if (i < 0) { + _ssh_log(SSH_LOG_WARNING, "=== my_channel_data_function", "Writing to tcp socket %d: %s", *event_fd_data->p_fd, strerror(errno)); + stack_socket_close(session, event_fd_data); + } + else { + _ssh_log(SSH_LOG_FUNCTIONS, "=== my_channel_data_function", "Sent %d bytes", i); + } + return i; +} + +static int my_fd_data_function(UNUSED_PARAM(socket_t fd), + int revents, + void *userdata) +{ + struct event_fd_data_struct *event_fd_data = (struct event_fd_data_struct *)userdata; + ssh_channel channel = event_fd_data->channel; + ssh_session session; + int len, i, wr; + char buf[16384]; + int blocking; + + if (channel == NULL) { + _ssh_log(SSH_LOG_FUNCTIONS, "=== my_fd_data_function", "channel == NULL!"); + return 0; + } + + session = ssh_channel_get_session(channel); + + if (ssh_channel_is_closed(channel)) { + _ssh_log(SSH_LOG_FUNCTIONS, "=== my_fd_data_function", "channel is closed!"); + stack_socket_close(session, event_fd_data); + return 0; + } + + if (!(revents & POLLIN)) { + if (revents & POLLPRI) { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "poll revents & POLLPRI"); + } + if (revents & POLLOUT) { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "poll revents & POLLOUT"); + } + if (revents & POLLHUP) { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "poll revents & POLLHUP"); + } + if (revents & POLLNVAL) { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "poll revents & POLLNVAL"); + } + if (revents & POLLERR) { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "poll revents & POLLERR"); + } + return 0; + } + + blocking = ssh_is_blocking(session); + ssh_set_blocking(session, 0); + + _ssh_log(SSH_LOG_FUNCTIONS, + "=== my_fd_data_function", + "Trying to read from tcp socket fd = %d", + *event_fd_data->p_fd); +#ifdef _WIN32 + struct sockaddr from; + int fromlen = sizeof(from); + len = recvfrom(*event_fd_data->p_fd, buf, sizeof(buf), 0, &from, &fromlen); +#else + len = recv(*event_fd_data->p_fd, buf, sizeof(buf), 0); +#endif // _WIN32 + if (len < 0) { + _ssh_log(SSH_LOG_WARNING, "=== my_fd_data_function", "Reading from tcp socket: %s", strerror(errno)); + + ssh_channel_send_eof(channel); + } + else if (len > 0) { + if (ssh_channel_is_open(channel)) { + wr = 0; + do { + i = ssh_channel_write(channel, buf, len); + if (i < 0) { + _ssh_log(SSH_LOG_WARNING, "=== my_fd_data_function", "Error writing on the direct-tcpip channel: %d", i); + len = wr; + break; + } + wr += i; + _ssh_log(SSH_LOG_FUNCTIONS, "=== my_fd_data_function", "channel_write (%d from %d)", wr, len); + } while (i > 0 && wr < len); + } + else { + _ssh_log(SSH_LOG_WARNING, "=== my_fd_data_function", "Can't write on closed channel!"); + } + } + else { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "The destination host has disconnected!"); + + ssh_channel_close(channel); +#ifdef _WIN32 + shutdown(*event_fd_data->p_fd, SD_RECEIVE); +#else + shutdown(*event_fd_data->p_fd, SHUT_RD); +#endif // _WIN32 + } + ssh_set_blocking(session, blocking); + + return len; +} + +static int open_tcp_socket(ssh_message msg) { + struct sockaddr_in sin; + int forwardsock = -1; + struct hostent *host; + const char *dest_hostname; + int dest_port; + + forwardsock = socket(AF_INET, SOCK_STREAM, 0); + if (forwardsock < 0) { + _ssh_log(SSH_LOG_WARNING, "=== open_tcp_socket", "ERROR opening socket: %s", strerror(errno)); + return -1; + } + + dest_hostname = ssh_message_channel_request_open_destination(msg); + dest_port = ssh_message_channel_request_open_destination_port(msg); + + _ssh_log(SSH_LOG_PROTOCOL, "=== open_tcp_socket", "Connecting to %s on port %d", dest_hostname, dest_port); + + host = gethostbyname(dest_hostname); + if (host == NULL) { + close(forwardsock); + _ssh_log(SSH_LOG_WARNING, "=== open_tcp_socket", "ERROR, no such host: %s", dest_hostname); + return -1; + } + + memset((char *)&sin, '\0', sizeof(sin)); + sin.sin_family = AF_INET; + memcpy((char *)&sin.sin_addr.s_addr, (char *)host->h_addr, host->h_length); + sin.sin_port = htons(dest_port); + + if (connect(forwardsock, (struct sockaddr *)&sin, sizeof(sin)) < 0) { + close(forwardsock); + _ssh_log(SSH_LOG_WARNING, "=== open_tcp_socket", "ERROR connecting: %s", strerror(errno)); + return -1; + } + + sockets_cnt++; + _ssh_log(SSH_LOG_FUNCTIONS, "=== open_tcp_socket", "Connected. sockets_cnt = %d", sockets_cnt); + return forwardsock; +} + +static int message_callback(ssh_session session, ssh_message message, void *userdata) { + ssh_channel channel; + int socket_fd, *pFd; + struct ssh_channel_callbacks_struct *cb_chan; + struct event_fd_data_struct *event_fd_data; + (void)session; + (void)message; + (void)userdata; + + _ssh_log(SSH_LOG_PACKET, "=== message_callback", "Message type: %d", ssh_message_type(message)); + _ssh_log(SSH_LOG_PACKET, "=== message_callback", "Message Subtype: %d", ssh_message_subtype(message)); + if (ssh_message_type(message) == SSH_REQUEST_CHANNEL_OPEN) { + _ssh_log(SSH_LOG_PROTOCOL, "=== message_callback", "channel_request_open"); + + if (ssh_message_subtype(message) == SSH_CHANNEL_DIRECT_TCPIP) { + channel = ssh_message_channel_request_open_reply_accept(message); + + if (channel == NULL) { + _ssh_log(SSH_LOG_WARNING, "=== message_callback", "Accepting direct-tcpip channel failed!"); + return 1; + } + else { + _ssh_log(SSH_LOG_PROTOCOL, "=== message_callback", "Connected to channel!"); + + socket_fd = open_tcp_socket(message); + if (-1 == socket_fd) { + return 1; + } + + pFd = malloc(sizeof *pFd); + cb_chan = malloc(sizeof *cb_chan); + event_fd_data = malloc(sizeof *event_fd_data); + + (*pFd) = socket_fd; + event_fd_data->channel = channel; + event_fd_data->p_fd = pFd; + event_fd_data->stacked = 0; + event_fd_data->cb_chan = cb_chan; + + cb_chan->userdata = event_fd_data; + cb_chan->channel_eof_function = my_channel_eof_function; + cb_chan->channel_close_function = my_channel_close_function; + cb_chan->channel_data_function = my_channel_data_function; + cb_chan->channel_exit_status_function = my_channel_exit_status_function; + + ssh_callbacks_init(cb_chan); + ssh_set_channel_callbacks(channel, cb_chan); + + ssh_event_add_fd(mainloop, (socket_t)*pFd, POLLIN, my_fd_data_function, event_fd_data); + + return 0; + } + } + } + return 1; +} + +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh server example " +SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "dsakey", + .key = 'd', + .arg = "FILE", + .flags = 0, + .doc = "Set the dsa key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key.", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + break; + case 'd': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, arg); + break; + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'r': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, arg); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "1"); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage (state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage (state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +int main(int argc, char **argv){ + ssh_session session; + ssh_bind sshbind; + struct ssh_server_callbacks_struct cb = { + .userdata = NULL, + .auth_password_function = auth_password, + .auth_gssapi_mic_function = auth_gssapi_mic, + .channel_open_request_session_function = new_session_channel, + .service_request_function = service_request + }; + struct ssh_callbacks_struct cb_gen = { + .userdata = NULL, + .global_request_function = global_request + }; + + int ret = 1; + + sshbind = ssh_bind_new(); + session = ssh_new(); + mainloop = ssh_event_new(); + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, KEYS_FOLDER "ssh_host_dsa_key"); + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, KEYS_FOLDER "ssh_host_rsa_key"); + +#ifdef HAVE_ARGP_H + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ + argp_parse (&argp, argc, argv, 0, 0, sshbind); +#else + (void)argc; + (void)argv; +#endif + + if (ssh_bind_listen(sshbind) < 0) { + printf("Error listening to socket: %s\n", ssh_get_error(sshbind)); + return 1; + } + + if (ssh_bind_accept(sshbind, session) == SSH_ERROR) { + printf("error accepting a connection : %s\n", ssh_get_error(sshbind)); + ret = 1; + goto shutdown; + } + + ssh_callbacks_init(&cb); + ssh_callbacks_init(&cb_gen); + ssh_set_server_callbacks(session, &cb); + ssh_set_callbacks(session, &cb_gen); + ssh_set_message_callback(session, message_callback, (void *)NULL); + + if (ssh_handle_key_exchange(session)) { + printf("ssh_handle_key_exchange: %s\n", ssh_get_error(session)); + ret = 1; + goto shutdown; + } + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_GSSAPI_MIC); + ssh_event_add_session(mainloop, session); + + while (!authenticated) { + if (error_set) { + break; + } + if (ssh_event_dopoll(mainloop, -1) == SSH_ERROR) { + printf("Error : %s\n", ssh_get_error(session)); + ret = 1; + goto shutdown; + } + } + if (error_set) { + printf("Error, exiting loop\n"); + } else { + printf("Authenticated and got a channel\n"); + + while (!error_set) { + if (ssh_event_dopoll(mainloop, 100) == SSH_ERROR) { + printf("Error : %s\n", ssh_get_error(session)); + ret = 1; + goto shutdown; + } + do_cleanup(&cleanup_stack); + } + } + +shutdown: + ssh_disconnect(session); + ssh_bind_free(sshbind); + ssh_finalize(); + return ret; +} diff --git a/examples/sshnetcat.c b/examples/sshnetcat.c new file mode 100644 index 0000000..ccc72c8 --- /dev/null +++ b/examples/sshnetcat.c @@ -0,0 +1,267 @@ +/* +Copyright 2010 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" +#include +#include +#include +#ifdef HAVE_TERMIOS_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "examples_common.h" +char *host; +const char *desthost="localhost"; +const char *port="22"; + +#ifdef WITH_PCAP +#include +char *pcap_file=NULL; +#endif + +static void usage(void) +{ + fprintf(stderr,"Usage : sshnetcat [user@]host forwarded_host forwarded_port\n"); + exit(1); +} + +static int opts(int argc, char **argv){ + int i; + while((i=getopt(argc,argv,"P:"))!=-1){ + switch(i){ +#ifdef WITH_PCAP + case 'P': + pcap_file=optarg; + break; +#endif + default: + fprintf(stderr,"unknown option %c\n",optopt); + usage(); + } + } + if(optind < argc) + host=argv[optind++]; + if(optind < argc) + desthost=argv[optind++]; + if(optind < argc) + port=argv[optind++]; + if(host==NULL) + usage(); + return 0; +} + +static void select_loop(ssh_session session,ssh_channel channel){ + fd_set fds; + struct timeval timeout; + char buffer[4096]; + /* channels will be set to the channels to poll. + * outchannels will contain the result of the poll + */ + ssh_channel channels[2], outchannels[2]; + int lus; + int eof=0; + int maxfd; + int ret; + while(channel){ + do{ + int fd; + + ZERO_STRUCT(fds); + FD_ZERO(&fds); + if(!eof) + FD_SET(0,&fds); + timeout.tv_sec=30; + timeout.tv_usec=0; + + fd = ssh_get_fd(session); + if (fd == -1) { + fprintf(stderr, "Error getting the session file descriptor: %s\n", + ssh_get_error(session)); + return; + } + FD_SET(fd, &fds); + maxfd = fd + 1; + + channels[0]=channel; // set the first channel we want to read from + channels[1]=NULL; + ret=ssh_select(channels,outchannels,maxfd,&fds,&timeout); + if(ret==EINTR) + continue; + if(FD_ISSET(0,&fds)){ + lus=read(0,buffer,sizeof(buffer)); + if(lus) + ssh_channel_write(channel,buffer,lus); + else { + eof=1; + ssh_channel_send_eof(channel); + } + } + if(channel && ssh_channel_is_closed(channel)){ + ssh_channel_free(channel); + channel=NULL; + channels[0]=NULL; + } + if(outchannels[0]){ + while(channel && ssh_channel_is_open(channel) && ssh_channel_poll(channel,0)){ + lus = ssh_channel_read(channel,buffer,sizeof(buffer),0); + if(lus==-1){ + fprintf(stderr, "Error reading channel: %s\n", + ssh_get_error(session)); + return; + } + if(lus==0){ + ssh_channel_free(channel); + channel=channels[0]=NULL; + } else { + ret = write(1, buffer, lus); + if (ret < 0) { + fprintf(stderr, "Error writing to stdin: %s", + strerror(errno)); + return; + } + } + } + while(channel && ssh_channel_is_open(channel) && ssh_channel_poll(channel,1)){ /* stderr */ + lus = ssh_channel_read(channel, buffer, sizeof(buffer), 1); + if(lus==-1){ + fprintf(stderr, "Error reading channel: %s\n", + ssh_get_error(session)); + return; + } + if(lus==0){ + ssh_channel_free(channel); + channel=channels[0]=NULL; + } else { + ret = write(2, buffer, lus); + if (ret < 0) { + fprintf(stderr, "Error writing to stderr: %s", + strerror(errno)); + return; + } + } + } + } + if(channel && ssh_channel_is_closed(channel)){ + ssh_channel_free(channel); + channel=NULL; + } + } while (ret==EINTR || ret==SSH_EINTR); + + } +} + +static void forwarding(ssh_session session){ + ssh_channel channel; + int r; + channel = ssh_channel_new(session); + r = ssh_channel_open_forward(channel, desthost, atoi(port), "localhost", 22); + if(r<0) { + printf("error forwarding port : %s\n",ssh_get_error(session)); + return; + } + select_loop(session,channel); +} + +static int client(ssh_session session){ + int auth=0; + char *banner; + int state; + + if (ssh_options_set(session, SSH_OPTIONS_HOST ,host) < 0) + return -1; + ssh_options_parse_config(session, NULL); + + if(ssh_connect(session)){ + fprintf(stderr,"Connection failed : %s\n",ssh_get_error(session)); + return -1; + } + state=verify_knownhost(session); + if (state != 0) + return -1; + ssh_userauth_none(session, NULL); + banner=ssh_get_issue_banner(session); + if(banner){ + printf("%s\n",banner); + free(banner); + } + auth=authenticate_console(session); + if(auth != SSH_AUTH_SUCCESS){ + return -1; + } + forwarding(session); + return 0; +} + +#ifdef WITH_PCAP +ssh_pcap_file pcap; +void set_pcap(ssh_session session); +void set_pcap(ssh_session session){ + if(!pcap_file) + return; + pcap=ssh_pcap_file_new(); + if(ssh_pcap_file_open(pcap,pcap_file) == SSH_ERROR){ + printf("Error opening pcap file\n"); + ssh_pcap_file_free(pcap); + pcap=NULL; + return; + } + ssh_set_pcap_file(session,pcap); +} + +void cleanup_pcap(void); +void cleanup_pcap(){ + ssh_pcap_file_free(pcap); + pcap=NULL; +} +#endif + +int main(int argc, char **argv){ + ssh_session session; + + session = ssh_new(); + + if(ssh_options_getopt(session, &argc, argv)) { + fprintf(stderr, "error parsing command line :%s\n", + ssh_get_error(session)); + usage(); + } + opts(argc,argv); +#ifdef WITH_PCAP + set_pcap(session); +#endif + client(session); + + ssh_disconnect(session); + ssh_free(session); +#ifdef WITH_PCAP + cleanup_pcap(); +#endif + + ssh_finalize(); + + return 0; +} diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt new file mode 100644 index 0000000..a01b529 --- /dev/null +++ b/include/CMakeLists.txt @@ -0,0 +1,3 @@ +project(libssh-headers-x C) + +add_subdirectory(libssh) diff --git a/include/libssh/CMakeLists.txt b/include/libssh/CMakeLists.txt new file mode 100644 index 0000000..acc966e --- /dev/null +++ b/include/libssh/CMakeLists.txt @@ -0,0 +1,33 @@ +project(libssh-headers C) + +set(libssh_HDRS + callbacks.h + libssh.h + ssh2.h + legacy.h + libsshpp.hpp +) + +if (WITH_SFTP) + set(libssh_HDRS + ${libssh_HDRS} + sftp.h + ) +endif (WITH_SFTP) + +if (WITH_SERVER) + set(libssh_HDRS + ${libssh_HDRS} + server.h + ) +endif (WITH_SERVER) + +install( + FILES + ${libssh_HDRS} + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/${APPLICATION_NAME} + COMPONENT + headers +) + diff --git a/include/libssh/agent.h b/include/libssh/agent.h new file mode 100644 index 0000000..d4eefbb --- /dev/null +++ b/include/libssh/agent.h @@ -0,0 +1,120 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 Andreas Schneider + * + * 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 + */ + +#ifndef __AGENT_H +#define __AGENT_H + +#include "libssh/libssh.h" + +/* Messages for the authentication agent connection. */ +#define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1 +#define SSH_AGENT_RSA_IDENTITIES_ANSWER 2 +#define SSH_AGENTC_RSA_CHALLENGE 3 +#define SSH_AGENT_RSA_RESPONSE 4 +#define SSH_AGENT_FAILURE 5 +#define SSH_AGENT_SUCCESS 6 +#define SSH_AGENTC_ADD_RSA_IDENTITY 7 +#define SSH_AGENTC_REMOVE_RSA_IDENTITY 8 +#define SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES 9 + +/* private OpenSSH extensions for SSH2 */ +#define SSH2_AGENTC_REQUEST_IDENTITIES 11 +#define SSH2_AGENT_IDENTITIES_ANSWER 12 +#define SSH2_AGENTC_SIGN_REQUEST 13 +#define SSH2_AGENT_SIGN_RESPONSE 14 +#define SSH2_AGENTC_ADD_IDENTITY 17 +#define SSH2_AGENTC_REMOVE_IDENTITY 18 +#define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19 + +/* smartcard */ +#define SSH_AGENTC_ADD_SMARTCARD_KEY 20 +#define SSH_AGENTC_REMOVE_SMARTCARD_KEY 21 + +/* lock/unlock the agent */ +#define SSH_AGENTC_LOCK 22 +#define SSH_AGENTC_UNLOCK 23 + +/* add key with constraints */ +#define SSH_AGENTC_ADD_RSA_ID_CONSTRAINED 24 +#define SSH2_AGENTC_ADD_ID_CONSTRAINED 25 +#define SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED 26 + +#define SSH_AGENT_CONSTRAIN_LIFETIME 1 +#define SSH_AGENT_CONSTRAIN_CONFIRM 2 + +/* extended failure messages */ +#define SSH2_AGENT_FAILURE 30 + +/* additional error code for ssh.com's ssh-agent2 */ +#define SSH_COM_AGENT2_FAILURE 102 + +#define SSH_AGENT_OLD_SIGNATURE 0x01 +/* Signature flags from draft-miller-ssh-agent-02 */ +#define SSH_AGENT_RSA_SHA2_256 0x02 +#define SSH_AGENT_RSA_SHA2_512 0x04 + +struct ssh_agent_struct { + struct ssh_socket_struct *sock; + ssh_buffer ident; + unsigned int count; + ssh_channel channel; +}; + +#ifndef _WIN32 +/* agent.c */ +/** + * @brief Create a new ssh agent structure. + * + * @return An allocated ssh agent structure or NULL on error. + */ +struct ssh_agent_struct *ssh_agent_new(struct ssh_session_struct *session); + +void ssh_agent_close(struct ssh_agent_struct *agent); + +/** + * @brief Free an allocated ssh agent structure. + * + * @param agent The ssh agent structure to free. + */ +void ssh_agent_free(struct ssh_agent_struct *agent); + +/** + * @brief Check if the ssh agent is running. + * + * @param session The ssh session to check for the agent. + * + * @return 1 if it is running, 0 if not. + */ +int ssh_agent_is_running(struct ssh_session_struct *session); + +uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session); + +ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session, + char **comment); + +ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, + char **comment); + +ssh_string ssh_agent_sign_data(ssh_session session, + const ssh_key pubkey, + struct ssh_buffer_struct *data); +#endif + +#endif /* __AGENT_H */ diff --git a/include/libssh/auth.h b/include/libssh/auth.h new file mode 100644 index 0000000..90b377d --- /dev/null +++ b/include/libssh/auth.h @@ -0,0 +1,103 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef AUTH_H_ +#define AUTH_H_ +#include "config.h" +#include "libssh/callbacks.h" + +SSH_PACKET_CALLBACK(ssh_packet_userauth_banner); +SSH_PACKET_CALLBACK(ssh_packet_userauth_failure); +SSH_PACKET_CALLBACK(ssh_packet_userauth_success); +SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok); +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request); +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response); + +/** @internal + * kdbint structure must be shared with message.c + * and server.c + */ +struct ssh_kbdint_struct { + uint32_t nprompts; + uint32_t nanswers; + char *name; + char *instruction; + char **prompts; + unsigned char *echo; /* bool array */ + char **answers; +}; +typedef struct ssh_kbdint_struct* ssh_kbdint; + +ssh_kbdint ssh_kbdint_new(void); +void ssh_kbdint_clean(ssh_kbdint kbd); +void ssh_kbdint_free(ssh_kbdint kbd); + +/** @internal + * States of authentication in the client-side. They describe + * what was the last response from the server + */ +enum ssh_auth_state_e { + /** No authentication asked */ + SSH_AUTH_STATE_NONE=0, + /** Last authentication response was a partial success */ + SSH_AUTH_STATE_PARTIAL, + /** Last authentication response was a success */ + SSH_AUTH_STATE_SUCCESS, + /** Last authentication response was failed */ + SSH_AUTH_STATE_FAILED, + /** Last authentication was erroneous */ + SSH_AUTH_STATE_ERROR, + /** Last state was a keyboard-interactive ask for info */ + SSH_AUTH_STATE_INFO, + /** Last state was a public key accepted for authentication */ + SSH_AUTH_STATE_PK_OK, + /** We asked for a keyboard-interactive authentication */ + SSH_AUTH_STATE_KBDINT_SENT, + /** We have sent an userauth request with gssapi-with-mic */ + SSH_AUTH_STATE_GSSAPI_REQUEST_SENT, + /** We are exchanging tokens until authentication */ + SSH_AUTH_STATE_GSSAPI_TOKEN, + /** We have sent the MIC and expecting to be authenticated */ + SSH_AUTH_STATE_GSSAPI_MIC_SENT, + /** We have offered a pubkey to check if it is supported */ + SSH_AUTH_STATE_PUBKEY_OFFER_SENT, + /** We have sent pubkey and signature expecting to be authenticated */ + SSH_AUTH_STATE_PUBKEY_AUTH_SENT, + /** We have sent a password expecting to be authenticated */ + SSH_AUTH_STATE_PASSWORD_AUTH_SENT, + /** We have sent a request without auth information (method 'none') */ + SSH_AUTH_STATE_AUTH_NONE_SENT, +}; + +/** @internal + * @brief states of the authentication service request + */ +enum ssh_auth_service_state_e { + /** initial state */ + SSH_AUTH_SERVICE_NONE=0, + /** Authentication service request packet sent */ + SSH_AUTH_SERVICE_SENT, + /** Service accepted */ + SSH_AUTH_SERVICE_ACCEPTED, + /** Access to service denied (fatal) */ + SSH_AUTH_SERVICE_DENIED, +}; + +#endif /* AUTH_H_ */ diff --git a/include/libssh/bignum.h b/include/libssh/bignum.h new file mode 100644 index 0000000..726ed7b --- /dev/null +++ b/include/libssh/bignum.h @@ -0,0 +1,33 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2014 by Aris Adamantiadis + * + * 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 + */ + +#ifndef BIGNUM_H_ +#define BIGNUM_H_ + +#include "libssh/libcrypto.h" +#include "libssh/libgcrypt.h" +#include "libssh/libmbedcrypto.h" + +bignum ssh_make_string_bn(ssh_string string); +ssh_string ssh_make_bignum_string(bignum num); +void ssh_print_bignum(const char *which, const_bignum num); + + +#endif /* BIGNUM_H_ */ diff --git a/include/libssh/bind.h b/include/libssh/bind.h new file mode 100644 index 0000000..6b5f19d --- /dev/null +++ b/include/libssh/bind.h @@ -0,0 +1,59 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * 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 + */ + +#ifndef BIND_H_ +#define BIND_H_ + +#include "libssh/priv.h" +#include "libssh/kex.h" +#include "libssh/session.h" + +struct ssh_bind_struct { + struct ssh_common_struct common; /* stuff common to ssh_bind and ssh_session */ + struct ssh_bind_callbacks_struct *bind_callbacks; + void *bind_callbacks_userdata; + + struct ssh_poll_handle_struct *poll; + /* options */ + char *wanted_methods[SSH_KEX_METHODS]; + char *banner; + char *ecdsakey; + char *dsakey; + char *rsakey; + char *ed25519key; + ssh_key ecdsa; + ssh_key dsa; + ssh_key rsa; + ssh_key ed25519; + char *bindaddr; + socket_t bindfd; + unsigned int bindport; + int blocking; + int toaccept; + bool config_processed; + char *config_dir; + char *pubkey_accepted_key_types; +}; + +struct ssh_poll_handle_struct *ssh_bind_get_poll(struct ssh_bind_struct + *sshbind); + + +#endif /* BIND_H_ */ diff --git a/include/libssh/bind_config.h b/include/libssh/bind_config.h new file mode 100644 index 0000000..cb68da8 --- /dev/null +++ b/include/libssh/bind_config.h @@ -0,0 +1,64 @@ +/* + * bind_config.h - Parse the SSH server configuration file + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * 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. + */ + +#ifndef BIND_CONFIG_H_ +#define BIND_CONFIG_H_ + +#include "libssh/server.h" + +enum ssh_bind_config_opcode_e { + /* Known but not allowed in Match block */ + BIND_CFG_NOT_ALLOWED_IN_MATCH = -4, + /* Unknown opcode */ + BIND_CFG_UNKNOWN = -3, + /* Known and not applicable to libssh */ + BIND_CFG_NA = -2, + /* Known but not supported by current libssh version */ + BIND_CFG_UNSUPPORTED = -1, + BIND_CFG_INCLUDE, + BIND_CFG_HOSTKEY, + BIND_CFG_LISTENADDRESS, + BIND_CFG_PORT, + BIND_CFG_LOGLEVEL, + BIND_CFG_CIPHERS, + BIND_CFG_MACS, + BIND_CFG_KEXALGORITHMS, + BIND_CFG_MATCH, + BIND_CFG_PUBKEY_ACCEPTED_KEY_TYPES, + BIND_CFG_HOSTKEY_ALGORITHMS, + + BIND_CFG_MAX /* Keep this one last in the list */ +}; + +/* @brief Parse configuration file and set the options to the given ssh_bind + * + * @params[in] sshbind The ssh_bind context to be configured + * @params[in] filename The path to the configuration file + * + * @returns 0 on successful parsing the configuration file, -1 on error + */ +int ssh_bind_config_parse_file(ssh_bind sshbind, const char *filename); + +#endif /* BIND_CONFIG_H_ */ diff --git a/include/libssh/blf.h b/include/libssh/blf.h new file mode 100644 index 0000000..ce131e6 --- /dev/null +++ b/include/libssh/blf.h @@ -0,0 +1,87 @@ +/* $OpenBSD: blf.h,v 1.7 2007/03/14 17:59:41 grunk Exp $ */ +/* + * Blowfish - a fast block cipher designed by Bruce Schneier + * + * Copyright 1997 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Niels Provos. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 _BLF_H_ +#define _BLF_H_ + +//#include "includes.h" + +#if !defined(HAVE_BCRYPT_PBKDF) && !defined(HAVE_BLH_H) + +/* Schneier specifies a maximum key length of 56 bytes. + * This ensures that every key bit affects every cipher + * bit. However, the subkeys can hold up to 72 bytes. + * Warning: For normal blowfish encryption only 56 bytes + * of the key affect all cipherbits. + */ + +#define BLF_N 16 /* Number of Subkeys */ +#define BLF_MAXKEYLEN ((BLF_N-2)*4) /* 448 bits */ +#define BLF_MAXUTILIZED ((BLF_N+2)*4) /* 576 bits */ + +/* Blowfish context */ +typedef struct BlowfishContext { + uint32_t S[4][256]; /* S-Boxes */ + uint32_t P[BLF_N + 2]; /* Subkeys */ +} ssh_blf_ctx; + +/* Raw access to customized Blowfish + * blf_key is just: + * Blowfish_initstate( state ) + * Blowfish_expand0state( state, key, keylen ) + */ + +void Blowfish_encipher(ssh_blf_ctx *, uint32_t *, uint32_t *); +void Blowfish_decipher(ssh_blf_ctx *, uint32_t *, uint32_t *); +void Blowfish_initstate(ssh_blf_ctx *); +void Blowfish_expand0state(ssh_blf_ctx *, const uint8_t *, uint16_t); +void Blowfish_expandstate +(ssh_blf_ctx *, const uint8_t *, uint16_t, const uint8_t *, uint16_t); + +/* Standard Blowfish */ + +void ssh_blf_key(ssh_blf_ctx *, const uint8_t *, uint16_t); +void ssh_blf_enc(ssh_blf_ctx *, uint32_t *, uint16_t); +void ssh_blf_dec(ssh_blf_ctx *, uint32_t *, uint16_t); + +void ssh_blf_ecb_encrypt(ssh_blf_ctx *, uint8_t *, uint32_t); +void ssh_blf_ecb_decrypt(ssh_blf_ctx *, uint8_t *, uint32_t); + +void ssh_blf_cbc_encrypt(ssh_blf_ctx *, uint8_t *, uint8_t *, uint32_t); +void ssh_blf_cbc_decrypt(ssh_blf_ctx *, uint8_t *, uint8_t *, uint32_t); + +/* Converts uint8_t to uint32_t */ +uint32_t Blowfish_stream2word(const uint8_t *, uint16_t , uint16_t *); + +#endif /* !defined(HAVE_BCRYPT_PBKDF) && !defined(HAVE_BLH_H) */ +#endif /* _BLF_H */ diff --git a/include/libssh/buffer.h b/include/libssh/buffer.h new file mode 100644 index 0000000..cd2dea6 --- /dev/null +++ b/include/libssh/buffer.h @@ -0,0 +1,77 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef BUFFER_H_ +#define BUFFER_H_ + +#include + +#include "libssh/libssh.h" + +#define SSH_BUFFER_PACK_END ((uint32_t) 0x4f65feb3) + +void ssh_buffer_set_secure(ssh_buffer buffer); +int ssh_buffer_add_ssh_string(ssh_buffer buffer, ssh_string string); +int ssh_buffer_add_u8(ssh_buffer buffer, uint8_t data); +int ssh_buffer_add_u16(ssh_buffer buffer, uint16_t data); +int ssh_buffer_add_u32(ssh_buffer buffer, uint32_t data); +int ssh_buffer_add_u64(ssh_buffer buffer, uint64_t data); + +int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len); + +void *ssh_buffer_allocate(struct ssh_buffer_struct *buffer, uint32_t len); +int ssh_buffer_allocate_size(struct ssh_buffer_struct *buffer, uint32_t len); +int ssh_buffer_pack_va(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + va_list ap); +int _ssh_buffer_pack(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + ...); +#define ssh_buffer_pack(buffer, format, ...) \ + _ssh_buffer_pack((buffer), (format), __VA_NARG__(__VA_ARGS__), __VA_ARGS__, SSH_BUFFER_PACK_END) + +int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, + const char *format, size_t argc, + va_list ap); +int _ssh_buffer_unpack(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + ...); +#define ssh_buffer_unpack(buffer, format, ...) \ + _ssh_buffer_unpack((buffer), (format), __VA_NARG__(__VA_ARGS__), __VA_ARGS__, SSH_BUFFER_PACK_END) + +int ssh_buffer_prepend_data(ssh_buffer buffer, const void *data, uint32_t len); +int ssh_buffer_add_buffer(ssh_buffer buffer, ssh_buffer source); + +/* buffer_read_*() returns the number of bytes read, except for ssh strings */ +int ssh_buffer_get_u8(ssh_buffer buffer, uint8_t *data); +int ssh_buffer_get_u32(ssh_buffer buffer, uint32_t *data); +int ssh_buffer_get_u64(ssh_buffer buffer, uint64_t *data); + +/* ssh_buffer_get_ssh_string() is an exception. if the String read is too large or invalid, it will answer NULL. */ +ssh_string ssh_buffer_get_ssh_string(ssh_buffer buffer); + +/* ssh_buffer_pass_bytes acts as if len bytes have been read (used for padding) */ +uint32_t ssh_buffer_pass_bytes_end(ssh_buffer buffer, uint32_t len); +uint32_t ssh_buffer_pass_bytes(ssh_buffer buffer, uint32_t len); + +#endif /* BUFFER_H_ */ diff --git a/include/libssh/bytearray.h b/include/libssh/bytearray.h new file mode 100644 index 0000000..0c0690d --- /dev/null +++ b/include/libssh/bytearray.h @@ -0,0 +1,90 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 Andreas Schneider + * + * 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 + */ +#ifndef _BYTEARRAY_H +#define _BYTEARRAY_H + +#define _DATA_BYTE_CONST(data, pos) \ + ((uint8_t)(((const uint8_t *)(data))[(pos)])) + +#define _DATA_BYTE(data, pos) \ + (((uint8_t *)(data))[(pos)]) + +/* + * These macros pull or push integer values from byte arrays stored in + * little-endian byte order. + */ +#define PULL_LE_U8(data, pos) \ + (_DATA_BYTE_CONST(data, pos)) + +#define PULL_LE_U16(data, pos) \ + ((uint16_t)PULL_LE_U8(data, pos) | ((uint16_t)(PULL_LE_U8(data, (pos) + 1))) << 8) + +#define PULL_LE_U32(data, pos) \ + ((uint32_t)(PULL_LE_U16(data, pos) | ((uint32_t)PULL_LE_U16(data, (pos) + 2)) << 16)) + +#define PULL_LE_U64(data, pos) \ + ((uint64_t)(PULL_LE_U32(data, pos) | ((uint64_t)PULL_LE_U32(data, (pos) + 4)) << 32)) + + +#define PUSH_LE_U8(data, pos, val) \ + (_DATA_BYTE(data, pos) = ((uint8_t)(val))) + +#define PUSH_LE_U16(data, pos, val) \ + (PUSH_LE_U8((data), (pos), (uint8_t)((uint16_t)(val) & 0xff)), PUSH_LE_U8((data), (pos) + 1, (uint8_t)((uint16_t)(val) >> 8))) + +#define PUSH_LE_U32(data, pos, val) \ + (PUSH_LE_U16((data), (pos), (uint16_t)((uint32_t)(val) & 0xffff)), PUSH_LE_U16((data), (pos) + 2, (uint16_t)((uint32_t)(val) >> 16))) + +#define PUSH_LE_U64(data, pos, val) \ + (PUSH_LE_U32((data), (pos), (uint32_t)((uint64_t)(val) & 0xffffffff)), PUSH_LE_U32((data), (pos) + 4, (uint32_t)((uint64_t)(val) >> 32))) + + + +/* + * These macros pull or push integer values from byte arrays stored in + * big-endian byte order (network byte order). + */ +#define PULL_BE_U8(data, pos) \ + (_DATA_BYTE_CONST(data, pos)) + +#define PULL_BE_U16(data, pos) \ + ((((uint16_t)(PULL_BE_U8(data, pos))) << 8) | (uint16_t)PULL_BE_U8(data, (pos) + 1)) + +#define PULL_BE_U32(data, pos) \ + ((((uint32_t)PULL_BE_U16(data, pos)) << 16) | (uint32_t)(PULL_BE_U16(data, (pos) + 2))) + +#define PULL_BE_U64(data, pos) \ + ((((uint64_t)PULL_BE_U32(data, pos)) << 32) | (uint64_t)(PULL_BE_U32(data, (pos) + 4))) + + + +#define PUSH_BE_U8(data, pos, val) \ + (_DATA_BYTE(data, pos) = ((uint8_t)(val))) + +#define PUSH_BE_U16(data, pos, val) \ + (PUSH_BE_U8((data), (pos), (uint8_t)(((uint16_t)(val)) >> 8)), PUSH_BE_U8((data), (pos) + 1, (uint8_t)((val) & 0xff))) + +#define PUSH_BE_U32(data, pos, val) \ + (PUSH_BE_U16((data), (pos), (uint16_t)(((uint32_t)(val)) >> 16)), PUSH_BE_U16((data), (pos) + 2, (uint16_t)((val) & 0xffff))) + +#define PUSH_BE_U64(data, pos, val) \ + (PUSH_BE_U32((data), (pos), (uint32_t)(((uint64_t)(val)) >> 32)), PUSH_BE_U32((data), (pos) + 4, (uint32_t)((val) & 0xffffffff))) + +#endif /* _BYTEARRAY_H */ diff --git a/include/libssh/callbacks.h b/include/libssh/callbacks.h new file mode 100644 index 0000000..15e8801 --- /dev/null +++ b/include/libssh/callbacks.h @@ -0,0 +1,1000 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 Aris Adamantiadis + * + * 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 + */ + +/* callback.h + * This file includes the public declarations for the libssh callback mechanism + */ + +#ifndef _SSH_CALLBACK_H +#define _SSH_CALLBACK_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup libssh_callbacks The libssh callbacks + * @ingroup libssh + * + * Callback which can be replaced in libssh. + * + * @{ + */ + +/** @internal + * @brief callback to process simple codes + * @param code value to transmit + * @param user Userdata to pass in callback + */ +typedef void (*ssh_callback_int) (int code, void *user); + +/** @internal + * @brief callback for data received messages. + * @param data data retrieved from the socket or stream + * @param len number of bytes available from this stream + * @param user user-supplied pointer sent along with all callback messages + * @returns number of bytes processed by the callee. The remaining bytes will + * be sent in the next callback message, when more data is available. + */ +typedef int (*ssh_callback_data) (const void *data, size_t len, void *user); + +typedef void (*ssh_callback_int_int) (int code, int errno_code, void *user); + +typedef int (*ssh_message_callback) (ssh_session, ssh_message message, void *user); +typedef int (*ssh_channel_callback_int) (ssh_channel channel, int code, void *user); +typedef int (*ssh_channel_callback_data) (ssh_channel channel, int code, void *data, size_t len, void *user); + +/** + * @brief SSH log callback. All logging messages will go through this callback + * @param session Current session handler + * @param priority Priority of the log, the smaller being the more important + * @param message the actual message + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_log_callback) (ssh_session session, int priority, + const char *message, void *userdata); + +/** + * @brief SSH log callback. + * + * All logging messages will go through this callback. + * + * @param priority Priority of the log, the smaller being the more important. + * + * @param function The function name calling the the logging fucntions. + * + * @param message The actual message + * + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_logging_callback) (int priority, + const char *function, + const char *buffer, + void *userdata); + +/** + * @brief SSH Connection status callback. + * @param session Current session handler + * @param status Percentage of connection status, going from 0.0 to 1.0 + * once connection is done. + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_status_callback) (ssh_session session, float status, + void *userdata); + +/** + * @brief SSH global request callback. All global request will go through this + * callback. + * @param session Current session handler + * @param message the actual message + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_global_request_callback) (ssh_session session, + ssh_message message, void *userdata); + +/** + * @brief Handles an SSH new channel open X11 request. This happens when the server + * sends back an X11 connection attempt. This is a client-side API + * @param session current session handler + * @param userdata Userdata to be passed to the callback function. + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the application. + */ +typedef ssh_channel (*ssh_channel_open_request_x11_callback) (ssh_session session, + const char * originator_address, int originator_port, void *userdata); + +/** + * @brief Handles an SSH new channel open "auth-agent" request. This happens when the server + * sends back an "auth-agent" connection attempt. This is a client-side API + * @param session current session handler + * @param userdata Userdata to be passed to the callback function. + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the application. + */ +typedef ssh_channel (*ssh_channel_open_request_auth_agent_callback) (ssh_session session, + void *userdata); + +/** + * The structure to replace libssh functions with appropriate callbacks. + */ +struct ssh_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This functions will be called if e.g. a keyphrase is needed. + */ + ssh_auth_callback auth_function; + /** + * This function will be called each time a loggable event happens. + */ + ssh_log_callback log_function; + /** + * This function gets called during connection time to indicate the + * percentage of connection steps completed. + */ + void (*connect_status_function)(void *userdata, float status); + /** + * This function will be called each time a global request is received. + */ + ssh_global_request_callback global_request_function; + /** This function will be called when an incoming X11 request is received. + */ + ssh_channel_open_request_x11_callback channel_open_request_x11_function; + /** This function will be called when an incoming "auth-agent" request is received. + */ + ssh_channel_open_request_auth_agent_callback channel_open_request_auth_agent_function; +}; +typedef struct ssh_callbacks_struct *ssh_callbacks; + +/** These are callbacks used specifically in SSH servers. + */ + +/** + * @brief SSH authentication callback. + * @param session Current session handler + * @param user User that wants to authenticate + * @param password Password used for authentication + * @param userdata Userdata to be passed to the callback function. + * @returns SSH_AUTH_SUCCESS Authentication is accepted. + * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. + * @returns SSH_AUTH_DENIED Authentication failed. + */ +typedef int (*ssh_auth_password_callback) (ssh_session session, const char *user, const char *password, + void *userdata); + +/** + * @brief SSH authentication callback. Tries to authenticates user with the "none" method + * which is anonymous or passwordless. + * @param session Current session handler + * @param user User that wants to authenticate + * @param userdata Userdata to be passed to the callback function. + * @returns SSH_AUTH_SUCCESS Authentication is accepted. + * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. + * @returns SSH_AUTH_DENIED Authentication failed. + */ +typedef int (*ssh_auth_none_callback) (ssh_session session, const char *user, void *userdata); + +/** + * @brief SSH authentication callback. Tries to authenticates user with the "gssapi-with-mic" method + * @param session Current session handler + * @param user Username of the user (can be spoofed) + * @param principal Authenticated principal of the user, including realm. + * @param userdata Userdata to be passed to the callback function. + * @returns SSH_AUTH_SUCCESS Authentication is accepted. + * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. + * @returns SSH_AUTH_DENIED Authentication failed. + * @warning Implementations should verify that parameter user matches in some way the principal. + * user and principal can be different. Only the latter is guaranteed to be safe. + */ +typedef int (*ssh_auth_gssapi_mic_callback) (ssh_session session, const char *user, const char *principal, + void *userdata); + +/** + * @brief SSH authentication callback. + * @param session Current session handler + * @param user User that wants to authenticate + * @param pubkey public key used for authentication + * @param signature_state SSH_PUBLICKEY_STATE_NONE if the key is not signed (simple public key probe), + * SSH_PUBLICKEY_STATE_VALID if the signature is valid. Others values should be + * replied with a SSH_AUTH_DENIED. + * @param userdata Userdata to be passed to the callback function. + * @returns SSH_AUTH_SUCCESS Authentication is accepted. + * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. + * @returns SSH_AUTH_DENIED Authentication failed. + */ +typedef int (*ssh_auth_pubkey_callback) (ssh_session session, const char *user, struct ssh_key_struct *pubkey, + char signature_state, void *userdata); + + +/** + * @brief Handles an SSH service request + * @param session current session handler + * @param service name of the service (e.g. "ssh-userauth") requested + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the request is to be allowed + * @returns -1 if the request should not be allowed + */ + +typedef int (*ssh_service_request_callback) (ssh_session session, const char *service, void *userdata); + +/** + * @brief Handles an SSH new channel open session request + * @param session current session handler + * @param userdata Userdata to be passed to the callback function. + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the application. + */ +typedef ssh_channel (*ssh_channel_open_request_session_callback) (ssh_session session, void *userdata); + +/* + * @brief handle the beginning of a GSSAPI authentication, server side. + * @param session current session handler + * @param user the username of the client + * @param n_oid number of available oids + * @param oids OIDs provided by the client + * @returns an ssh_string containing the chosen OID, that's supported by both + * client and server. + * @warning It is not necessary to fill this callback in if libssh is linked + * with libgssapi. + */ +typedef ssh_string (*ssh_gssapi_select_oid_callback) (ssh_session session, const char *user, + int n_oid, ssh_string *oids, void *userdata); + +/* + * @brief handle the negociation of a security context, server side. + * @param session current session handler + * @param[in] input_token input token provided by client + * @param[out] output_token output of the gssapi accept_sec_context method, + * NULL after completion. + * @returns SSH_OK if the token was generated correctly or accept_sec_context + * returned GSS_S_COMPLETE + * @returns SSH_ERROR in case of error + * @warning It is not necessary to fill this callback in if libssh is linked + * with libgssapi. + */ +typedef int (*ssh_gssapi_accept_sec_ctx_callback) (ssh_session session, + ssh_string input_token, ssh_string *output_token, void *userdata); + +/* + * @brief Verify and authenticates a MIC, server side. + * @param session current session handler + * @param[in] mic input mic to be verified provided by client + * @param[in] mic_buffer buffer of data to be signed. + * @param[in] mic_buffer_size size of mic_buffer + * @returns SSH_OK if the MIC was authenticated correctly + * @returns SSH_ERROR in case of error + * @warning It is not necessary to fill this callback in if libssh is linked + * with libgssapi. + */ +typedef int (*ssh_gssapi_verify_mic_callback) (ssh_session session, + ssh_string mic, void *mic_buffer, size_t mic_buffer_size, void *userdata); + + +/** + * This structure can be used to implement a libssh server, with appropriate callbacks. + */ + +struct ssh_server_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** This function gets called when a client tries to authenticate through + * password method. + */ + ssh_auth_password_callback auth_password_function; + + /** This function gets called when a client tries to authenticate through + * none method. + */ + ssh_auth_none_callback auth_none_function; + + /** This function gets called when a client tries to authenticate through + * gssapi-mic method. + */ + ssh_auth_gssapi_mic_callback auth_gssapi_mic_function; + + /** this function gets called when a client tries to authenticate or offer + * a public key. + */ + ssh_auth_pubkey_callback auth_pubkey_function; + + /** This functions gets called when a service request is issued by the + * client + */ + ssh_service_request_callback service_request_function; + /** This functions gets called when a new channel request is issued by + * the client + */ + ssh_channel_open_request_session_callback channel_open_request_session_function; + /** This function will be called when a new gssapi authentication is attempted. + */ + ssh_gssapi_select_oid_callback gssapi_select_oid_function; + /** This function will be called when a gssapi token comes in. + */ + ssh_gssapi_accept_sec_ctx_callback gssapi_accept_sec_ctx_function; + /* This function will be called when a MIC needs to be verified. + */ + ssh_gssapi_verify_mic_callback gssapi_verify_mic_function; +}; +typedef struct ssh_server_callbacks_struct *ssh_server_callbacks; + +/** + * @brief Set the session server callback functions. + * + * This functions sets the callback structure to use your own callback + * functions for user authentication, new channels and requests. + * + * @code + * struct ssh_server_callbacks_struct cb = { + * .userdata = data, + * .auth_password_function = my_auth_function + * }; + * ssh_callbacks_init(&cb); + * ssh_set_server_callbacks(session, &cb); + * @endcode + * + * @param session The session to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +LIBSSH_API int ssh_set_server_callbacks(ssh_session session, ssh_server_callbacks cb); + +/** + * These are the callbacks exported by the socket structure + * They are called by the socket module when a socket event appears + */ +struct ssh_socket_callbacks_struct { + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This function will be called each time data appears on socket. The data + * not consumed will appear on the next data event. + */ + ssh_callback_data data; + /** This function will be called each time a controlflow state changes, i.e. + * the socket is available for reading or writing. + */ + ssh_callback_int controlflow; + /** This function will be called each time an exception appears on socket. An + * exception can be a socket problem (timeout, ...) or an end-of-file. + */ + ssh_callback_int_int exception; + /** This function is called when the ssh_socket_connect was used on the socket + * on nonblocking state, and the connection successed. + */ + ssh_callback_int_int connected; +}; +typedef struct ssh_socket_callbacks_struct *ssh_socket_callbacks; + +#define SSH_SOCKET_FLOW_WRITEWILLBLOCK 1 +#define SSH_SOCKET_FLOW_WRITEWONTBLOCK 2 + +#define SSH_SOCKET_EXCEPTION_EOF 1 +#define SSH_SOCKET_EXCEPTION_ERROR 2 + +#define SSH_SOCKET_CONNECTED_OK 1 +#define SSH_SOCKET_CONNECTED_ERROR 2 +#define SSH_SOCKET_CONNECTED_TIMEOUT 3 + +/** + * @brief Initializes an ssh_callbacks_struct + * A call to this macro is mandatory when you have set a new + * ssh_callback_struct structure. Its goal is to maintain the binary + * compatibility with future versions of libssh as the structure + * evolves with time. + */ +#define ssh_callbacks_init(p) do {\ + (p)->size=sizeof(*(p)); \ +} while(0); + +/** + * @internal + * @brief tests if a callback can be called without crash + * verifies that the struct size if big enough + * verifies that the callback pointer exists + * @param p callback pointer + * @param c callback name + * @returns nonzero if callback can be called + */ +#define ssh_callbacks_exists(p,c) (\ + (p != NULL) && ( (char *)&((p)-> c) < (char *)(p) + (p)->size ) && \ + ((p)-> c != NULL) \ + ) + +/** + * @internal + * + * @brief Iterate through a list of callback structures + * + * This tests for their validity and executes them. The userdata argument is + * automatically passed through. + * + * @param list list of callbacks + * + * @param cbtype type of the callback + * + * @param c callback name + * + * @param va_args parameters to be passed + */ +#define ssh_callbacks_execute_list(list, cbtype, c, ...) \ + do { \ + struct ssh_iterator *i = ssh_list_get_iterator(list); \ + cbtype cb; \ + while (i != NULL){ \ + cb = ssh_iterator_value(cbtype, i); \ + if (ssh_callbacks_exists(cb, c)) \ + cb-> c (__VA_ARGS__, cb->userdata); \ + i = i->next; \ + } \ + } while(0) + +/** + * @internal + * + * @brief iterate through a list of callback structures. + * + * This tests for their validity and give control back to the calling code to + * execute them. Caller can decide to break the loop or continue executing the + * callbacks with different parameters + * + * @code + * ssh_callbacks_iterate(channel->callbacks, ssh_channel_callbacks, + * channel_eof_function){ + * rc = ssh_callbacks_iterate_exec(session, channel); + * if (rc != SSH_OK){ + * break; + * } + * } + * ssh_callbacks_iterate_end(); + * @endcode + */ +#define ssh_callbacks_iterate(_cb_list, _cb_type, _cb_name) \ + do { \ + struct ssh_iterator *_cb_i = ssh_list_get_iterator(_cb_list); \ + _cb_type _cb; \ + for (; _cb_i != NULL; _cb_i = _cb_i->next) { \ + _cb = ssh_iterator_value(_cb_type, _cb_i); \ + if (ssh_callbacks_exists(_cb, _cb_name)) + +#define ssh_callbacks_iterate_exec(_cb_name, ...) \ + _cb->_cb_name(__VA_ARGS__, _cb->userdata) + +#define ssh_callbacks_iterate_end() \ + } \ + } while(0) + +/** @brief Prototype for a packet callback, to be called when a new packet arrives + * @param session The current session of the packet + * @param type packet type (see ssh2.h) + * @param packet buffer containing the packet, excluding size, type and padding fields + * @param user user argument to the callback + * and are called each time a packet shows up + * @returns SSH_PACKET_USED Packet was parsed and used + * @returns SSH_PACKET_NOT_USED Packet was not used or understood, processing must continue + */ +typedef int (*ssh_packet_callback) (ssh_session session, uint8_t type, ssh_buffer packet, void *user); + +/** return values for a ssh_packet_callback */ +/** Packet was used and should not be parsed by another callback */ +#define SSH_PACKET_USED 1 +/** Packet was not used and should be passed to any other callback + * available */ +#define SSH_PACKET_NOT_USED 2 + + +/** @brief This macro declares a packet callback handler + * @code + * SSH_PACKET_CALLBACK(mycallback){ + * ... + * } + * @endcode + */ +#define SSH_PACKET_CALLBACK(name) \ + int name (ssh_session session, uint8_t type, ssh_buffer packet, void *user) + +struct ssh_packet_callbacks_struct { + /** Index of the first packet type being handled */ + uint8_t start; + /** Number of packets being handled by this callback struct */ + uint8_t n_callbacks; + /** A pointer to n_callbacks packet callbacks */ + ssh_packet_callback *callbacks; + /** + * User-provided data. User is free to set anything he wants here + */ + void *user; +}; + +typedef struct ssh_packet_callbacks_struct *ssh_packet_callbacks; + +/** + * @brief Set the session callback functions. + * + * This functions sets the callback structure to use your own callback + * functions for auth, logging and status. + * + * @code + * struct ssh_callbacks_struct cb = { + * .userdata = data, + * .auth_function = my_auth_function + * }; + * ssh_callbacks_init(&cb); + * ssh_set_callbacks(session, &cb); + * @endcode + * + * @param session The session to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +LIBSSH_API int ssh_set_callbacks(ssh_session session, ssh_callbacks cb); + +/** + * @brief SSH channel data callback. Called when data is available on a channel + * @param session Current session handler + * @param channel the actual channel + * @param data the data that has been read on the channel + * @param len the length of the data + * @param is_stderr is 0 for stdout or 1 for stderr + * @param userdata Userdata to be passed to the callback function. + * @returns number of bytes processed by the callee. The remaining bytes will + * be sent in the next callback message, when more data is available. + */ +typedef int (*ssh_channel_data_callback) (ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata); + +/** + * @brief SSH channel eof callback. Called when a channel receives EOF + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_eof_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +/** + * @brief SSH channel close callback. Called when a channel is closed by remote peer + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_close_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +/** + * @brief SSH channel signal callback. Called when a channel has received a signal + * @param session Current session handler + * @param channel the actual channel + * @param signal the signal name (without the SIG prefix) + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_signal_callback) (ssh_session session, + ssh_channel channel, + const char *signal, + void *userdata); + +/** + * @brief SSH channel exit status callback. Called when a channel has received an exit status + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_exit_status_callback) (ssh_session session, + ssh_channel channel, + int exit_status, + void *userdata); + +/** + * @brief SSH channel exit signal callback. Called when a channel has received an exit signal + * @param session Current session handler + * @param channel the actual channel + * @param signal the signal name (without the SIG prefix) + * @param core a boolean telling wether a core has been dumped or not + * @param errmsg the description of the exception + * @param lang the language of the description (format: RFC 3066) + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_exit_signal_callback) (ssh_session session, + ssh_channel channel, + const char *signal, + int core, + const char *errmsg, + const char *lang, + void *userdata); + +/** + * @brief SSH channel PTY request from a client. + * @param channel the channel + * @param term The type of terminal emulation + * @param width width of the terminal, in characters + * @param height height of the terminal, in characters + * @param pxwidth width of the terminal, in pixels + * @param pxheight height of the terminal, in pixels + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the pty request is accepted + * @returns -1 if the request is denied + */ +typedef int (*ssh_channel_pty_request_callback) (ssh_session session, + ssh_channel channel, + const char *term, + int width, int height, + int pxwidth, int pwheight, + void *userdata); + +/** + * @brief SSH channel Shell request from a client. + * @param channel the channel + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the shell request is accepted + * @returns 1 if the request is denied + */ +typedef int (*ssh_channel_shell_request_callback) (ssh_session session, + ssh_channel channel, + void *userdata); +/** + * @brief SSH auth-agent-request from the client. This request is + * sent by a client when agent forwarding is available. + * Server is free to ignore this callback, no answer is expected. + * @param channel the channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_auth_agent_req_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +/** + * @brief SSH X11 request from the client. This request is + * sent by a client when X11 forwarding is requested(and available). + * Server is free to ignore this callback, no answer is expected. + * @param channel the channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_x11_req_callback) (ssh_session session, + ssh_channel channel, + int single_connection, + const char *auth_protocol, + const char *auth_cookie, + uint32_t screen_number, + void *userdata); +/** + * @brief SSH channel PTY windows change (terminal size) from a client. + * @param channel the channel + * @param width width of the terminal, in characters + * @param height height of the terminal, in characters + * @param pxwidth width of the terminal, in pixels + * @param pxheight height of the terminal, in pixels + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the pty request is accepted + * @returns -1 if the request is denied + */ +typedef int (*ssh_channel_pty_window_change_callback) (ssh_session session, + ssh_channel channel, + int width, int height, + int pxwidth, int pwheight, + void *userdata); + +/** + * @brief SSH channel Exec request from a client. + * @param channel the channel + * @param command the shell command to be executed + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the exec request is accepted + * @returns 1 if the request is denied + */ +typedef int (*ssh_channel_exec_request_callback) (ssh_session session, + ssh_channel channel, + const char *command, + void *userdata); + +/** + * @brief SSH channel environment request from a client. + * @param channel the channel + * @param env_name name of the environment value to be set + * @param env_value value of the environment value to be set + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the env request is accepted + * @returns 1 if the request is denied + * @warning some environment variables can be dangerous if changed (e.g. + * LD_PRELOAD) and should not be fulfilled. + */ +typedef int (*ssh_channel_env_request_callback) (ssh_session session, + ssh_channel channel, + const char *env_name, + const char *env_value, + void *userdata); +/** + * @brief SSH channel subsystem request from a client. + * @param channel the channel + * @param subsystem the subsystem required + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the subsystem request is accepted + * @returns 1 if the request is denied + */ +typedef int (*ssh_channel_subsystem_request_callback) (ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata); + +/** + * @brief SSH channel write will not block (flow control). + * + * @param channel the channel + * + * @param[in] bytes size of the remote window in bytes. Writing as much data + * will not block. + * + * @param[in] userdata Userdata to be passed to the callback function. + * + * @returns 0 default return value (other return codes may be added in future). + */ +typedef int (*ssh_channel_write_wontblock_callback) (ssh_session session, + ssh_channel channel, + size_t bytes, + void *userdata); + +struct ssh_channel_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This functions will be called when there is data available. + */ + ssh_channel_data_callback channel_data_function; + /** + * This functions will be called when the channel has received an EOF. + */ + ssh_channel_eof_callback channel_eof_function; + /** + * This functions will be called when the channel has been closed by remote + */ + ssh_channel_close_callback channel_close_function; + /** + * This functions will be called when a signal has been received + */ + ssh_channel_signal_callback channel_signal_function; + /** + * This functions will be called when an exit status has been received + */ + ssh_channel_exit_status_callback channel_exit_status_function; + /** + * This functions will be called when an exit signal has been received + */ + ssh_channel_exit_signal_callback channel_exit_signal_function; + /** + * This function will be called when a client requests a PTY + */ + ssh_channel_pty_request_callback channel_pty_request_function; + /** + * This function will be called when a client requests a shell + */ + ssh_channel_shell_request_callback channel_shell_request_function; + /** This function will be called when a client requests agent + * authentication forwarding. + */ + ssh_channel_auth_agent_req_callback channel_auth_agent_req_function; + /** This function will be called when a client requests X11 + * forwarding. + */ + ssh_channel_x11_req_callback channel_x11_req_function; + /** This function will be called when a client requests a + * window change. + */ + ssh_channel_pty_window_change_callback channel_pty_window_change_function; + /** This function will be called when a client requests a + * command execution. + */ + ssh_channel_exec_request_callback channel_exec_request_function; + /** This function will be called when a client requests an environment + * variable to be set. + */ + ssh_channel_env_request_callback channel_env_request_function; + /** This function will be called when a client requests a subsystem + * (like sftp). + */ + ssh_channel_subsystem_request_callback channel_subsystem_request_function; + /** This function will be called when the channel write is guaranteed + * not to block. + */ + ssh_channel_write_wontblock_callback channel_write_wontblock_function; +}; + +typedef struct ssh_channel_callbacks_struct *ssh_channel_callbacks; + +/** + * @brief Set the channel callback functions. + * + * This functions sets the callback structure to use your own callback + * functions for channel data and exceptions + * + * @code + * struct ssh_channel_callbacks_struct cb = { + * .userdata = data, + * .channel_data_function = my_channel_data_function + * }; + * ssh_callbacks_init(&cb); + * ssh_set_channel_callbacks(channel, &cb); + * @endcode + * + * @param channel The channel to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return SSH_OK on success, SSH_ERROR on error. + * @warning this function will not replace existing callbacks but set the + * new one atop of them. + */ +LIBSSH_API int ssh_set_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb); + +/** + * @brief Add channel callback functions + * + * This function will add channel callback functions to the channel callback + * list. + * Callbacks missing from a callback structure will be probed in the next + * on the list. + * + * @param channel The channel to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_set_channel_callbacks + */ +LIBSSH_API int ssh_add_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb); + +/** + * @brief Remove a channel callback. + * + * The channel has been added with ssh_add_channel_callbacks or + * ssh_set_channel_callbacks in this case. + * + * @param channel The channel to remove the callback structure from. + * + * @param cb The callback structure to remove + * + * @returns SSH_OK on success, SSH_ERROR on error. + */ +LIBSSH_API int ssh_remove_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb); + +/** @} */ + +/** @group libssh_threads + * @{ + */ + +typedef int (*ssh_thread_callback) (void **lock); + +typedef unsigned long (*ssh_thread_id_callback) (void); +struct ssh_threads_callbacks_struct { + const char *type; + ssh_thread_callback mutex_init; + ssh_thread_callback mutex_destroy; + ssh_thread_callback mutex_lock; + ssh_thread_callback mutex_unlock; + ssh_thread_id_callback thread_id; +}; + +/** + * @brief Set the thread callbacks structure. + * + * This is necessary if your program is using libssh in a multithreaded fashion. + * This function must be called first, outside of any threading context (in your + * main() function for instance), before you call ssh_init(). + * + * @param[in] cb A pointer to a ssh_threads_callbacks_struct structure, which + * contains the different callbacks to be set. + * + * @returns Always returns SSH_OK. + * + * @see ssh_threads_callbacks_struct + * @see SSH_THREADS_PTHREAD + * @bug libgcrypt 1.6 and bigger backend does not support custom callback. + * Using anything else than pthreads here will fail. + */ +LIBSSH_API int ssh_threads_set_callbacks(struct ssh_threads_callbacks_struct + *cb); + +/** + * @brief Returns a pointer to the appropriate callbacks structure for the + * environment, to be used with ssh_threads_set_callbacks. + * + * @returns A pointer to a ssh_threads_callbacks_struct to be used with + * ssh_threads_set_callbacks. + * + * @see ssh_threads_set_callbacks + */ +LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_default(void); + +/** + * @brief Returns a pointer on the pthread threads callbacks, to be used with + * ssh_threads_set_callbacks. + * + * @see ssh_threads_set_callbacks + */ +LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_pthread(void); + +/** + * @brief Get the noop threads callbacks structure + * + * This can be used with ssh_threads_set_callbacks. These callbacks do nothing + * and are being used by default. + * + * @return Always returns a valid pointer to the noop callbacks structure. + * + * @see ssh_threads_set_callbacks + */ +LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_noop(void); + +/** + * @brief Set the logging callback function. + * + * @param[in] cb The callback to set. + * + * @return 0 on success, < 0 on errror. + */ +LIBSSH_API int ssh_set_log_callback(ssh_logging_callback cb); + +/** + * @brief Get the pointer to the logging callback function. + * + * @return The pointer the the callback or NULL if none set. + */ +LIBSSH_API ssh_logging_callback ssh_get_log_callback(void); + +/** @} */ +#ifdef __cplusplus +} +#endif + +#endif /*_SSH_CALLBACK_H */ + +/* @} */ diff --git a/include/libssh/chacha.h b/include/libssh/chacha.h new file mode 100644 index 0000000..bac78c6 --- /dev/null +++ b/include/libssh/chacha.h @@ -0,0 +1,41 @@ +/* $OpenBSD: chacha.h,v 1.3 2014/05/02 03:27:54 djm Exp $ */ + +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. +*/ + +#ifndef CHACHA_H +#define CHACHA_H + +struct chacha_ctx { + uint32_t input[16]; +}; + +#define CHACHA_MINKEYLEN 16 +#define CHACHA_NONCELEN 8 +#define CHACHA_CTRLEN 8 +#define CHACHA_STATELEN (CHACHA_NONCELEN+CHACHA_CTRLEN) +#define CHACHA_BLOCKLEN 64 + +void chacha_keysetup(struct chacha_ctx *x, const uint8_t *k, uint32_t kbits) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_MINKEYLEN))) +#endif + ; +void chacha_ivsetup(struct chacha_ctx *x, const uint8_t *iv, const uint8_t *ctr) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_NONCELEN))) + __attribute__((__bounded__(__minbytes__, 3, CHACHA_CTRLEN))) +#endif + ; +void chacha_encrypt_bytes(struct chacha_ctx *x, const uint8_t *m, + uint8_t *c, uint32_t bytes) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__buffer__, 2, 4))) + __attribute__((__bounded__(__buffer__, 3, 4))) +#endif + ; + +#endif /* CHACHA_H */ diff --git a/include/libssh/channels.h b/include/libssh/channels.h new file mode 100644 index 0000000..bbabcfd --- /dev/null +++ b/include/libssh/channels.h @@ -0,0 +1,112 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef CHANNELS_H_ +#define CHANNELS_H_ +#include "libssh/priv.h" + +/** @internal + * Describes the different possible states in a + * outgoing (client) channel request + */ +enum ssh_channel_request_state_e { + /** No request has been made */ + SSH_CHANNEL_REQ_STATE_NONE = 0, + /** A request has been made and answer is pending */ + SSH_CHANNEL_REQ_STATE_PENDING, + /** A request has been replied and accepted */ + SSH_CHANNEL_REQ_STATE_ACCEPTED, + /** A request has been replied and refused */ + SSH_CHANNEL_REQ_STATE_DENIED, + /** A request has been replied and an error happend */ + SSH_CHANNEL_REQ_STATE_ERROR +}; + +enum ssh_channel_state_e { + SSH_CHANNEL_STATE_NOT_OPEN = 0, + SSH_CHANNEL_STATE_OPENING, + SSH_CHANNEL_STATE_OPEN_DENIED, + SSH_CHANNEL_STATE_OPEN, + SSH_CHANNEL_STATE_CLOSED +}; + +/* The channel has been closed by the remote side */ +#define SSH_CHANNEL_FLAG_CLOSED_REMOTE 0x0001 + +/* The channel has been closed locally */ +#define SSH_CHANNEL_FLAG_CLOSED_LOCAL 0x0002 + +/* The channel has been freed by the calling program */ +#define SSH_CHANNEL_FLAG_FREED_LOCAL 0x0004 + +/* the channel has not yet been bound to a remote one */ +#define SSH_CHANNEL_FLAG_NOT_BOUND 0x0008 + +struct ssh_channel_struct { + ssh_session session; /* SSH_SESSION pointer */ + uint32_t local_channel; + uint32_t local_window; + int local_eof; + uint32_t local_maxpacket; + + uint32_t remote_channel; + uint32_t remote_window; + int remote_eof; /* end of file received */ + uint32_t remote_maxpacket; + enum ssh_channel_state_e state; + int delayed_close; + int flags; + ssh_buffer stdout_buffer; + ssh_buffer stderr_buffer; + void *userarg; + int exit_status; + enum ssh_channel_request_state_e request_state; + struct ssh_list *callbacks; /* list of ssh_channel_callbacks */ + + /* counters */ + ssh_counter counter; +}; + +SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf); +SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail); +SSH_PACKET_CALLBACK(ssh_packet_channel_success); +SSH_PACKET_CALLBACK(ssh_packet_channel_failure); +SSH_PACKET_CALLBACK(ssh_request_success); +SSH_PACKET_CALLBACK(ssh_request_denied); + +SSH_PACKET_CALLBACK(channel_rcv_change_window); +SSH_PACKET_CALLBACK(channel_rcv_eof); +SSH_PACKET_CALLBACK(channel_rcv_close); +SSH_PACKET_CALLBACK(channel_rcv_request); +SSH_PACKET_CALLBACK(channel_rcv_data); + +int channel_default_bufferize(ssh_channel channel, + void *data, size_t len, + bool is_stderr); +int ssh_channel_flush(ssh_channel channel); +uint32_t ssh_channel_new_id(ssh_session session); +ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id); +void ssh_channel_do_free(ssh_channel channel); +int ssh_global_request(ssh_session session, + const char *request, + ssh_buffer buffer, + int reply); + +#endif /* CHANNELS_H_ */ diff --git a/include/libssh/config.h b/include/libssh/config.h new file mode 100644 index 0000000..f2b9b57 --- /dev/null +++ b/include/libssh/config.h @@ -0,0 +1,68 @@ +/* + * config.h - parse the ssh config file + * + * This file is part of the SSH Library + * + * Copyright (c) 2009-2018 by Andreas Schneider + * + * 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. + */ + +#ifndef LIBSSH_CONFIG_H_ +#define LIBSSH_CONFIG_H_ + + +enum ssh_config_opcode_e { + /* Unknown opcode */ + SOC_UNKNOWN = -3, + /* Known and not applicable to libssh */ + SOC_NA = -2, + /* Known but not supported by current libssh version */ + SOC_UNSUPPORTED = -1, + SOC_HOST, + SOC_MATCH, + SOC_HOSTNAME, + SOC_PORT, + SOC_USERNAME, + SOC_IDENTITY, + SOC_CIPHERS, + SOC_MACS, + SOC_COMPRESSION, + SOC_TIMEOUT, + SOC_PROTOCOL, + SOC_STRICTHOSTKEYCHECK, + SOC_KNOWNHOSTS, + SOC_PROXYCOMMAND, + SOC_PROXYJUMP, + SOC_GSSAPISERVERIDENTITY, + SOC_GSSAPICLIENTIDENTITY, + SOC_GSSAPIDELEGATECREDENTIALS, + SOC_INCLUDE, + SOC_BINDADDRESS, + SOC_GLOBALKNOWNHOSTSFILE, + SOC_LOGLEVEL, + SOC_HOSTKEYALGORITHMS, + SOC_KEXALGORITHMS, + SOC_GSSAPIAUTHENTICATION, + SOC_KBDINTERACTIVEAUTHENTICATION, + SOC_PASSWORDAUTHENTICATION, + SOC_PUBKEYAUTHENTICATION, + SOC_PUBKEYACCEPTEDTYPES, + SOC_REKEYLIMIT, + + SOC_MAX /* Keep this one last in the list */ +}; +#endif /* LIBSSH_CONFIG_H_ */ diff --git a/include/libssh/config_parser.h b/include/libssh/config_parser.h new file mode 100644 index 0000000..e974917 --- /dev/null +++ b/include/libssh/config_parser.h @@ -0,0 +1,57 @@ +/* + * config_parser.h - Common configuration file parser functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * 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. + */ + +#ifndef CONFIG_PARSER_H_ +#define CONFIG_PARSER_H_ + +char *ssh_config_get_cmd(char **str); + +char *ssh_config_get_token(char **str); + +long ssh_config_get_long(char **str, long notfound); + +const char *ssh_config_get_str_tok(char **str, const char *def); + +int ssh_config_get_yesno(char **str, int notfound); + +/* @brief Parse SSH URI in format [user@]host[:port] from the given string + * + * @param[in] tok String to parse + * @param[out] username Pointer to the location, where the new username will + * be stored or NULL if we do not care about the result. + * @param[out] hostname Pointer to the location, where the new hostname will + * be stored or NULL if we do not care about the result. + * @param[out] port Pointer to the location, where the new port will + * be stored or NULL if we do not care about the result. + * + * @returns SSH_OK if the provided string is in format of SSH URI, + * SSH_ERROR on failure + */ +int ssh_config_parse_uri(const char *tok, + char **username, + char **hostname, + char **port); + +#endif /* LIBSSH_CONFIG_H_ */ diff --git a/include/libssh/crypto.h b/include/libssh/crypto.h new file mode 100644 index 0000000..ede7166 --- /dev/null +++ b/include/libssh/crypto.h @@ -0,0 +1,216 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * 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 + */ + +/* + * crypto.h is an include file for internal cryptographic structures of libssh + */ + +#ifndef _CRYPTO_H_ +#define _CRYPTO_H_ + +#include +#include "config.h" + +#ifdef HAVE_LIBGCRYPT +#include +#elif defined(HAVE_LIBMBEDCRYPTO) +#include +#endif +#include "libssh/wrapper.h" + +#ifdef cbc_encrypt +#undef cbc_encrypt +#endif +#ifdef cbc_decrypt +#undef cbc_decrypt +#endif + +#ifdef HAVE_OPENSSL_ECDH_H +#include +#endif +#include "libssh/dh.h" +#include "libssh/ecdh.h" +#include "libssh/kex.h" +#include "libssh/curve25519.h" + +#define DIGEST_MAX_LEN 64 + +#define AES_GCM_TAGLEN 16 +#define AES_GCM_IVLEN 12 + +enum ssh_key_exchange_e { + /* diffie-hellman-group1-sha1 */ + SSH_KEX_DH_GROUP1_SHA1=1, + /* diffie-hellman-group14-sha1 */ + SSH_KEX_DH_GROUP14_SHA1, +#ifdef WITH_GEX + /* diffie-hellman-group-exchange-sha1 */ + SSH_KEX_DH_GEX_SHA1, + /* diffie-hellman-group-exchange-sha256 */ + SSH_KEX_DH_GEX_SHA256, +#endif /* WITH_GEX */ + /* ecdh-sha2-nistp256 */ + SSH_KEX_ECDH_SHA2_NISTP256, + /* ecdh-sha2-nistp384 */ + SSH_KEX_ECDH_SHA2_NISTP384, + /* ecdh-sha2-nistp521 */ + SSH_KEX_ECDH_SHA2_NISTP521, + /* curve25519-sha256@libssh.org */ + SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG, + /* curve25519-sha256 */ + SSH_KEX_CURVE25519_SHA256, + /* diffie-hellman-group16-sha512 */ + SSH_KEX_DH_GROUP16_SHA512, + /* diffie-hellman-group18-sha512 */ + SSH_KEX_DH_GROUP18_SHA512, + /* diffie-hellman-group14-sha256 */ + SSH_KEX_DH_GROUP14_SHA256, +}; + +enum ssh_cipher_e { + SSH_NO_CIPHER=0, +#ifdef WITH_BLOWFISH_CIPHER + SSH_BLOWFISH_CBC, +#endif /* WITH_BLOWFISH_CIPHER */ + SSH_3DES_CBC, + SSH_AES128_CBC, + SSH_AES192_CBC, + SSH_AES256_CBC, + SSH_AES128_CTR, + SSH_AES192_CTR, + SSH_AES256_CTR, + SSH_AEAD_AES128_GCM, + SSH_AEAD_AES256_GCM, + SSH_AEAD_CHACHA20_POLY1305 +}; + +struct dh_ctx; + +struct ssh_crypto_struct { + bignum shared_secret; + struct dh_ctx *dh_ctx; +#ifdef WITH_GEX + size_t dh_pmin; size_t dh_pn; size_t dh_pmax; /* preferred group parameters */ +#endif /* WITH_GEX */ +#ifdef HAVE_ECDH +#ifdef HAVE_OPENSSL_ECC + EC_KEY *ecdh_privkey; +#elif defined HAVE_GCRYPT_ECC + gcry_sexp_t ecdh_privkey; +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_ecp_keypair *ecdh_privkey; +#endif + ssh_string ecdh_client_pubkey; + ssh_string ecdh_server_pubkey; +#endif +#ifdef HAVE_CURVE25519 + ssh_curve25519_privkey curve25519_privkey; + ssh_curve25519_pubkey curve25519_client_pubkey; + ssh_curve25519_pubkey curve25519_server_pubkey; +#endif + ssh_string dh_server_signature; /* information used by dh_handshake. */ + size_t digest_len; /* len of the two fields below */ + unsigned char *session_id; + unsigned char *secret_hash; /* Secret hash is same as session id until re-kex */ + unsigned char *encryptIV; + unsigned char *decryptIV; + unsigned char *decryptkey; + unsigned char *encryptkey; + unsigned char *encryptMAC; + unsigned char *decryptMAC; + unsigned char hmacbuf[DIGEST_MAX_LEN]; + struct ssh_cipher_struct *in_cipher, *out_cipher; /* the cipher structures/objects */ + enum ssh_hmac_e in_hmac, out_hmac; /* the MAC algorithms used */ + bool in_hmac_etm, out_hmac_etm; /* Whether EtM mode is used or not */ + + ssh_key server_pubkey; + int do_compress_out; /* idem */ + int do_compress_in; /* don't set them, set the option instead */ + int delayed_compress_in; /* Use of zlib@openssh.org */ + int delayed_compress_out; + void *compress_out_ctx; /* don't touch it */ + void *compress_in_ctx; /* really, don't */ + /* kex sent by server, client, and mutually elected methods */ + struct ssh_kex_struct server_kex; + struct ssh_kex_struct client_kex; + char *kex_methods[SSH_KEX_METHODS]; + enum ssh_key_exchange_e kex_type; + enum ssh_kdf_digest digest_type; /* Digest type for session keys derivation */ + enum ssh_crypto_direction_e used; /* Is this crypto still used for either of directions? */ +}; + +struct ssh_cipher_struct { + const char *name; /* ssh name of the algorithm */ + unsigned int blocksize; /* blocksize of the algo */ + enum ssh_cipher_e ciphertype; + uint32_t lenfield_blocksize; /* blocksize of the packet length field */ + size_t keylen; /* length of the key structure */ +#ifdef HAVE_LIBGCRYPT + gcry_cipher_hd_t *key; + unsigned char last_iv[AES_GCM_IVLEN]; +#elif defined HAVE_LIBCRYPTO + struct ssh_3des_key_schedule *des3_key; + struct ssh_aes_key_schedule *aes_key; + const EVP_CIPHER *cipher; + EVP_CIPHER_CTX *ctx; +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_cipher_context_t encrypt_ctx; + mbedtls_cipher_context_t decrypt_ctx; + mbedtls_cipher_type_t type; +#ifdef MBEDTLS_GCM_C + mbedtls_gcm_context gcm_ctx; + unsigned char last_iv[AES_GCM_IVLEN]; +#endif /* MBEDTLS_GCM_C */ +#endif + struct chacha20_poly1305_keysched *chacha20_schedule; + unsigned int keysize; /* bytes of key used. != keylen */ + size_t tag_size; /* overhead required for tag */ + /* Counters for rekeying initialization */ + uint32_t packets; + uint64_t blocks; + /* Rekeying limit for the cipher or manually enforced */ + uint64_t max_blocks; + /* sets the new key for immediate use */ + int (*set_encrypt_key)(struct ssh_cipher_struct *cipher, void *key, void *IV); + int (*set_decrypt_key)(struct ssh_cipher_struct *cipher, void *key, void *IV); + void (*encrypt)(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len); + void (*decrypt)(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len); + void (*aead_encrypt)(struct ssh_cipher_struct *cipher, void *in, void *out, + size_t len, uint8_t *mac, uint64_t seq); + int (*aead_decrypt_length)(struct ssh_cipher_struct *cipher, void *in, + uint8_t *out, size_t len, uint64_t seq); + int (*aead_decrypt)(struct ssh_cipher_struct *cipher, void *complete_packet, uint8_t *out, + size_t encrypted_size, uint64_t seq); + void (*cleanup)(struct ssh_cipher_struct *cipher); +}; + +const struct ssh_cipher_struct *ssh_get_chacha20poly1305_cipher(void); +int sshkdf_derive_key(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + int key_type, unsigned char *output, + size_t requested_len); + +#endif /* _CRYPTO_H_ */ diff --git a/include/libssh/curve25519.h b/include/libssh/curve25519.h new file mode 100644 index 0000000..f0cc634 --- /dev/null +++ b/include/libssh/curve25519.h @@ -0,0 +1,56 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * + * 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, + * version 2.1 of the License. + * + * 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 + */ + +#ifndef CURVE25519_H_ +#define CURVE25519_H_ + +#include "config.h" +#include "libssh.h" + +#ifdef WITH_NACL + +#include +#define CURVE25519_PUBKEY_SIZE crypto_scalarmult_curve25519_BYTES +#define CURVE25519_PRIVKEY_SIZE crypto_scalarmult_curve25519_SCALARBYTES +#define crypto_scalarmult_base crypto_scalarmult_curve25519_base +#define crypto_scalarmult crypto_scalarmult_curve25519 +#else + +#define CURVE25519_PUBKEY_SIZE 32 +#define CURVE25519_PRIVKEY_SIZE 32 +int crypto_scalarmult_base(unsigned char *q, const unsigned char *n); +int crypto_scalarmult(unsigned char *q, const unsigned char *n, const unsigned char *p); +#endif /* WITH_NACL */ + +#ifdef HAVE_ECC +#define HAVE_CURVE25519 1 +#endif + +typedef unsigned char ssh_curve25519_pubkey[CURVE25519_PUBKEY_SIZE]; +typedef unsigned char ssh_curve25519_privkey[CURVE25519_PRIVKEY_SIZE]; + + +int ssh_client_curve25519_init(ssh_session session); + +#ifdef WITH_SERVER +void ssh_server_curve25519_init(ssh_session session); +#endif /* WITH_SERVER */ + +#endif /* CURVE25519_H_ */ diff --git a/include/libssh/dh-gex.h b/include/libssh/dh-gex.h new file mode 100644 index 0000000..4fc23d8 --- /dev/null +++ b/include/libssh/dh-gex.h @@ -0,0 +1,32 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2016 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. + */ + + +#ifndef SRC_DH_GEX_H_ +#define SRC_DH_GEX_H_ + +int ssh_client_dhgex_init(ssh_session session); + +#ifdef WITH_SERVER +void ssh_server_dhgex_init(ssh_session session); +#endif /* WITH_SERVER */ + +#endif /* SRC_DH_GEX_H_ */ diff --git a/include/libssh/dh.h b/include/libssh/dh.h new file mode 100644 index 0000000..390b30d --- /dev/null +++ b/include/libssh/dh.h @@ -0,0 +1,75 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef DH_H_ +#define DH_H_ + +#include "config.h" + +#include "libssh/crypto.h" + +struct dh_ctx; + +#define DH_CLIENT_KEYPAIR 0 +#define DH_SERVER_KEYPAIR 1 + +/* functions implemented by crypto backends */ +int ssh_dh_init_common(struct ssh_crypto_struct *crypto); +void ssh_dh_cleanup(struct ssh_crypto_struct *crypto); + +int ssh_dh_get_parameters(struct dh_ctx *ctx, + const_bignum *modulus, const_bignum *generator); +int ssh_dh_set_parameters(struct dh_ctx *ctx, + const bignum modulus, const bignum generator); + +int ssh_dh_keypair_gen_keys(struct dh_ctx *ctx, int peer); +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + const_bignum *priv, const_bignum *pub); +int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, + const bignum priv, const bignum pub); + +int ssh_dh_compute_shared_secret(struct dh_ctx *ctx, int local, int remote, + bignum *dest); + +void ssh_dh_debug_crypto(struct ssh_crypto_struct *c); + +/* common functions */ +int ssh_dh_init(void); +void ssh_dh_finalize(void); + +int ssh_dh_import_next_pubkey_blob(ssh_session session, + ssh_string pubkey_blob); + +ssh_key ssh_dh_get_current_server_publickey(ssh_session session); +int ssh_dh_get_current_server_publickey_blob(ssh_session session, + ssh_string *pubkey_blob); +ssh_key ssh_dh_get_next_server_publickey(ssh_session session); +int ssh_dh_get_next_server_publickey_blob(ssh_session session, + ssh_string *pubkey_blob); + +int ssh_client_dh_init(ssh_session session); +#ifdef WITH_SERVER +void ssh_server_dh_init(ssh_session session); +#endif /* WITH_SERVER */ +int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet); +int ssh_fallback_group(uint32_t pmax, bignum *p, bignum *g); +bool ssh_dh_is_known_group(bignum modulus, bignum generator); + +#endif /* DH_H_ */ diff --git a/include/libssh/ecdh.h b/include/libssh/ecdh.h new file mode 100644 index 0000000..17fe02e --- /dev/null +++ b/include/libssh/ecdh.h @@ -0,0 +1,56 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011 by Aris Adamantiadis + * + * 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 + */ + +#ifndef ECDH_H_ +#define ECDH_H_ + +#include "config.h" +#include "libssh/callbacks.h" + +#ifdef HAVE_LIBCRYPTO +#ifdef HAVE_OPENSSL_ECDH_H + +#ifdef HAVE_ECC +#define HAVE_ECDH 1 +#endif + +#endif /* HAVE_OPENSSL_ECDH_H */ +#endif /* HAVE_LIBCRYPTO */ + +#ifdef HAVE_GCRYPT_ECC +#define HAVE_ECDH 1 +#endif + +#ifdef HAVE_LIBMBEDCRYPTO +#define HAVE_ECDH 1 +#endif + +extern struct ssh_packet_callbacks_struct ssh_ecdh_client_callbacks; +/* Backend-specific functions. */ +int ssh_client_ecdh_init(ssh_session session); +int ecdh_build_k(ssh_session session); + +#ifdef WITH_SERVER +extern struct ssh_packet_callbacks_struct ssh_ecdh_server_callbacks; +void ssh_server_ecdh_init(ssh_session session); +SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init); +#endif /* WITH_SERVER */ + +#endif /* ECDH_H_ */ diff --git a/include/libssh/ed25519.h b/include/libssh/ed25519.h new file mode 100644 index 0000000..8a3263c --- /dev/null +++ b/include/libssh/ed25519.h @@ -0,0 +1,79 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2014 by Aris Adamantiadis + * + * 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 + */ + +#ifndef ED25519_H_ +#define ED25519_H_ +#include "libssh/priv.h" + +/** + * @defgroup ed25519 ed25519 API + * @internal + * @brief API for DJB's ed25519 + * + * @{ */ + +#define ED25519_PK_LEN 32 +#define ED25519_SK_LEN 64 +#define ED25519_SIG_LEN 64 + +typedef uint8_t ed25519_pubkey[ED25519_PK_LEN]; +typedef uint8_t ed25519_privkey[ED25519_SK_LEN]; +typedef uint8_t ed25519_signature[ED25519_SIG_LEN]; + +/** @internal + * @brief generate an ed25519 key pair + * @param[out] pk generated public key + * @param[out] sk generated secret key + * @return 0 on success, -1 on error. + * */ +int crypto_sign_ed25519_keypair(ed25519_pubkey pk, ed25519_privkey sk); + +/** @internal + * @brief sign a message with ed25519 + * @param[out] sm location to store the signed message. + * Its length should be mlen + 64. + * @param[out] smlen pointer to the size of the signed message + * @param[in] m message to be signed + * @param[in] mlen length of the message to be signed + * @param[in] sk secret key to sign the message with + * @return 0 on success. + */ +int crypto_sign_ed25519( + unsigned char *sm, uint64_t *smlen, + const unsigned char *m, uint64_t mlen, + const ed25519_privkey sk); + +/** @internal + * @brief "open" and verify the signature of a signed message + * @param[out] m location to store the verified message. + * Its length should be equal to smlen. + * @param[out] mlen pointer to the size of the verified message + * @param[in] sm signed message to verify + * @param[in] smlen length of the signed message to verify + * @param[in] pk public key used to sign the message + * @returns 0 on success (supposedly). + */ +int crypto_sign_ed25519_open( + unsigned char *m, uint64_t *mlen, + const unsigned char *sm, uint64_t smlen, + const ed25519_pubkey pk); + +/** @} */ +#endif /* ED25519_H_ */ diff --git a/include/libssh/fe25519.h b/include/libssh/fe25519.h new file mode 100644 index 0000000..438d85d --- /dev/null +++ b/include/libssh/fe25519.h @@ -0,0 +1,68 @@ +/* $OpenBSD: fe25519.h,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/fe25519.h + */ + +#ifndef FE25519_H +#define FE25519_H + +#include "libssh/priv.h" + +#define fe25519 crypto_sign_ed25519_ref_fe25519 +#define fe25519_freeze crypto_sign_ed25519_ref_fe25519_freeze +#define fe25519_unpack crypto_sign_ed25519_ref_fe25519_unpack +#define fe25519_pack crypto_sign_ed25519_ref_fe25519_pack +#define fe25519_iszero crypto_sign_ed25519_ref_fe25519_iszero +#define fe25519_iseq_vartime crypto_sign_ed25519_ref_fe25519_iseq_vartime +#define fe25519_cmov crypto_sign_ed25519_ref_fe25519_cmov +#define fe25519_setone crypto_sign_ed25519_ref_fe25519_setone +#define fe25519_setzero crypto_sign_ed25519_ref_fe25519_setzero +#define fe25519_neg crypto_sign_ed25519_ref_fe25519_neg +#define fe25519_getparity crypto_sign_ed25519_ref_fe25519_getparity +#define fe25519_add crypto_sign_ed25519_ref_fe25519_add +#define fe25519_sub crypto_sign_ed25519_ref_fe25519_sub +#define fe25519_mul crypto_sign_ed25519_ref_fe25519_mul +#define fe25519_square crypto_sign_ed25519_ref_fe25519_square +#define fe25519_invert crypto_sign_ed25519_ref_fe25519_invert +#define fe25519_pow2523 crypto_sign_ed25519_ref_fe25519_pow2523 + +typedef struct { + uint32_t v[32]; +} fe25519; + +void fe25519_freeze(fe25519 *r); + +void fe25519_unpack(fe25519 *r, const unsigned char x[32]); + +void fe25519_pack(unsigned char r[32], const fe25519 *x); + +uint32_t fe25519_iszero(const fe25519 *x); + +int fe25519_iseq_vartime(const fe25519 *x, const fe25519 *y); + +void fe25519_cmov(fe25519 *r, const fe25519 *x, unsigned char b); + +void fe25519_setone(fe25519 *r); + +void fe25519_setzero(fe25519 *r); + +void fe25519_neg(fe25519 *r, const fe25519 *x); + +unsigned char fe25519_getparity(const fe25519 *x); + +void fe25519_add(fe25519 *r, const fe25519 *x, const fe25519 *y); + +void fe25519_sub(fe25519 *r, const fe25519 *x, const fe25519 *y); + +void fe25519_mul(fe25519 *r, const fe25519 *x, const fe25519 *y); + +void fe25519_square(fe25519 *r, const fe25519 *x); + +void fe25519_invert(fe25519 *r, const fe25519 *x); + +void fe25519_pow2523(fe25519 *r, const fe25519 *x); + +#endif diff --git a/include/libssh/ge25519.h b/include/libssh/ge25519.h new file mode 100644 index 0000000..329bd04 --- /dev/null +++ b/include/libssh/ge25519.h @@ -0,0 +1,43 @@ +/* $OpenBSD: ge25519.h,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/ge25519.h + */ + +#ifndef GE25519_H +#define GE25519_H + +#include "fe25519.h" +#include "sc25519.h" + +#define ge25519 crypto_sign_ed25519_ref_ge25519 +#define ge25519_base crypto_sign_ed25519_ref_ge25519_base +#define ge25519_unpackneg_vartime crypto_sign_ed25519_ref_unpackneg_vartime +#define ge25519_pack crypto_sign_ed25519_ref_pack +#define ge25519_isneutral_vartime crypto_sign_ed25519_ref_isneutral_vartime +#define ge25519_double_scalarmult_vartime crypto_sign_ed25519_ref_double_scalarmult_vartime +#define ge25519_scalarmult_base crypto_sign_ed25519_ref_scalarmult_base + +typedef struct +{ + fe25519 x; + fe25519 y; + fe25519 z; + fe25519 t; +} ge25519; + +extern const ge25519 ge25519_base; + +int ge25519_unpackneg_vartime(ge25519 *r, const unsigned char p[32]); + +void ge25519_pack(unsigned char r[32], const ge25519 *p); + +int ge25519_isneutral_vartime(const ge25519 *p); + +void ge25519_double_scalarmult_vartime(ge25519 *r, const ge25519 *p1, const sc25519 *s1, const ge25519 *p2, const sc25519 *s2); + +void ge25519_scalarmult_base(ge25519 *r, const sc25519 *s); + +#endif diff --git a/include/libssh/gssapi.h b/include/libssh/gssapi.h new file mode 100644 index 0000000..ccd8366 --- /dev/null +++ b/include/libssh/gssapi.h @@ -0,0 +1,45 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * + * 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 + */ + +#ifndef GSSAPI_H_ +#define GSSAPI_H_ + +#include "config.h" +#include "session.h" + +/* all OID begin with the tag identifier + length */ +#define SSH_OID_TAG 06 + +typedef struct ssh_gssapi_struct *ssh_gssapi; + +#ifdef WITH_SERVER +int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n_oid, ssh_string *oids); +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server); +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic); +#endif /* WITH_SERVER */ + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token); +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client); +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response); + + +int ssh_gssapi_auth_mic(ssh_session session); + +#endif /* GSSAPI_H */ diff --git a/include/libssh/kex.h b/include/libssh/kex.h new file mode 100644 index 0000000..3a1f4a6 --- /dev/null +++ b/include/libssh/kex.h @@ -0,0 +1,59 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef KEX_H_ +#define KEX_H_ + +#include "libssh/priv.h" +#include "libssh/callbacks.h" + +#define SSH_KEX_METHODS 10 + +struct ssh_kex_struct { + unsigned char cookie[16]; + char *methods[SSH_KEX_METHODS]; +}; + +SSH_PACKET_CALLBACK(ssh_packet_kexinit); + +int ssh_send_kex(ssh_session session, int server_kex); +void ssh_list_kex(struct ssh_kex_struct *kex); +int ssh_set_client_kex(ssh_session session); +int ssh_kex_select_methods(ssh_session session); +int ssh_verify_existing_algo(enum ssh_kex_types_e algo, const char *name); +char *ssh_keep_known_algos(enum ssh_kex_types_e algo, const char *list); +char *ssh_keep_fips_algos(enum ssh_kex_types_e algo, const char *list); +char **ssh_space_tokenize(const char *chain); +int ssh_get_kex1(ssh_session session); +char *ssh_find_matching(const char *in_d, const char *what_d); +const char *ssh_kex_get_supported_method(uint32_t algo); +const char *ssh_kex_get_default_methods(uint32_t algo); +const char *ssh_kex_get_fips_methods(uint32_t algo); +const char *ssh_kex_get_description(uint32_t algo); +char *ssh_client_select_hostkeys(ssh_session session); +int ssh_send_rekex(ssh_session session); +int server_set_kex(ssh_session session); +int ssh_make_sessionid(ssh_session session); +/* add data for the final cookie */ +int ssh_hashbufin_add_cookie(ssh_session session, unsigned char *cookie); +int ssh_hashbufout_add_cookie(ssh_session session); +int ssh_generate_session_keys(ssh_session session); + +#endif /* KEX_H_ */ diff --git a/include/libssh/keys.h b/include/libssh/keys.h new file mode 100644 index 0000000..7b13861 --- /dev/null +++ b/include/libssh/keys.h @@ -0,0 +1,62 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef KEYS_H_ +#define KEYS_H_ + +#include "config.h" +#include "libssh/libssh.h" +#include "libssh/wrapper.h" + +struct ssh_public_key_struct { + int type; + const char *type_c; /* Don't free it ! it is static */ +#if defined(HAVE_LIBGCRYPT) + gcry_sexp_t dsa_pub; + gcry_sexp_t rsa_pub; +#elif defined(HAVE_LIBCRYPTO) + DSA *dsa_pub; + RSA *rsa_pub; +#elif defined(HAVE_LIBMBEDCRYPTO) + mbedtls_pk_context *rsa_pub; + void *dsa_pub; +#endif +}; + +struct ssh_private_key_struct { + int type; +#if defined(HAVE_LIBGCRYPT) + gcry_sexp_t dsa_priv; + gcry_sexp_t rsa_priv; +#elif defined(HAVE_LIBCRYPTO) + DSA *dsa_priv; + RSA *rsa_priv; +#elif defined(HAVE_LIBMBEDCRYPTO) + mbedtls_pk_context *rsa_priv; + void *dsa_priv; +#endif +}; + +const char *ssh_type_to_char(int type); +int ssh_type_from_name(const char *name); + +ssh_public_key publickey_from_string(ssh_session session, ssh_string pubkey_s); + +#endif /* KEYS_H_ */ diff --git a/include/libssh/knownhosts.h b/include/libssh/knownhosts.h new file mode 100644 index 0000000..44e434c --- /dev/null +++ b/include/libssh/knownhosts.h @@ -0,0 +1,32 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 20014 by Aris Adamantiadis + * + * 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. + * + * 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 + */ + + +#ifndef SSH_KNOWNHOSTS_H_ +#define SSH_KNOWNHOSTS_H_ + +struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session); +char *ssh_known_hosts_get_algorithms_names(ssh_session session); +enum ssh_known_hosts_e +ssh_session_get_known_hosts_entry_file(ssh_session session, + const char *filename, + struct ssh_knownhosts_entry **pentry); + +#endif /* SSH_KNOWNHOSTS_H_ */ diff --git a/include/libssh/legacy.h b/include/libssh/legacy.h new file mode 100644 index 0000000..911173e --- /dev/null +++ b/include/libssh/legacy.h @@ -0,0 +1,120 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * 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 + */ + +/* Since libssh.h includes legacy.h, it's important that libssh.h is included + * first. we don't define LEGACY_H now because we want it to be defined when + * included from libssh.h + * All function calls declared in this header are deprecated and meant to be + * removed in future. + */ + +#ifndef LEGACY_H_ +#define LEGACY_H_ + +typedef struct ssh_private_key_struct* ssh_private_key; +typedef struct ssh_public_key_struct* ssh_public_key; + +LIBSSH_API int ssh_auth_list(ssh_session session); +LIBSSH_API int ssh_userauth_offer_pubkey(ssh_session session, const char *username, int type, ssh_string publickey); +LIBSSH_API int ssh_userauth_pubkey(ssh_session session, const char *username, ssh_string publickey, ssh_private_key privatekey); +#ifndef _WIN32 +LIBSSH_API int ssh_userauth_agent_pubkey(ssh_session session, const char *username, + ssh_public_key publickey); +#endif +LIBSSH_API int ssh_userauth_autopubkey(ssh_session session, const char *passphrase); +LIBSSH_API int ssh_userauth_privatekey_file(ssh_session session, const char *username, + const char *filename, const char *passphrase); + +SSH_DEPRECATED LIBSSH_API void buffer_free(ssh_buffer buffer); +SSH_DEPRECATED LIBSSH_API void *buffer_get(ssh_buffer buffer); +SSH_DEPRECATED LIBSSH_API uint32_t buffer_get_len(ssh_buffer buffer); +SSH_DEPRECATED LIBSSH_API ssh_buffer buffer_new(void); + +SSH_DEPRECATED LIBSSH_API ssh_channel channel_accept_x11(ssh_channel channel, int timeout_ms); +SSH_DEPRECATED LIBSSH_API int channel_change_pty_size(ssh_channel channel,int cols,int rows); +SSH_DEPRECATED LIBSSH_API ssh_channel channel_forward_accept(ssh_session session, int timeout_ms); +SSH_DEPRECATED LIBSSH_API int channel_close(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_forward_cancel(ssh_session session, const char *address, int port); +SSH_DEPRECATED LIBSSH_API int channel_forward_listen(ssh_session session, const char *address, int port, int *bound_port); +SSH_DEPRECATED LIBSSH_API void channel_free(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_get_exit_status(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API ssh_session channel_get_session(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_is_closed(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_is_eof(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_is_open(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API ssh_channel channel_new(ssh_session session); +SSH_DEPRECATED LIBSSH_API int channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +SSH_DEPRECATED LIBSSH_API int channel_open_session(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_poll(ssh_channel channel, int is_stderr); +SSH_DEPRECATED LIBSSH_API int channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr); + +SSH_DEPRECATED LIBSSH_API int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, + int is_stderr); + +SSH_DEPRECATED LIBSSH_API int channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr); +SSH_DEPRECATED LIBSSH_API int channel_request_env(ssh_channel channel, const char *name, const char *value); +SSH_DEPRECATED LIBSSH_API int channel_request_exec(ssh_channel channel, const char *cmd); +SSH_DEPRECATED LIBSSH_API int channel_request_pty(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_request_pty_size(ssh_channel channel, const char *term, + int cols, int rows); +SSH_DEPRECATED LIBSSH_API int channel_request_shell(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_request_send_signal(ssh_channel channel, const char *signum); +SSH_DEPRECATED LIBSSH_API int channel_request_sftp(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_request_subsystem(ssh_channel channel, const char *subsystem); +SSH_DEPRECATED LIBSSH_API int channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number); +SSH_DEPRECATED LIBSSH_API int channel_send_eof(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout); +SSH_DEPRECATED LIBSSH_API void channel_set_blocking(ssh_channel channel, int blocking); +SSH_DEPRECATED LIBSSH_API int channel_write(ssh_channel channel, const void *data, uint32_t len); + +LIBSSH_API void privatekey_free(ssh_private_key prv); +LIBSSH_API ssh_private_key privatekey_from_file(ssh_session session, const char *filename, + int type, const char *passphrase); +LIBSSH_API void publickey_free(ssh_public_key key); +LIBSSH_API int ssh_publickey_to_file(ssh_session session, const char *file, + ssh_string pubkey, int type); +LIBSSH_API ssh_string publickey_from_file(ssh_session session, const char *filename, + int *type); +LIBSSH_API ssh_public_key publickey_from_privatekey(ssh_private_key prv); +LIBSSH_API ssh_string publickey_to_string(ssh_public_key key); +LIBSSH_API int ssh_try_publickey_from_file(ssh_session session, const char *keyfile, + ssh_string *publickey, int *type); +LIBSSH_API enum ssh_keytypes_e ssh_privatekey_type(ssh_private_key privatekey); + +LIBSSH_API ssh_string ssh_get_pubkey(ssh_session session); + +LIBSSH_API ssh_message ssh_message_retrieve(ssh_session session, uint32_t packettype); +LIBSSH_API ssh_public_key ssh_message_auth_publickey(ssh_message msg); + +SSH_DEPRECATED LIBSSH_API void string_burn(ssh_string str); +SSH_DEPRECATED LIBSSH_API ssh_string string_copy(ssh_string str); +SSH_DEPRECATED LIBSSH_API void *string_data(ssh_string str); +SSH_DEPRECATED LIBSSH_API int string_fill(ssh_string str, const void *data, size_t len); +SSH_DEPRECATED LIBSSH_API void string_free(ssh_string str); +SSH_DEPRECATED LIBSSH_API ssh_string string_from_char(const char *what); +SSH_DEPRECATED LIBSSH_API size_t string_len(ssh_string str); +SSH_DEPRECATED LIBSSH_API ssh_string string_new(size_t size); +SSH_DEPRECATED LIBSSH_API char *string_to_char(ssh_string str); + +#endif /* LEGACY_H_ */ diff --git a/include/libssh/libcrypto.h b/include/libssh/libcrypto.h new file mode 100644 index 0000000..4117942 --- /dev/null +++ b/include/libssh/libcrypto.h @@ -0,0 +1,123 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef LIBCRYPTO_H_ +#define LIBCRYPTO_H_ + +#include "config.h" + +#ifdef HAVE_LIBCRYPTO + +#include +#include +#include +#include +#include +#include +#include + +typedef EVP_MD_CTX* SHACTX; +typedef EVP_MD_CTX* SHA256CTX; +typedef EVP_MD_CTX* SHA384CTX; +typedef EVP_MD_CTX* SHA512CTX; +typedef EVP_MD_CTX* MD5CTX; +typedef HMAC_CTX* HMACCTX; +#ifdef HAVE_ECC +typedef EVP_MD_CTX *EVPCTX; +#else +typedef void *EVPCTX; +#endif + +#define SHA_DIGEST_LEN SHA_DIGEST_LENGTH +#define SHA256_DIGEST_LEN SHA256_DIGEST_LENGTH +#define SHA384_DIGEST_LEN SHA384_DIGEST_LENGTH +#define SHA512_DIGEST_LEN SHA512_DIGEST_LENGTH +#ifdef MD5_DIGEST_LEN + #undef MD5_DIGEST_LEN +#endif +#define MD5_DIGEST_LEN MD5_DIGEST_LENGTH + +#ifdef HAVE_OPENSSL_ECC +#define EVP_DIGEST_LEN EVP_MAX_MD_SIZE +#endif + +#include +#include +#define OPENSSL_0_9_7b 0x0090702fL +#if (OPENSSL_VERSION_NUMBER <= OPENSSL_0_9_7b) +#define BROKEN_AES_CTR +#endif +typedef BIGNUM* bignum; +typedef const BIGNUM* const_bignum; +typedef BN_CTX* bignum_CTX; + +#define bignum_new() BN_new() +#define bignum_safe_free(num) do { \ + if ((num) != NULL) { \ + BN_clear_free((num)); \ + (num)=NULL; \ + } \ + } while(0) +#define bignum_set_word(bn,n) BN_set_word(bn,n) +#define bignum_bin2bn(data, datalen, dest) \ + do { \ + (*dest) = BN_new(); \ + if ((*dest) != NULL) { \ + BN_bin2bn(data,datalen,(*dest)); \ + } \ + } while(0) +#define bignum_bn2dec(num) BN_bn2dec(num) +#define bignum_dec2bn(data, bn) BN_dec2bn(bn, data) +#define bignum_hex2bn(data, bn) BN_hex2bn(bn, data) +#define bignum_bn2hex(num, dest) (*dest)=(unsigned char *)BN_bn2hex(num) +#define bignum_rand(rnd, bits) BN_rand(rnd, bits, 0, 1) +#define bignum_rand_range(rnd, max) BN_rand_range(rnd, max) +#define bignum_ctx_new() BN_CTX_new() +#define bignum_ctx_free(num) BN_CTX_free(num) +#define bignum_ctx_invalid(ctx) ((ctx) == NULL) +#define bignum_mod_exp(dest,generator,exp,modulo,ctx) BN_mod_exp(dest,generator,exp,modulo,ctx) +#define bignum_add(dest, a, b) BN_add(dest, a, b) +#define bignum_sub(dest, a, b) BN_sub(dest, a, b) +#define bignum_mod(dest, a, b, ctx) BN_mod(dest, a, b, ctx) +#define bignum_num_bytes(num) (size_t)BN_num_bytes(num) +#define bignum_num_bits(num) (size_t)BN_num_bits(num) +#define bignum_is_bit_set(num,bit) BN_is_bit_set(num, (int)bit) +#define bignum_bn2bin(num,len, ptr) BN_bn2bin(num, ptr) +#define bignum_cmp(num1,num2) BN_cmp(num1,num2) +#define bignum_rshift1(dest, src) BN_rshift1(dest, src) +#define bignum_dup(orig, dest) do { \ + if (*(dest) == NULL) { \ + *(dest) = BN_dup(orig); \ + } else { \ + BN_copy(*(dest), orig); \ + } \ + } while(0) + + +/* Returns true if the OpenSSL is operating in FIPS mode */ +#ifdef HAVE_OPENSSL_FIPS_MODE +#define ssh_fips_mode() (FIPS_mode() != 0) +#else +#define ssh_fips_mode() false +#endif + +#endif /* HAVE_LIBCRYPTO */ + +#endif /* LIBCRYPTO_H_ */ diff --git a/include/libssh/libgcrypt.h b/include/libssh/libgcrypt.h new file mode 100644 index 0000000..347d851 --- /dev/null +++ b/include/libssh/libgcrypt.h @@ -0,0 +1,119 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef LIBGCRYPT_H_ +#define LIBGCRYPT_H_ + +#include "config.h" + +#ifdef HAVE_LIBGCRYPT + +#include +typedef gcry_md_hd_t SHACTX; +typedef gcry_md_hd_t SHA256CTX; +typedef gcry_md_hd_t SHA384CTX; +typedef gcry_md_hd_t SHA512CTX; +typedef gcry_md_hd_t MD5CTX; +typedef gcry_md_hd_t HMACCTX; +typedef gcry_md_hd_t EVPCTX; +#define SHA_DIGEST_LENGTH 20 +#define SHA_DIGEST_LEN SHA_DIGEST_LENGTH +#define MD5_DIGEST_LEN 16 +#define SHA256_DIGEST_LENGTH 32 +#define SHA256_DIGEST_LEN SHA256_DIGEST_LENGTH +#define SHA384_DIGEST_LENGTH 48 +#define SHA384_DIGEST_LEN SHA384_DIGEST_LENGTH +#define SHA512_DIGEST_LENGTH 64 +#define SHA512_DIGEST_LEN SHA512_DIGEST_LENGTH + +#ifndef EVP_MAX_MD_SIZE +#define EVP_MAX_MD_SIZE 64 +#endif + +#define EVP_DIGEST_LEN EVP_MAX_MD_SIZE + +typedef gcry_mpi_t bignum; +typedef const struct gcry_mpi *const_bignum; +typedef void* bignum_CTX; + +/* Constants for curves. */ +#define NID_gcrypt_nistp256 0 +#define NID_gcrypt_nistp384 1 +#define NID_gcrypt_nistp521 2 + +/* missing gcrypt functions */ +int ssh_gcry_dec2bn(bignum *bn, const char *data); +char *ssh_gcry_bn2dec(bignum bn); +int ssh_gcry_rand_range(bignum rnd, bignum max); + +#define bignum_new() gcry_mpi_new(0) +#define bignum_safe_free(num) do { \ + if ((num) != NULL) { \ + gcry_mpi_release((num)); \ + (num)=NULL; \ + } \ + } while (0) +#define bignum_free(num) gcry_mpi_release(num) +#define bignum_ctx_new() NULL +#define bignum_ctx_free(ctx) do {(ctx) = NULL;} while(0) +#define bignum_ctx_invalid(ctx) (ctx != NULL) +#define bignum_set_word(bn,n) (gcry_mpi_set_ui(bn,n)!=NULL ? 1 : 0) +#define bignum_bin2bn(data,datalen,dest) gcry_mpi_scan(dest,GCRYMPI_FMT_USG,data,datalen,NULL) +#define bignum_bn2dec(num) ssh_gcry_bn2dec(num) +#define bignum_dec2bn(num, data) ssh_gcry_dec2bn(data, num) + +#define bignum_bn2hex(num, data) \ + gcry_mpi_aprint(GCRYMPI_FMT_HEX, data, NULL, (const gcry_mpi_t)num) + +#define bignum_hex2bn(data, num) (gcry_mpi_scan(num,GCRYMPI_FMT_HEX,data,0,NULL)==0?1:0) +#define bignum_rand(num,bits) 1,gcry_mpi_randomize(num,bits,GCRY_STRONG_RANDOM),gcry_mpi_set_bit(num,bits-1),gcry_mpi_set_bit(num,0) +#define bignum_mod_exp(dest,generator,exp,modulo, ctx) 1,gcry_mpi_powm(dest,generator,exp,modulo) +#define bignum_num_bits(num) gcry_mpi_get_nbits(num) +#define bignum_num_bytes(num) ((gcry_mpi_get_nbits(num)+7)/8) +#define bignum_is_bit_set(num,bit) gcry_mpi_test_bit(num,bit) +#define bignum_bn2bin(num,datalen,data) gcry_mpi_print(GCRYMPI_FMT_USG,data,datalen,NULL,num) +#define bignum_cmp(num1,num2) gcry_mpi_cmp(num1,num2) +#define bignum_rshift1(dest, src) gcry_mpi_rshift (dest, src, 1) +#define bignum_add(dst, a, b) gcry_mpi_add(dst, a, b) +#define bignum_sub(dst, a, b) gcry_mpi_sub(dst, a, b) +#define bignum_mod(dst, a, b, ctx) 1,gcry_mpi_mod(dst, a, b) +#define bignum_rand_range(rnd, max) ssh_gcry_rand_range(rnd, max); +#define bignum_dup(orig, dest) do { \ + if (*(dest) == NULL) { \ + *(dest) = gcry_mpi_copy(orig); \ + } else { \ + gcry_mpi_set(*(dest), orig); \ + } \ + } while(0) +/* Helper functions for data conversions. */ + +/* Extract an MPI from the given s-expression SEXP named NAME which is + encoded using INFORMAT and store it in a newly allocated ssh_string + encoded using OUTFORMAT. */ +ssh_string ssh_sexp_extract_mpi(const gcry_sexp_t sexp, + const char *name, + enum gcry_mpi_format informat, + enum gcry_mpi_format outformat); + +#define ssh_fips_mode() false + +#endif /* HAVE_LIBGCRYPT */ + +#endif /* LIBGCRYPT_H_ */ diff --git a/include/libssh/libmbedcrypto.h b/include/libssh/libmbedcrypto.h new file mode 100644 index 0000000..fe53019 --- /dev/null +++ b/include/libssh/libmbedcrypto.h @@ -0,0 +1,140 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * 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. + */ + +#ifndef LIBMBEDCRYPTO_H_ +#define LIBMBEDCRYPTO_H_ + +#include "config.h" + +#ifdef HAVE_LIBMBEDCRYPTO + +#include +#include +#include +#include +#include +#include + +typedef mbedtls_md_context_t *SHACTX; +typedef mbedtls_md_context_t *SHA256CTX; +typedef mbedtls_md_context_t *SHA384CTX; +typedef mbedtls_md_context_t *SHA512CTX; +typedef mbedtls_md_context_t *MD5CTX; +typedef mbedtls_md_context_t *HMACCTX; +typedef mbedtls_md_context_t *EVPCTX; + +#define SHA_DIGEST_LENGTH 20 +#define SHA_DIGEST_LEN SHA_DIGEST_LENGTH +#define MD5_DIGEST_LEN 16 +#define SHA256_DIGEST_LENGTH 32 +#define SHA256_DIGEST_LEN SHA256_DIGEST_LENGTH +#define SHA384_DIGEST_LENGTH 48 +#define SHA384_DIGEST_LEN SHA384_DIGEST_LENGTH +#define SHA512_DIGEST_LENGTH 64 +#define SHA512_DIGEST_LEN SHA512_DIGEST_LENGTH + +#ifndef EVP_MAX_MD_SIZE +#define EVP_MAX_MD_SIZE 64 +#endif + +#define EVP_DIGEST_LEN EVP_MAX_MD_SIZE + +typedef mbedtls_mpi *bignum; +typedef const mbedtls_mpi *const_bignum; +typedef void* bignum_CTX; + +/* Constants for curves */ +#define NID_mbedtls_nistp256 0 +#define NID_mbedtls_nistp384 1 +#define NID_mbedtls_nistp521 2 + +struct mbedtls_ecdsa_sig { + bignum r; + bignum s; +}; + +bignum ssh_mbedcry_bn_new(void); +void ssh_mbedcry_bn_free(bignum num); +unsigned char *ssh_mbedcry_bn2num(const_bignum num, int radix); +int ssh_mbedcry_rand(bignum rnd, int bits, int top, int bottom); +int ssh_mbedcry_is_bit_set(bignum num, size_t pos); +int ssh_mbedcry_rand_range(bignum dest, bignum max); +int ssh_mbedcry_hex2bn(bignum *dest, char *data); + +#define bignum_new() ssh_mbedcry_bn_new() +#define bignum_safe_free(num) do { \ + if ((num) != NULL) { \ + ssh_mbedcry_bn_free(num); \ + (num)=NULL; \ + } \ + } while(0) +#define bignum_ctx_new() NULL +#define bignum_ctx_free(num) do {(num) = NULL;} while(0) +#define bignum_ctx_invalid(ctx) (ctx == NULL?0:1) +#define bignum_set_word(bn, n) (mbedtls_mpi_lset(bn, n)==0?1:0) /* TODO fix + overflow/underflow */ +#define bignum_bin2bn(data, datalen, bn) do { \ + *(bn) = bignum_new(); \ + if (*(bn) != NULL) { \ + mbedtls_mpi_read_binary(*(bn), data, datalen); \ + } \ + } while(0) +#define bignum_bn2dec(num) ssh_mbedcry_bn2num(num, 10) +#define bignum_dec2bn(data, bn) mbedtls_mpi_read_string(bn, 10, data) +#define bignum_bn2hex(num, dest) (*dest)=ssh_mbedcry_bn2num(num, 16) +#define bignum_hex2bn(data, dest) ssh_mbedcry_hex2bn(dest, data) +#define bignum_rand(rnd, bits) ssh_mbedcry_rand((rnd), (bits), 0, 1) +#define bignum_rand_range(rnd, max) ssh_mbedcry_rand_range(rnd, max) +#define bignum_mod_exp(dest, generator, exp, modulo, ctx) \ + (mbedtls_mpi_exp_mod(dest, generator, exp, modulo, NULL)==0?1:0) +#define bignum_add(dest, a, b) mbedtls_mpi_add_mpi(dest, a, b) +#define bignum_sub(dest, a, b) mbedtls_mpi_sub_mpi(dest, a, b) +#define bignum_mod(dest, a, b, ctx) \ + (mbedtls_mpi_mod_mpi(dest, a, b) == 0 ? 1 : 0) +#define bignum_num_bytes(num) mbedtls_mpi_size(num) +#define bignum_num_bits(num) mbedtls_mpi_bitlen(num) +#define bignum_is_bit_set(num, bit) ssh_mbedcry_is_bit_set(num, bit) +#define bignum_bn2bin(num, len, ptr) mbedtls_mpi_write_binary(num, ptr, \ + mbedtls_mpi_size(num)) +#define bignum_cmp(num1, num2) mbedtls_mpi_cmp_mpi(num1, num2) +#define bignum_rshift1(dest, src) mbedtls_mpi_copy(dest, src), mbedtls_mpi_shift_r(dest, 1) +#define bignum_dup(orig, dest) do { \ + if (*(dest) == NULL) { \ + *(dest) = bignum_new(); \ + } \ + if (*(dest) != NULL) { \ + mbedtls_mpi_copy(orig, *(dest)); \ + } \ + } while(0) + +mbedtls_ctr_drbg_context *ssh_get_mbedtls_ctr_drbg_context(void); + +int ssh_mbedtls_random(void *where, int len, int strong); + +ssh_string make_ecpoint_string(const mbedtls_ecp_group *g, const + mbedtls_ecp_point *p); + +#define ssh_fips_mode() false + +#endif /* HAVE_LIBMBEDCRYPTO */ +#endif /* LIBMBEDCRYPTO_H_ */ diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h new file mode 100644 index 0000000..7903013 --- /dev/null +++ b/include/libssh/libssh.h @@ -0,0 +1,847 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef _LIBSSH_H +#define _LIBSSH_H + +#if defined _WIN32 || defined __CYGWIN__ + #ifdef LIBSSH_STATIC + #define LIBSSH_API + #else + #ifdef LIBSSH_EXPORTS + #ifdef __GNUC__ + #define LIBSSH_API __attribute__((dllexport)) + #else + #define LIBSSH_API __declspec(dllexport) + #endif + #else + #ifdef __GNUC__ + #define LIBSSH_API __attribute__((dllimport)) + #else + #define LIBSSH_API __declspec(dllimport) + #endif + #endif + #endif +#else + #if __GNUC__ >= 4 && !defined(__OS2__) + #define LIBSSH_API __attribute__((visibility("default"))) + #else + #define LIBSSH_API + #endif +#endif + +#ifdef _MSC_VER + /* Visual Studio hasn't inttypes.h so it doesn't know uint32_t */ + typedef int int32_t; + typedef unsigned int uint32_t; + typedef unsigned short uint16_t; + typedef unsigned char uint8_t; + typedef unsigned long long uint64_t; + typedef int mode_t; +#else /* _MSC_VER */ + #include + #include + #include +#endif /* _MSC_VER */ + +#ifdef _WIN32 + #include +#else /* _WIN32 */ + #include /* for fd_set * */ + #include +#endif /* _WIN32 */ + +#define SSH_STRINGIFY(s) SSH_TOSTRING(s) +#define SSH_TOSTRING(s) #s + +/* libssh version macros */ +#define SSH_VERSION_INT(a, b, c) ((a) << 16 | (b) << 8 | (c)) +#define SSH_VERSION_DOT(a, b, c) a ##.## b ##.## c +#define SSH_VERSION(a, b, c) SSH_VERSION_DOT(a, b, c) + +/* libssh version */ +#define LIBSSH_VERSION_MAJOR 0 +#define LIBSSH_VERSION_MINOR 9 +#define LIBSSH_VERSION_MICRO 3 + +#define LIBSSH_VERSION_INT SSH_VERSION_INT(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) +#define LIBSSH_VERSION SSH_VERSION(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) + +/* GCC have printf type attribute check. */ +#ifdef __GNUC__ +#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) +#else +#define PRINTF_ATTRIBUTE(a,b) +#endif /* __GNUC__ */ + +#ifdef __GNUC__ +#define SSH_DEPRECATED __attribute__ ((deprecated)) +#else +#define SSH_DEPRECATED +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssh_counter_struct { + uint64_t in_bytes; + uint64_t out_bytes; + uint64_t in_packets; + uint64_t out_packets; +}; +typedef struct ssh_counter_struct *ssh_counter; + +typedef struct ssh_agent_struct* ssh_agent; +typedef struct ssh_buffer_struct* ssh_buffer; +typedef struct ssh_channel_struct* ssh_channel; +typedef struct ssh_message_struct* ssh_message; +typedef struct ssh_pcap_file_struct* ssh_pcap_file; +typedef struct ssh_key_struct* ssh_key; +typedef struct ssh_scp_struct* ssh_scp; +typedef struct ssh_session_struct* ssh_session; +typedef struct ssh_string_struct* ssh_string; +typedef struct ssh_event_struct* ssh_event; +typedef struct ssh_connector_struct * ssh_connector; +typedef void* ssh_gssapi_creds; + +/* Socket type */ +#ifdef _WIN32 +#ifndef socket_t +typedef SOCKET socket_t; +#endif /* socket_t */ +#else /* _WIN32 */ +#ifndef socket_t +typedef int socket_t; +#endif +#endif /* _WIN32 */ + +#define SSH_INVALID_SOCKET ((socket_t) -1) + +/* the offsets of methods */ +enum ssh_kex_types_e { + SSH_KEX=0, + SSH_HOSTKEYS, + SSH_CRYPT_C_S, + SSH_CRYPT_S_C, + SSH_MAC_C_S, + SSH_MAC_S_C, + SSH_COMP_C_S, + SSH_COMP_S_C, + SSH_LANG_C_S, + SSH_LANG_S_C +}; + +#define SSH_CRYPT 2 +#define SSH_MAC 3 +#define SSH_COMP 4 +#define SSH_LANG 5 + +enum ssh_auth_e { + SSH_AUTH_SUCCESS=0, + SSH_AUTH_DENIED, + SSH_AUTH_PARTIAL, + SSH_AUTH_INFO, + SSH_AUTH_AGAIN, + SSH_AUTH_ERROR=-1 +}; + +/* auth flags */ +#define SSH_AUTH_METHOD_UNKNOWN 0x0000u +#define SSH_AUTH_METHOD_NONE 0x0001u +#define SSH_AUTH_METHOD_PASSWORD 0x0002u +#define SSH_AUTH_METHOD_PUBLICKEY 0x0004u +#define SSH_AUTH_METHOD_HOSTBASED 0x0008u +#define SSH_AUTH_METHOD_INTERACTIVE 0x0010u +#define SSH_AUTH_METHOD_GSSAPI_MIC 0x0020u + +/* messages */ +enum ssh_requests_e { + SSH_REQUEST_AUTH=1, + SSH_REQUEST_CHANNEL_OPEN, + SSH_REQUEST_CHANNEL, + SSH_REQUEST_SERVICE, + SSH_REQUEST_GLOBAL +}; + +enum ssh_channel_type_e { + SSH_CHANNEL_UNKNOWN=0, + SSH_CHANNEL_SESSION, + SSH_CHANNEL_DIRECT_TCPIP, + SSH_CHANNEL_FORWARDED_TCPIP, + SSH_CHANNEL_X11, + SSH_CHANNEL_AUTH_AGENT +}; + +enum ssh_channel_requests_e { + SSH_CHANNEL_REQUEST_UNKNOWN=0, + SSH_CHANNEL_REQUEST_PTY, + SSH_CHANNEL_REQUEST_EXEC, + SSH_CHANNEL_REQUEST_SHELL, + SSH_CHANNEL_REQUEST_ENV, + SSH_CHANNEL_REQUEST_SUBSYSTEM, + SSH_CHANNEL_REQUEST_WINDOW_CHANGE, + SSH_CHANNEL_REQUEST_X11 +}; + +enum ssh_global_requests_e { + SSH_GLOBAL_REQUEST_UNKNOWN=0, + SSH_GLOBAL_REQUEST_TCPIP_FORWARD, + SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD, + SSH_GLOBAL_REQUEST_KEEPALIVE +}; + +enum ssh_publickey_state_e { + SSH_PUBLICKEY_STATE_ERROR=-1, + SSH_PUBLICKEY_STATE_NONE=0, + SSH_PUBLICKEY_STATE_VALID=1, + SSH_PUBLICKEY_STATE_WRONG=2 +}; + +/* Status flags */ +/** Socket is closed */ +#define SSH_CLOSED 0x01 +/** Reading to socket won't block */ +#define SSH_READ_PENDING 0x02 +/** Session was closed due to an error */ +#define SSH_CLOSED_ERROR 0x04 +/** Output buffer not empty */ +#define SSH_WRITE_PENDING 0x08 + +enum ssh_server_known_e { + SSH_SERVER_ERROR=-1, + SSH_SERVER_NOT_KNOWN=0, + SSH_SERVER_KNOWN_OK, + SSH_SERVER_KNOWN_CHANGED, + SSH_SERVER_FOUND_OTHER, + SSH_SERVER_FILE_NOT_FOUND +}; + +enum ssh_known_hosts_e { + /** + * There had been an error checking the host. + */ + SSH_KNOWN_HOSTS_ERROR = -2, + + /** + * The known host file does not exist. The host is thus unknown. File will + * be created if host key is accepted. + */ + SSH_KNOWN_HOSTS_NOT_FOUND = -1, + + /** + * The server is unknown. User should confirm the public key hash is + * correct. + */ + SSH_KNOWN_HOSTS_UNKNOWN = 0, + + /** + * The server is known and has not changed. + */ + SSH_KNOWN_HOSTS_OK, + + /** + * The server key has changed. Either you are under attack or the + * administrator changed the key. You HAVE to warn the user about a + * possible attack. + */ + SSH_KNOWN_HOSTS_CHANGED, + + /** + * The server gave use a key of a type while we had an other type recorded. + * It is a possible attack. + */ + SSH_KNOWN_HOSTS_OTHER, +}; + +#ifndef MD5_DIGEST_LEN + #define MD5_DIGEST_LEN 16 +#endif +/* errors */ + +enum ssh_error_types_e { + SSH_NO_ERROR=0, + SSH_REQUEST_DENIED, + SSH_FATAL, + SSH_EINTR +}; + +/* some types for keys */ +enum ssh_keytypes_e{ + SSH_KEYTYPE_UNKNOWN=0, + SSH_KEYTYPE_DSS=1, + SSH_KEYTYPE_RSA, + SSH_KEYTYPE_RSA1, + SSH_KEYTYPE_ECDSA, /* deprecated */ + SSH_KEYTYPE_ED25519, + SSH_KEYTYPE_DSS_CERT01, + SSH_KEYTYPE_RSA_CERT01, + SSH_KEYTYPE_ECDSA_P256, + SSH_KEYTYPE_ECDSA_P384, + SSH_KEYTYPE_ECDSA_P521, + SSH_KEYTYPE_ECDSA_P256_CERT01, + SSH_KEYTYPE_ECDSA_P384_CERT01, + SSH_KEYTYPE_ECDSA_P521_CERT01, + SSH_KEYTYPE_ED25519_CERT01, +}; + +enum ssh_keycmp_e { + SSH_KEY_CMP_PUBLIC = 0, + SSH_KEY_CMP_PRIVATE +}; + +#define SSH_ADDRSTRLEN 46 + +struct ssh_knownhosts_entry { + char *hostname; + char *unparsed; + ssh_key publickey; + char *comment; +}; + + +/* Error return codes */ +#define SSH_OK 0 /* No error */ +#define SSH_ERROR -1 /* Error of some kind */ +#define SSH_AGAIN -2 /* The nonblocking call must be repeated */ +#define SSH_EOF -127 /* We have already a eof */ + +/** + * @addtogroup libssh_log + * + * @{ + */ + +enum { + /** No logging at all + */ + SSH_LOG_NOLOG=0, + /** Only warnings + */ + SSH_LOG_WARNING, + /** High level protocol information + */ + SSH_LOG_PROTOCOL, + /** Lower level protocol infomations, packet level + */ + SSH_LOG_PACKET, + /** Every function path + */ + SSH_LOG_FUNCTIONS +}; +/** @} */ +#define SSH_LOG_RARE SSH_LOG_WARNING + +/** + * @name Logging levels + * + * @brief Debug levels for logging. + * @{ + */ + +/** No logging at all */ +#define SSH_LOG_NONE 0 +/** Show only warnings */ +#define SSH_LOG_WARN 1 +/** Get some information what's going on */ +#define SSH_LOG_INFO 2 +/** Get detailed debuging information **/ +#define SSH_LOG_DEBUG 3 +/** Get trace output, packet information, ... */ +#define SSH_LOG_TRACE 4 + +/** @} */ + +enum ssh_options_e { + SSH_OPTIONS_HOST, + SSH_OPTIONS_PORT, + SSH_OPTIONS_PORT_STR, + SSH_OPTIONS_FD, + SSH_OPTIONS_USER, + SSH_OPTIONS_SSH_DIR, + SSH_OPTIONS_IDENTITY, + SSH_OPTIONS_ADD_IDENTITY, + SSH_OPTIONS_KNOWNHOSTS, + SSH_OPTIONS_TIMEOUT, + SSH_OPTIONS_TIMEOUT_USEC, + SSH_OPTIONS_SSH1, + SSH_OPTIONS_SSH2, + SSH_OPTIONS_LOG_VERBOSITY, + SSH_OPTIONS_LOG_VERBOSITY_STR, + SSH_OPTIONS_CIPHERS_C_S, + SSH_OPTIONS_CIPHERS_S_C, + SSH_OPTIONS_COMPRESSION_C_S, + SSH_OPTIONS_COMPRESSION_S_C, + SSH_OPTIONS_PROXYCOMMAND, + SSH_OPTIONS_BINDADDR, + SSH_OPTIONS_STRICTHOSTKEYCHECK, + SSH_OPTIONS_COMPRESSION, + SSH_OPTIONS_COMPRESSION_LEVEL, + SSH_OPTIONS_KEY_EXCHANGE, + SSH_OPTIONS_HOSTKEYS, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, + SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, + SSH_OPTIONS_HMAC_C_S, + SSH_OPTIONS_HMAC_S_C, + SSH_OPTIONS_PASSWORD_AUTH, + SSH_OPTIONS_PUBKEY_AUTH, + SSH_OPTIONS_KBDINT_AUTH, + SSH_OPTIONS_GSSAPI_AUTH, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + SSH_OPTIONS_NODELAY, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + SSH_OPTIONS_PROCESS_CONFIG, + SSH_OPTIONS_REKEY_DATA, + SSH_OPTIONS_REKEY_TIME, +}; + +enum { + /** Code is going to write/create remote files */ + SSH_SCP_WRITE, + /** Code is going to read remote files */ + SSH_SCP_READ, + SSH_SCP_RECURSIVE=0x10 +}; + +enum ssh_scp_request_types { + /** A new directory is going to be pulled */ + SSH_SCP_REQUEST_NEWDIR=1, + /** A new file is going to be pulled */ + SSH_SCP_REQUEST_NEWFILE, + /** End of requests */ + SSH_SCP_REQUEST_EOF, + /** End of directory */ + SSH_SCP_REQUEST_ENDDIR, + /** Warning received */ + SSH_SCP_REQUEST_WARNING +}; + +enum ssh_connector_flags_e { + /** Only the standard stream of the channel */ + SSH_CONNECTOR_STDOUT = 1, + SSH_CONNECTOR_STDINOUT = 1, + /** Only the exception stream of the channel */ + SSH_CONNECTOR_STDERR = 2, + /** Merge both standard and exception streams */ + SSH_CONNECTOR_BOTH = 3 +}; + +LIBSSH_API int ssh_blocking_flush(ssh_session session, int timeout); +LIBSSH_API ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms); +LIBSSH_API int ssh_channel_change_pty_size(ssh_channel channel,int cols,int rows); +LIBSSH_API int ssh_channel_close(ssh_channel channel); +LIBSSH_API void ssh_channel_free(ssh_channel channel); +LIBSSH_API int ssh_channel_get_exit_status(ssh_channel channel); +LIBSSH_API ssh_session ssh_channel_get_session(ssh_channel channel); +LIBSSH_API int ssh_channel_is_closed(ssh_channel channel); +LIBSSH_API int ssh_channel_is_eof(ssh_channel channel); +LIBSSH_API int ssh_channel_is_open(ssh_channel channel); +LIBSSH_API ssh_channel ssh_channel_new(ssh_session session); +LIBSSH_API int ssh_channel_open_auth_agent(ssh_channel channel); +LIBSSH_API int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +LIBSSH_API int ssh_channel_open_forward_unix(ssh_channel channel, const char *remotepath, + const char *sourcehost, int localport); +LIBSSH_API int ssh_channel_open_session(ssh_channel channel); +LIBSSH_API int ssh_channel_open_x11(ssh_channel channel, const char *orig_addr, int orig_port); +LIBSSH_API int ssh_channel_poll(ssh_channel channel, int is_stderr); +LIBSSH_API int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr); +LIBSSH_API int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr); +LIBSSH_API int ssh_channel_read_timeout(ssh_channel channel, void *dest, uint32_t count, int is_stderr, int timeout_ms); +LIBSSH_API int ssh_channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr); +LIBSSH_API int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value); +LIBSSH_API int ssh_channel_request_exec(ssh_channel channel, const char *cmd); +LIBSSH_API int ssh_channel_request_pty(ssh_channel channel); +LIBSSH_API int ssh_channel_request_pty_size(ssh_channel channel, const char *term, + int cols, int rows); +LIBSSH_API int ssh_channel_request_shell(ssh_channel channel); +LIBSSH_API int ssh_channel_request_send_signal(ssh_channel channel, const char *signum); +LIBSSH_API int ssh_channel_request_send_break(ssh_channel channel, uint32_t length); +LIBSSH_API int ssh_channel_request_sftp(ssh_channel channel); +LIBSSH_API int ssh_channel_request_subsystem(ssh_channel channel, const char *subsystem); +LIBSSH_API int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number); +LIBSSH_API int ssh_channel_request_auth_agent(ssh_channel channel); +LIBSSH_API int ssh_channel_send_eof(ssh_channel channel); +LIBSSH_API int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout); +LIBSSH_API void ssh_channel_set_blocking(ssh_channel channel, int blocking); +LIBSSH_API void ssh_channel_set_counter(ssh_channel channel, + ssh_counter counter); +LIBSSH_API int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len); +LIBSSH_API int ssh_channel_write_stderr(ssh_channel channel, + const void *data, + uint32_t len); +LIBSSH_API uint32_t ssh_channel_window_size(ssh_channel channel); + +LIBSSH_API char *ssh_basename (const char *path); +LIBSSH_API void ssh_clean_pubkey_hash(unsigned char **hash); +LIBSSH_API int ssh_connect(ssh_session session); + +LIBSSH_API ssh_connector ssh_connector_new(ssh_session session); +LIBSSH_API void ssh_connector_free(ssh_connector connector); +LIBSSH_API int ssh_connector_set_in_channel(ssh_connector connector, + ssh_channel channel, + enum ssh_connector_flags_e flags); +LIBSSH_API int ssh_connector_set_out_channel(ssh_connector connector, + ssh_channel channel, + enum ssh_connector_flags_e flags); +LIBSSH_API void ssh_connector_set_in_fd(ssh_connector connector, socket_t fd); +LIBSSH_API void ssh_connector_set_out_fd(ssh_connector connector, socket_t fd); + +LIBSSH_API const char *ssh_copyright(void); +LIBSSH_API void ssh_disconnect(ssh_session session); +LIBSSH_API char *ssh_dirname (const char *path); +LIBSSH_API int ssh_finalize(void); + +/* REVERSE PORT FORWARDING */ +LIBSSH_API ssh_channel ssh_channel_accept_forward(ssh_session session, + int timeout_ms, + int *destination_port); +LIBSSH_API int ssh_channel_cancel_forward(ssh_session session, + const char *address, + int port); +LIBSSH_API int ssh_channel_listen_forward(ssh_session session, + const char *address, + int port, + int *bound_port); + +LIBSSH_API void ssh_free(ssh_session session); +LIBSSH_API const char *ssh_get_disconnect_message(ssh_session session); +LIBSSH_API const char *ssh_get_error(void *error); +LIBSSH_API int ssh_get_error_code(void *error); +LIBSSH_API socket_t ssh_get_fd(ssh_session session); +LIBSSH_API char *ssh_get_hexa(const unsigned char *what, size_t len); +LIBSSH_API char *ssh_get_issue_banner(ssh_session session); +LIBSSH_API int ssh_get_openssh_version(ssh_session session); + +LIBSSH_API int ssh_get_server_publickey(ssh_session session, ssh_key *key); + +enum ssh_publickey_hash_type { + SSH_PUBLICKEY_HASH_SHA1, + SSH_PUBLICKEY_HASH_MD5, + SSH_PUBLICKEY_HASH_SHA256 +}; +LIBSSH_API int ssh_get_publickey_hash(const ssh_key key, + enum ssh_publickey_hash_type type, + unsigned char **hash, + size_t *hlen); + +/* DEPRECATED FUNCTIONS */ +SSH_DEPRECATED LIBSSH_API int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash); +SSH_DEPRECATED LIBSSH_API ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms); +SSH_DEPRECATED LIBSSH_API int ssh_forward_cancel(ssh_session session, const char *address, int port); +SSH_DEPRECATED LIBSSH_API int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port); +SSH_DEPRECATED LIBSSH_API int ssh_get_publickey(ssh_session session, ssh_key *key); +SSH_DEPRECATED LIBSSH_API int ssh_write_knownhost(ssh_session session); +SSH_DEPRECATED LIBSSH_API char *ssh_dump_knownhost(ssh_session session); +SSH_DEPRECATED LIBSSH_API int ssh_is_server_known(ssh_session session); +SSH_DEPRECATED LIBSSH_API void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len); + + + +LIBSSH_API int ssh_get_random(void *where,int len,int strong); +LIBSSH_API int ssh_get_version(ssh_session session); +LIBSSH_API int ssh_get_status(ssh_session session); +LIBSSH_API int ssh_get_poll_flags(ssh_session session); +LIBSSH_API int ssh_init(void); +LIBSSH_API int ssh_is_blocking(ssh_session session); +LIBSSH_API int ssh_is_connected(ssh_session session); + +/* KNOWN HOSTS */ +LIBSSH_API void ssh_knownhosts_entry_free(struct ssh_knownhosts_entry *entry); +#define SSH_KNOWNHOSTS_ENTRY_FREE(e) do { \ + if ((e) != NULL) { \ + ssh_knownhosts_entry_free(e); \ + e = NULL; \ + } \ +} while(0) + +LIBSSH_API int ssh_known_hosts_parse_line(const char *host, + const char *line, + struct ssh_knownhosts_entry **entry); +LIBSSH_API enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session); + +LIBSSH_API int ssh_session_export_known_hosts_entry(ssh_session session, + char **pentry_string); +LIBSSH_API int ssh_session_update_known_hosts(ssh_session session); + +LIBSSH_API enum ssh_known_hosts_e ssh_session_get_known_hosts_entry(ssh_session session, + struct ssh_knownhosts_entry **pentry); +LIBSSH_API enum ssh_known_hosts_e ssh_session_is_known_server(ssh_session session); + +/* LOGGING */ +LIBSSH_API int ssh_set_log_level(int level); +LIBSSH_API int ssh_get_log_level(void); +LIBSSH_API void *ssh_get_log_userdata(void); +LIBSSH_API int ssh_set_log_userdata(void *data); +LIBSSH_API void _ssh_log(int verbosity, + const char *function, + const char *format, ...) PRINTF_ATTRIBUTE(3, 4); + +/* legacy */ +SSH_DEPRECATED LIBSSH_API void ssh_log(ssh_session session, + int prioriry, + const char *format, ...) PRINTF_ATTRIBUTE(3, 4); + +LIBSSH_API ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_open_reply_accept_channel(ssh_message msg, ssh_channel chan); +LIBSSH_API int ssh_message_channel_request_reply_success(ssh_message msg); +#define SSH_MESSAGE_FREE(x) \ + do { if ((x) != NULL) { ssh_message_free(x); (x) = NULL; } } while(0) +LIBSSH_API void ssh_message_free(ssh_message msg); +LIBSSH_API ssh_message ssh_message_get(ssh_session session); +LIBSSH_API int ssh_message_subtype(ssh_message msg); +LIBSSH_API int ssh_message_type(ssh_message msg); +LIBSSH_API int ssh_mkdir (const char *pathname, mode_t mode); +LIBSSH_API ssh_session ssh_new(void); + +LIBSSH_API int ssh_options_copy(ssh_session src, ssh_session *dest); +LIBSSH_API int ssh_options_getopt(ssh_session session, int *argcptr, char **argv); +LIBSSH_API int ssh_options_parse_config(ssh_session session, const char *filename); +LIBSSH_API int ssh_options_set(ssh_session session, enum ssh_options_e type, + const void *value); +LIBSSH_API int ssh_options_get(ssh_session session, enum ssh_options_e type, + char **value); +LIBSSH_API int ssh_options_get_port(ssh_session session, unsigned int * port_target); +LIBSSH_API int ssh_pcap_file_close(ssh_pcap_file pcap); +LIBSSH_API void ssh_pcap_file_free(ssh_pcap_file pcap); +LIBSSH_API ssh_pcap_file ssh_pcap_file_new(void); +LIBSSH_API int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename); + +/** + * @addtogroup libssh_auth + * + * @{ + */ + +/** + * @brief SSH authentication callback for password and publickey auth. + * + * @param prompt Prompt to be displayed. + * @param buf Buffer to save the password. You should null-terminate it. + * @param len Length of the buffer. + * @param echo Enable or disable the echo of what you type. + * @param verify Should the password be verified? + * @param userdata Userdata to be passed to the callback function. Useful + * for GUI applications. + * + * @return 0 on success, < 0 on error. + */ +typedef int (*ssh_auth_callback) (const char *prompt, char *buf, size_t len, + int echo, int verify, void *userdata); + +/** @} */ + +LIBSSH_API ssh_key ssh_key_new(void); +#define SSH_KEY_FREE(x) \ + do { if ((x) != NULL) { ssh_key_free(x); x = NULL; } } while(0) +LIBSSH_API void ssh_key_free (ssh_key key); +LIBSSH_API enum ssh_keytypes_e ssh_key_type(const ssh_key key); +LIBSSH_API const char *ssh_key_type_to_char(enum ssh_keytypes_e type); +LIBSSH_API enum ssh_keytypes_e ssh_key_type_from_name(const char *name); +LIBSSH_API int ssh_key_is_public(const ssh_key k); +LIBSSH_API int ssh_key_is_private(const ssh_key k); +LIBSSH_API int ssh_key_cmp(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what); + +LIBSSH_API int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, + ssh_key *pkey); +LIBSSH_API int ssh_pki_import_privkey_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey); +LIBSSH_API int ssh_pki_export_privkey_base64(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key); +LIBSSH_API int ssh_pki_import_privkey_file(const char *filename, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey); +LIBSSH_API int ssh_pki_export_privkey_file(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename); + +LIBSSH_API int ssh_pki_copy_cert_to_privkey(const ssh_key cert_key, + ssh_key privkey); + +LIBSSH_API int ssh_pki_import_pubkey_base64(const char *b64_key, + enum ssh_keytypes_e type, + ssh_key *pkey); +LIBSSH_API int ssh_pki_import_pubkey_file(const char *filename, + ssh_key *pkey); + +LIBSSH_API int ssh_pki_import_cert_base64(const char *b64_cert, + enum ssh_keytypes_e type, + ssh_key *pkey); +LIBSSH_API int ssh_pki_import_cert_file(const char *filename, + ssh_key *pkey); + +LIBSSH_API int ssh_pki_export_privkey_to_pubkey(const ssh_key privkey, + ssh_key *pkey); +LIBSSH_API int ssh_pki_export_pubkey_base64(const ssh_key key, + char **b64_key); +LIBSSH_API int ssh_pki_export_pubkey_file(const ssh_key key, + const char *filename); + +LIBSSH_API const char *ssh_pki_key_ecdsa_name(const ssh_key key); + +LIBSSH_API char *ssh_get_fingerprint_hash(enum ssh_publickey_hash_type type, + unsigned char *hash, + size_t len); +LIBSSH_API void ssh_print_hash(enum ssh_publickey_hash_type type, unsigned char *hash, size_t len); +LIBSSH_API int ssh_send_ignore (ssh_session session, const char *data); +LIBSSH_API int ssh_send_debug (ssh_session session, const char *message, int always_display); +LIBSSH_API void ssh_gssapi_set_creds(ssh_session session, const ssh_gssapi_creds creds); +LIBSSH_API int ssh_scp_accept_request(ssh_scp scp); +LIBSSH_API int ssh_scp_close(ssh_scp scp); +LIBSSH_API int ssh_scp_deny_request(ssh_scp scp, const char *reason); +LIBSSH_API void ssh_scp_free(ssh_scp scp); +LIBSSH_API int ssh_scp_init(ssh_scp scp); +LIBSSH_API int ssh_scp_leave_directory(ssh_scp scp); +LIBSSH_API ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location); +LIBSSH_API int ssh_scp_pull_request(ssh_scp scp); +LIBSSH_API int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode); +LIBSSH_API int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int perms); +LIBSSH_API int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, int perms); +LIBSSH_API int ssh_scp_read(ssh_scp scp, void *buffer, size_t size); +LIBSSH_API const char *ssh_scp_request_get_filename(ssh_scp scp); +LIBSSH_API int ssh_scp_request_get_permissions(ssh_scp scp); +LIBSSH_API size_t ssh_scp_request_get_size(ssh_scp scp); +LIBSSH_API uint64_t ssh_scp_request_get_size64(ssh_scp scp); +LIBSSH_API const char *ssh_scp_request_get_warning(ssh_scp scp); +LIBSSH_API int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len); +LIBSSH_API int ssh_select(ssh_channel *channels, ssh_channel *outchannels, socket_t maxfd, + fd_set *readfds, struct timeval *timeout); +LIBSSH_API int ssh_service_request(ssh_session session, const char *service); +LIBSSH_API int ssh_set_agent_channel(ssh_session session, ssh_channel channel); +LIBSSH_API int ssh_set_agent_socket(ssh_session session, socket_t fd); +LIBSSH_API void ssh_set_blocking(ssh_session session, int blocking); +LIBSSH_API void ssh_set_counters(ssh_session session, ssh_counter scounter, + ssh_counter rcounter); +LIBSSH_API void ssh_set_fd_except(ssh_session session); +LIBSSH_API void ssh_set_fd_toread(ssh_session session); +LIBSSH_API void ssh_set_fd_towrite(ssh_session session); +LIBSSH_API void ssh_silent_disconnect(ssh_session session); +LIBSSH_API int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcapfile); + +/* USERAUTH */ +LIBSSH_API int ssh_userauth_none(ssh_session session, const char *username); +LIBSSH_API int ssh_userauth_list(ssh_session session, const char *username); +LIBSSH_API int ssh_userauth_try_publickey(ssh_session session, + const char *username, + const ssh_key pubkey); +LIBSSH_API int ssh_userauth_publickey(ssh_session session, + const char *username, + const ssh_key privkey); +#ifndef _WIN32 +LIBSSH_API int ssh_userauth_agent(ssh_session session, + const char *username); +#endif +LIBSSH_API int ssh_userauth_publickey_auto(ssh_session session, + const char *username, + const char *passphrase); +LIBSSH_API int ssh_userauth_password(ssh_session session, + const char *username, + const char *password); + +LIBSSH_API int ssh_userauth_kbdint(ssh_session session, const char *user, const char *submethods); +LIBSSH_API const char *ssh_userauth_kbdint_getinstruction(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getname(ssh_session session); +LIBSSH_API int ssh_userauth_kbdint_getnprompts(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, char *echo); +LIBSSH_API int ssh_userauth_kbdint_getnanswers(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i); +LIBSSH_API int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, + const char *answer); +LIBSSH_API int ssh_userauth_gssapi(ssh_session session); +LIBSSH_API const char *ssh_version(int req_version); + +LIBSSH_API void ssh_string_burn(ssh_string str); +LIBSSH_API ssh_string ssh_string_copy(ssh_string str); +LIBSSH_API void *ssh_string_data(ssh_string str); +LIBSSH_API int ssh_string_fill(ssh_string str, const void *data, size_t len); +#define SSH_STRING_FREE(x) \ + do { if ((x) != NULL) { ssh_string_free(x); x = NULL; } } while(0) +LIBSSH_API void ssh_string_free(ssh_string str); +LIBSSH_API ssh_string ssh_string_from_char(const char *what); +LIBSSH_API size_t ssh_string_len(ssh_string str); +LIBSSH_API ssh_string ssh_string_new(size_t size); +LIBSSH_API const char *ssh_string_get_char(ssh_string str); +LIBSSH_API char *ssh_string_to_char(ssh_string str); +#define SSH_STRING_FREE_CHAR(x) \ + do { if ((x) != NULL) { ssh_string_free_char(x); x = NULL; } } while(0) +LIBSSH_API void ssh_string_free_char(char *s); + +LIBSSH_API int ssh_getpass(const char *prompt, char *buf, size_t len, int echo, + int verify); + + +typedef int (*ssh_event_callback)(socket_t fd, int revents, void *userdata); + +LIBSSH_API ssh_event ssh_event_new(void); +LIBSSH_API int ssh_event_add_fd(ssh_event event, socket_t fd, short events, + ssh_event_callback cb, void *userdata); +LIBSSH_API int ssh_event_add_session(ssh_event event, ssh_session session); +LIBSSH_API int ssh_event_add_connector(ssh_event event, ssh_connector connector); +LIBSSH_API int ssh_event_dopoll(ssh_event event, int timeout); +LIBSSH_API int ssh_event_remove_fd(ssh_event event, socket_t fd); +LIBSSH_API int ssh_event_remove_session(ssh_event event, ssh_session session); +LIBSSH_API int ssh_event_remove_connector(ssh_event event, ssh_connector connector); +LIBSSH_API void ssh_event_free(ssh_event event); +LIBSSH_API const char* ssh_get_clientbanner(ssh_session session); +LIBSSH_API const char* ssh_get_serverbanner(ssh_session session); +LIBSSH_API const char* ssh_get_kex_algo(ssh_session session); +LIBSSH_API const char* ssh_get_cipher_in(ssh_session session); +LIBSSH_API const char* ssh_get_cipher_out(ssh_session session); +LIBSSH_API const char* ssh_get_hmac_in(ssh_session session); +LIBSSH_API const char* ssh_get_hmac_out(ssh_session session); + +LIBSSH_API ssh_buffer ssh_buffer_new(void); +LIBSSH_API void ssh_buffer_free(ssh_buffer buffer); +#define SSH_BUFFER_FREE(x) \ + do { if ((x) != NULL) { ssh_buffer_free(x); x = NULL; } } while(0) +LIBSSH_API int ssh_buffer_reinit(ssh_buffer buffer); +LIBSSH_API int ssh_buffer_add_data(ssh_buffer buffer, const void *data, uint32_t len); +LIBSSH_API uint32_t ssh_buffer_get_data(ssh_buffer buffer, void *data, uint32_t requestedlen); +LIBSSH_API void *ssh_buffer_get(ssh_buffer buffer); +LIBSSH_API uint32_t ssh_buffer_get_len(ssh_buffer buffer); + +#ifndef LIBSSH_LEGACY_0_4 +#include "libssh/legacy.h" +#endif + +#ifdef __cplusplus +} +#endif +#endif /* _LIBSSH_H */ diff --git a/include/libssh/libsshpp.hpp b/include/libssh/libsshpp.hpp new file mode 100644 index 0000000..75c9c7a --- /dev/null +++ b/include/libssh/libsshpp.hpp @@ -0,0 +1,683 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * 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 + */ + +#ifndef LIBSSHPP_HPP_ +#define LIBSSHPP_HPP_ + +/** + * @defgroup ssh_cpp The libssh C++ wrapper + * + * The C++ bindings for libssh are completely embedded in a single .hpp file, and + * this for two reasons: + * - C++ is hard to keep binary compatible, C is easy. We try to keep libssh C version + * as much as possible binary compatible between releases, while this would be hard for + * C++. If you compile your program with these headers, you will only link to the C version + * of libssh which will be kept ABI compatible. No need to recompile your C++ program + * each time a new binary-compatible version of libssh is out + * - Most of the functions in this file are really short and are probably worth the "inline" + * linking mode, which the compiler can decide to do in some case. There would be nearly no + * performance penalty of using the wrapper rather than native calls. + * + * Please visit the documentation of ssh::Session and ssh::Channel + * @see ssh::Session + * @see ssh::Channel + * + * If you wish not to use C++ exceptions, please define SSH_NO_CPP_EXCEPTIONS: + * @code + * #define SSH_NO_CPP_EXCEPTIONS + * #include + * @endcode + * All functions will then return SSH_ERROR in case of error. + * @{ + */ + +/* do not use deprecated functions */ +#define LIBSSH_LEGACY_0_4 + +#include +#include +#include +#include +#include +#include + +namespace ssh { + +class Channel; +/** Some people do not like C++ exceptions. With this define, we give + * the choice to use or not exceptions. + * @brief if defined, disable C++ exceptions for libssh c++ wrapper + */ +#ifndef SSH_NO_CPP_EXCEPTIONS + +/** @brief This class describes a SSH Exception object. This object can be thrown + * by several SSH functions that interact with the network, and may fail because of + * socket, protocol or memory errors. + */ +class SshException{ +public: + SshException(ssh_session csession){ + code=ssh_get_error_code(csession); + description=std::string(ssh_get_error(csession)); + } + SshException(const SshException &e){ + code=e.code; + description=e.description; + } + /** @brief returns the Error code + * @returns SSH_FATAL Fatal error happened (not recoverable) + * @returns SSH_REQUEST_DENIED Request was denied by remote host + * @see ssh_get_error_code + */ + int getCode(){ + return code; + } + /** @brief returns the error message of the last exception + * @returns pointer to a c string containing the description of error + * @see ssh_get_error + */ + std::string getError(){ + return description; + } +private: + int code; + std::string description; +}; + +/** @internal + * @brief Macro to throw exception if there was an error + */ +#define ssh_throw(x) if((x)==SSH_ERROR) throw SshException(getCSession()) +#define ssh_throw_null(CSession,x) if((x)==NULL) throw SshException(CSession) +#define void_throwable void +#define return_throwable return + +#else + +/* No exception at all. All functions will return an error code instead + * of an exception + */ +#define ssh_throw(x) if((x)==SSH_ERROR) return SSH_ERROR +#define ssh_throw_null(CSession,x) if((x)==NULL) return NULL +#define void_throwable int +#define return_throwable return SSH_OK +#endif + +/** + * The ssh::Session class contains the state of a SSH connection. + */ +class Session { + friend class Channel; +public: + Session(){ + c_session=ssh_new(); + } + ~Session(){ + ssh_free(c_session); + c_session=NULL; + } + /** @brief sets an SSH session options + * @param type Type of option + * @param option cstring containing the value of option + * @throws SshException on error + * @see ssh_options_set + */ + void_throwable setOption(enum ssh_options_e type, const char *option){ + ssh_throw(ssh_options_set(c_session,type,option)); + return_throwable; + } + /** @brief sets an SSH session options + * @param type Type of option + * @param option long integer containing the value of option + * @throws SshException on error + * @see ssh_options_set + */ + void_throwable setOption(enum ssh_options_e type, long int option){ + ssh_throw(ssh_options_set(c_session,type,&option)); + return_throwable; + } + /** @brief sets an SSH session options + * @param type Type of option + * @param option void pointer containing the value of option + * @throws SshException on error + * @see ssh_options_set + */ + void_throwable setOption(enum ssh_options_e type, void *option){ + ssh_throw(ssh_options_set(c_session,type,option)); + return_throwable; + } + /** @brief connects to the remote host + * @throws SshException on error + * @see ssh_connect + */ + void_throwable connect(){ + int ret=ssh_connect(c_session); + ssh_throw(ret); + return_throwable; + } + /** @brief Authenticates automatically using public key + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_autopubkey + */ + int userauthPublickeyAuto(void){ + int ret=ssh_userauth_publickey_auto(c_session, NULL, NULL); + ssh_throw(ret); + return ret; + } + /** @brief Authenticates using the "none" method. Prefer using autopubkey if + * possible. + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_none + * @see Session::userauthAutoPubkey + */ + int userauthNone(){ + int ret=ssh_userauth_none(c_session,NULL); + ssh_throw(ret); + return ret; + } + + /** + * @brief Authenticate through the "keyboard-interactive" method. + * + * @param[in] username The username to authenticate. You can specify NULL if + * ssh_option_set_username() has been used. You cannot + * try two different logins in a row. + * + * @param[in] submethods Undocumented. Set it to NULL. + * + * @throws SshException on error + * + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED, + * SSH_AUTH_ERROR, SSH_AUTH_INFO, SSH_AUTH_AGAIN + * + * @see ssh_userauth_kbdint + */ + int userauthKbdint(const char* username, const char* submethods){ + int ret = ssh_userauth_kbdint(c_session, username, submethods); + ssh_throw(ret); + return ret; + } + + /** @brief Get the number of prompts (questions) the server has given. + * @returns The number of prompts. + * @see ssh_userauth_kbdint_getnprompts + */ + int userauthKbdintGetNPrompts(){ + return ssh_userauth_kbdint_getnprompts(c_session); + } + + /** + * @brief Set the answer for a question from a message block. + * + * @param[in] index The index number of the prompt. + * @param[in] answer The answer to give to the server. The answer MUST be + * encoded UTF-8. It is up to the server how to interpret + * the value and validate it. However, if you read the + * answer in some other encoding, you MUST convert it to + * UTF-8. + * + * @throws SshException on error + * + * @returns 0 on success, < 0 on error + * + * @see ssh_userauth_kbdint_setanswer + */ + int userauthKbdintSetAnswer(unsigned int index, const char *answer) + { + int ret = ssh_userauth_kbdint_setanswer(c_session, index, answer); + ssh_throw(ret); + return ret; + } + + + + /** @brief Authenticates using the password method. + * @param[in] password password to use for authentication + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_password + */ + int userauthPassword(const char *password){ + int ret=ssh_userauth_password(c_session,NULL,password); + ssh_throw(ret); + return ret; + } + /** @brief Try to authenticate using the publickey method. + * @param[in] pubkey public key to use for authentication + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS if the pubkey is accepted, + * @returns SSH_AUTH_DENIED if the pubkey is denied + * @see ssh_userauth_try_pubkey + */ + int userauthTryPublickey(ssh_key pubkey){ + int ret=ssh_userauth_try_publickey(c_session, NULL, pubkey); + ssh_throw(ret); + return ret; + } + /** @brief Authenticates using the publickey method. + * @param[in] privkey private key to use for authentication + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_pubkey + */ + int userauthPublickey(ssh_key privkey){ + int ret=ssh_userauth_publickey(c_session, NULL, privkey); + ssh_throw(ret); + return ret; + } + + /** @brief Returns the available authentication methods from the server + * @throws SshException on error + * @returns Bitfield of available methods. + * @see ssh_userauth_list + */ + int getAuthList(){ + int ret=ssh_userauth_list(c_session, NULL); + ssh_throw(ret); + return ret; + } + /** @brief Disconnects from the SSH server and closes connection + * @see ssh_disconnect + */ + void disconnect(){ + ssh_disconnect(c_session); + } + /** @brief Returns the disconnect message from the server, if any + * @returns pointer to the message, or NULL. Do not attempt to free + * the pointer. + */ + const char *getDisconnectMessage(){ + const char *msg=ssh_get_disconnect_message(c_session); + return msg; + } + /** @internal + * @brief gets error message + */ + const char *getError(){ + return ssh_get_error(c_session); + } + /** @internal + * @brief returns error code + */ + int getErrorCode(){ + return ssh_get_error_code(c_session); + } + /** @brief returns the file descriptor used for the communication + * @returns the file descriptor + * @warning if a proxycommand is used, this function will only return + * one of the two file descriptors being used + * @see ssh_get_fd + */ + socket_t getSocket(){ + return ssh_get_fd(c_session); + } + /** @brief gets the Issue banner from the ssh server + * @returns the issue banner. This is generally a MOTD from server + * @see ssh_get_issue_banner + */ + std::string getIssueBanner(){ + char *banner = ssh_get_issue_banner(c_session); + std::string ret = ""; + if (banner != NULL) { + ret = std::string(banner); + ::free(banner); + } + return ret; + } + /** @brief returns the OpenSSH version (server) if possible + * @returns openssh version code + * @see ssh_get_openssh_version + */ + int getOpensshVersion(){ + return ssh_get_openssh_version(c_session); + } + /** @brief returns the version of the SSH protocol being used + * @returns the SSH protocol version + * @see ssh_get_version + */ + int getVersion(){ + return ssh_get_version(c_session); + } + /** @brief verifies that the server is known + * @throws SshException on error + * @returns Integer value depending on the knowledge of the + * server key + * @see ssh_session_update_known_hosts + */ + int isServerKnown(){ + int state = ssh_session_is_known_server(c_session); + ssh_throw(state); + return state; + } + void log(int priority, const char *format, ...){ + char buffer[1024]; + va_list va; + + va_start(va, format); + vsnprintf(buffer, sizeof(buffer), format, va); + va_end(va); + _ssh_log(priority, "libsshpp", "%s", buffer); + } + + /** @brief copies options from a session to another + * @throws SshException on error + * @see ssh_options_copy + */ + void_throwable optionsCopy(const Session &source){ + ssh_throw(ssh_options_copy(source.c_session,&c_session)); + return_throwable; + } + /** @brief parses a configuration file for options + * @throws SshException on error + * @param[in] file configuration file name + * @see ssh_options_parse_config + */ + void_throwable optionsParseConfig(const char *file){ + ssh_throw(ssh_options_parse_config(c_session,file)); + return_throwable; + } + /** @brief silently disconnect from remote host + * @see ssh_silent_disconnect + */ + void silentDisconnect(){ + ssh_silent_disconnect(c_session); + } + /** @brief Writes the known host file with current + * host key + * @throws SshException on error + * @see ssh_write_knownhost + */ + int writeKnownhost(){ + int ret = ssh_session_update_known_hosts(c_session); + ssh_throw(ret); + return ret; + } + + /** @brief accept an incoming forward connection + * @param[in] timeout_ms timeout for waiting, in ms + * @returns new Channel pointer on the forward connection + * @returns NULL in case of error + * @warning you have to delete this pointer after use + * @see ssh_channel_forward_accept + * @see Session::listenForward + */ + inline Channel *acceptForward(int timeout_ms); + /* implemented outside the class due Channel references */ + + void_throwable cancelForward(const char *address, int port){ + int err=ssh_channel_cancel_forward(c_session, address, port); + ssh_throw(err); + return_throwable; + } + + void_throwable listenForward(const char *address, int port, + int &boundport){ + int err=ssh_channel_listen_forward(c_session, address, port, &boundport); + ssh_throw(err); + return_throwable; + } + + ssh_session getCSession(){ + return c_session; + } + +protected: + ssh_session c_session; + +private: + /* No copy constructor, no = operator */ + Session(const Session &); + Session& operator=(const Session &); +}; + +/** @brief the ssh::Channel class describes the state of an SSH + * channel. + * @see ssh_channel + */ +class Channel { + friend class Session; +public: + Channel(Session &ssh_session){ + channel = ssh_channel_new(ssh_session.getCSession()); + this->session = &ssh_session; + } + ~Channel(){ + ssh_channel_free(channel); + channel=NULL; + } + + /** @brief accept an incoming X11 connection + * @param[in] timeout_ms timeout for waiting, in ms + * @returns new Channel pointer on the X11 connection + * @returns NULL in case of error + * @warning you have to delete this pointer after use + * @see ssh_channel_accept_x11 + * @see Channel::requestX11 + */ + Channel *acceptX11(int timeout_ms){ + ssh_channel x11chan = ssh_channel_accept_x11(channel,timeout_ms); + ssh_throw_null(getCSession(),x11chan); + Channel *newchan = new Channel(getSession(),x11chan); + return newchan; + } + /** @brief change the size of a pseudoterminal + * @param[in] cols number of columns + * @param[in] rows number of rows + * @throws SshException on error + * @see ssh_channel_change_pty_size + */ + void_throwable changePtySize(int cols, int rows){ + int err=ssh_channel_change_pty_size(channel,cols,rows); + ssh_throw(err); + return_throwable; + } + + /** @brief closes a channel + * @throws SshException on error + * @see ssh_channel_close + */ + void_throwable close(){ + ssh_throw(ssh_channel_close(channel)); + return_throwable; + } + + int getExitStatus(){ + return ssh_channel_get_exit_status(channel); + } + Session &getSession(){ + return *session; + } + /** @brief returns true if channel is in closed state + * @see ssh_channel_is_closed + */ + bool isClosed(){ + return ssh_channel_is_closed(channel) != 0; + } + /** @brief returns true if channel is in EOF state + * @see ssh_channel_is_eof + */ + bool isEof(){ + return ssh_channel_is_eof(channel) != 0; + } + /** @brief returns true if channel is in open state + * @see ssh_channel_is_open + */ + bool isOpen(){ + return ssh_channel_is_open(channel) != 0; + } + int openForward(const char *remotehost, int remoteport, + const char *sourcehost=NULL, int localport=0){ + int err=ssh_channel_open_forward(channel,remotehost,remoteport, + sourcehost, localport); + ssh_throw(err); + return err; + } + /* TODO: completely remove this ? */ + void_throwable openSession(){ + int err=ssh_channel_open_session(channel); + ssh_throw(err); + return_throwable; + } + int poll(bool is_stderr=false){ + int err=ssh_channel_poll(channel,is_stderr); + ssh_throw(err); + return err; + } + int read(void *dest, size_t count){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read_timeout(channel,dest,count,false,-1); + ssh_throw(err); + return err; + } + int read(void *dest, size_t count, int timeout){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read_timeout(channel,dest,count,false,timeout); + ssh_throw(err); + return err; + } + int read(void *dest, size_t count, bool is_stderr=false, int timeout=-1){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read_timeout(channel,dest,count,is_stderr,timeout); + ssh_throw(err); + return err; + } + int readNonblocking(void *dest, size_t count, bool is_stderr=false){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read_nonblocking(channel,dest,count,is_stderr); + ssh_throw(err); + return err; + } + void_throwable requestEnv(const char *name, const char *value){ + int err=ssh_channel_request_env(channel,name,value); + ssh_throw(err); + return_throwable; + } + + void_throwable requestExec(const char *cmd){ + int err=ssh_channel_request_exec(channel,cmd); + ssh_throw(err); + return_throwable; + } + void_throwable requestPty(const char *term=NULL, int cols=0, int rows=0){ + int err; + if(term != NULL && cols != 0 && rows != 0) + err=ssh_channel_request_pty_size(channel,term,cols,rows); + else + err=ssh_channel_request_pty(channel); + ssh_throw(err); + return_throwable; + } + + void_throwable requestShell(){ + int err=ssh_channel_request_shell(channel); + ssh_throw(err); + return_throwable; + } + void_throwable requestSendSignal(const char *signum){ + int err=ssh_channel_request_send_signal(channel, signum); + ssh_throw(err); + return_throwable; + } + void_throwable requestSubsystem(const char *subsystem){ + int err=ssh_channel_request_subsystem(channel,subsystem); + ssh_throw(err); + return_throwable; + } + int requestX11(bool single_connection, + const char *protocol, const char *cookie, int screen_number){ + int err=ssh_channel_request_x11(channel,single_connection, + protocol, cookie, screen_number); + ssh_throw(err); + return err; + } + void_throwable sendEof(){ + int err=ssh_channel_send_eof(channel); + ssh_throw(err); + return_throwable; + } + /** @brief Writes on a channel + * @param data data to write. + * @param len number of bytes to write. + * @param is_stderr write should be done on the stderr channel (server only) + * @returns number of bytes written + * @throws SshException in case of error + * @see channel_write + * @see channel_write_stderr + */ + int write(const void *data, size_t len, bool is_stderr=false){ + int ret; + if(is_stderr){ + ret=ssh_channel_write_stderr(channel,data,len); + } else { + ret=ssh_channel_write(channel,data,len); + } + ssh_throw(ret); + return ret; + } + + ssh_session getCSession(){ + return session->getCSession(); + } + + ssh_channel getCChannel() { + return channel; + } + +protected: + Session *session; + ssh_channel channel; + +private: + Channel (Session &ssh_session, ssh_channel c_channel){ + this->channel=c_channel; + this->session = &ssh_session; + } + /* No copy and no = operator */ + Channel(const Channel &); + Channel &operator=(const Channel &); +}; + + +inline Channel *Session::acceptForward(int timeout_ms){ + ssh_channel forward = + ssh_channel_accept_forward(c_session, timeout_ms, NULL); + ssh_throw_null(c_session,forward); + Channel *newchan = new Channel(*this,forward); + return newchan; + } + +} // namespace ssh + +/** @} */ +#endif /* LIBSSHPP_HPP_ */ diff --git a/include/libssh/messages.h b/include/libssh/messages.h new file mode 100644 index 0000000..04d041d --- /dev/null +++ b/include/libssh/messages.h @@ -0,0 +1,106 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef MESSAGES_H_ +#define MESSAGES_H_ + +#include "config.h" + +struct ssh_auth_request { + char *username; + int method; + char *password; + struct ssh_key_struct *pubkey; + enum ssh_publickey_state_e signature_state; + char kbdint_response; +}; + +struct ssh_channel_request_open { + int type; + uint32_t sender; + uint32_t window; + uint32_t packet_size; + char *originator; + uint16_t originator_port; + char *destination; + uint16_t destination_port; +}; + +struct ssh_service_request { + char *service; +}; + +struct ssh_global_request { + int type; + uint8_t want_reply; + char *bind_address; + uint16_t bind_port; +}; + +struct ssh_channel_request { + int type; + ssh_channel channel; + uint8_t want_reply; + /* pty-req type specifics */ + char *TERM; + uint32_t width; + uint32_t height; + uint32_t pxwidth; + uint32_t pxheight; + ssh_string modes; + + /* env type request */ + char *var_name; + char *var_value; + /* exec type request */ + char *command; + /* subsystem */ + char *subsystem; + + /* X11 */ + uint8_t x11_single_connection; + char *x11_auth_protocol; + char *x11_auth_cookie; + uint32_t x11_screen_number; +}; + +struct ssh_message_struct { + ssh_session session; + int type; + struct ssh_auth_request auth_request; + struct ssh_channel_request_open channel_request_open; + struct ssh_channel_request channel_request; + struct ssh_service_request service_request; + struct ssh_global_request global_request; +}; + +SSH_PACKET_CALLBACK(ssh_packet_channel_open); +SSH_PACKET_CALLBACK(ssh_packet_global_request); + +#ifdef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_service_request); +SSH_PACKET_CALLBACK(ssh_packet_userauth_request); +#endif /* WITH_SERVER */ + +int ssh_message_handle_channel_request(ssh_session session, ssh_channel channel, ssh_buffer packet, + const char *request, uint8_t want_reply); +ssh_message ssh_message_pop_head(ssh_session session); + +#endif /* MESSAGES_H_ */ diff --git a/include/libssh/misc.h b/include/libssh/misc.h new file mode 100644 index 0000000..3cc3b11 --- /dev/null +++ b/include/libssh/misc.h @@ -0,0 +1,100 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef MISC_H_ +#define MISC_H_ + +/* in misc.c */ +/* gets the user home dir. */ +char *ssh_get_user_home_dir(void); +char *ssh_get_local_username(void); +int ssh_file_readaccess_ok(const char *file); +int ssh_dir_writeable(const char *path); + +char *ssh_path_expand_tilde(const char *d); +char *ssh_path_expand_escape(ssh_session session, const char *s); +int ssh_analyze_banner(ssh_session session, int server); +int ssh_is_ipaddr_v4(const char *str); +int ssh_is_ipaddr(const char *str); + +/* list processing */ + +struct ssh_list { + struct ssh_iterator *root; + struct ssh_iterator *end; +}; + +struct ssh_iterator { + struct ssh_iterator *next; + const void *data; +}; + +struct ssh_timestamp { + long seconds; + long useconds; +}; + +enum ssh_quote_state_e { + NO_QUOTE, + SINGLE_QUOTE, + DOUBLE_QUOTE +}; + +struct ssh_list *ssh_list_new(void); +void ssh_list_free(struct ssh_list *list); +struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list); +struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value); +size_t ssh_list_count(const struct ssh_list *list); +int ssh_list_append(struct ssh_list *list, const void *data); +int ssh_list_prepend(struct ssh_list *list, const void *data); +void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator); +char *ssh_lowercase(const char* str); +char *ssh_hostport(const char *host, int port); + +const void *_ssh_list_pop_head(struct ssh_list *list); + +#define ssh_iterator_value(type, iterator)\ + ((type)((iterator)->data)) + +/** @brief fetch the head element of a list and remove it from list + * @param type type of the element to return + * @param list the ssh_list to use + * @return the first element of the list, or NULL if the list is empty + */ +#define ssh_list_pop_head(type, ssh_list)\ + ((type)_ssh_list_pop_head(ssh_list)) + +int ssh_make_milliseconds(long sec, long usec); +void ssh_timestamp_init(struct ssh_timestamp *ts); +int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout); +int ssh_timeout_update(struct ssh_timestamp *ts, int timeout); + +int ssh_match_group(const char *group, const char *object); + +void uint64_inc(unsigned char *counter); + +void ssh_log_hexdump(const char *descr, const unsigned char *what, size_t len); + +int ssh_mkdirs(const char *pathname, mode_t mode); + +int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len); +int ssh_newline_vis(const char *string, char *buf, size_t buf_len); + +#endif /* MISC_H_ */ diff --git a/include/libssh/options.h b/include/libssh/options.h new file mode 100644 index 0000000..5b1cb9f --- /dev/null +++ b/include/libssh/options.h @@ -0,0 +1,30 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011 Andreas Schneider + * + * 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 + */ + +#ifndef _OPTIONS_H +#define _OPTIONS_H + +int ssh_config_parse_file(ssh_session session, const char *filename); +int ssh_options_set_algo(ssh_session session, + enum ssh_kex_types_e algo, + const char *list); +int ssh_options_apply(ssh_session session); + +#endif /* _OPTIONS_H */ diff --git a/include/libssh/packet.h b/include/libssh/packet.h new file mode 100644 index 0000000..8fc7ce4 --- /dev/null +++ b/include/libssh/packet.h @@ -0,0 +1,91 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef PACKET_H_ +#define PACKET_H_ + +#include "libssh/wrapper.h" + +struct ssh_socket_struct; + +/* this structure should go someday */ +typedef struct packet_struct { + int valid; + uint32_t len; + uint8_t type; +} PACKET; + +/** different state of packet reading. */ +enum ssh_packet_state_e { + /** Packet not initialized, must read the size of packet */ + PACKET_STATE_INIT, + /** Size was read, waiting for the rest of data */ + PACKET_STATE_SIZEREAD, + /** Full packet was read and callbacks are being called. Future packets + * should wait for the end of the callback. */ + PACKET_STATE_PROCESSING +}; + +enum ssh_packet_filter_result_e { + SSH_PACKET_UNKNOWN, + SSH_PACKET_ALLOWED, + SSH_PACKET_DENIED +}; + +int ssh_packet_send(ssh_session session); + +SSH_PACKET_CALLBACK(ssh_packet_unimplemented); +SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback); +SSH_PACKET_CALLBACK(ssh_packet_ignore_callback); +SSH_PACKET_CALLBACK(ssh_packet_dh_reply); +SSH_PACKET_CALLBACK(ssh_packet_newkeys); +SSH_PACKET_CALLBACK(ssh_packet_service_accept); +SSH_PACKET_CALLBACK(ssh_packet_ext_info); + +#ifdef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_kexdh_init); +#endif + +int ssh_packet_send_unimplemented(ssh_session session, uint32_t seqnum); +int ssh_packet_parse_type(ssh_session session); +//int packet_flush(ssh_session session, int enforce_blocking); + +int ssh_packet_socket_callback(const void *data, size_t len, void *user); +void ssh_packet_register_socket_callback(ssh_session session, struct ssh_socket_struct *s); +void ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks); +void ssh_packet_remove_callbacks(ssh_session session, ssh_packet_callbacks callbacks); +void ssh_packet_set_default_callbacks(ssh_session session); +void ssh_packet_process(ssh_session session, uint8_t type); + +/* PACKET CRYPT */ +uint32_t ssh_packet_decrypt_len(ssh_session session, uint8_t *destination, uint8_t *source); +int ssh_packet_decrypt(ssh_session session, uint8_t *destination, uint8_t *source, + size_t start, size_t encrypted_size); +unsigned char *ssh_packet_encrypt(ssh_session session, + void *packet, + unsigned int len); +int ssh_packet_hmac_verify(ssh_session session, const void *data, size_t len, + unsigned char *mac, enum ssh_hmac_e type); +int ssh_packet_set_newkeys(ssh_session session, + enum ssh_crypto_direction_e direction); +struct ssh_crypto_struct *ssh_packet_get_current_crypto(ssh_session session, + enum ssh_crypto_direction_e direction); + +#endif /* PACKET_H_ */ diff --git a/include/libssh/pcap.h b/include/libssh/pcap.h new file mode 100644 index 0000000..2e43ae8 --- /dev/null +++ b/include/libssh/pcap.h @@ -0,0 +1,45 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef PCAP_H_ +#define PCAP_H_ + +#include "config.h" +#include "libssh/libssh.h" + +#ifdef WITH_PCAP +typedef struct ssh_pcap_context_struct* ssh_pcap_context; + +int ssh_pcap_file_write_packet(ssh_pcap_file pcap, ssh_buffer packet, uint32_t original_len); + +ssh_pcap_context ssh_pcap_context_new(ssh_session session); +void ssh_pcap_context_free(ssh_pcap_context ctx); + +enum ssh_pcap_direction{ + SSH_PCAP_DIR_IN, + SSH_PCAP_DIR_OUT +}; +void ssh_pcap_context_set_file(ssh_pcap_context, ssh_pcap_file); +int ssh_pcap_context_write(ssh_pcap_context,enum ssh_pcap_direction direction, void *data, + uint32_t len, uint32_t origlen); + + +#endif /* WITH_PCAP */ +#endif /* PCAP_H_ */ diff --git a/include/libssh/pki.h b/include/libssh/pki.h new file mode 100644 index 0000000..9b10434 --- /dev/null +++ b/include/libssh/pki.h @@ -0,0 +1,165 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * 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 + */ + +#ifndef PKI_H_ +#define PKI_H_ + +#include "libssh/priv.h" +#ifdef HAVE_OPENSSL_EC_H +#include +#endif +#ifdef HAVE_OPENSSL_ECDSA_H +#include +#endif + +#include "libssh/crypto.h" +#ifdef HAVE_OPENSSL_ED25519 +/* If using OpenSSL implementation, define the signature lenght which would be + * defined in libssh/ed25519.h otherwise */ +#define ED25519_SIG_LEN 64 +#else +#include "libssh/ed25519.h" +#endif +/* This definition is used for both OpenSSL and internal implementations */ +#define ED25519_KEY_LEN 32 + +#define MAX_PUBKEY_SIZE 0x100000 /* 1M */ +#define MAX_PRIVKEY_SIZE 0x400000 /* 4M */ + +#define SSH_KEY_FLAG_EMPTY 0x0 +#define SSH_KEY_FLAG_PUBLIC 0x0001 +#define SSH_KEY_FLAG_PRIVATE 0x0002 + +struct ssh_key_struct { + enum ssh_keytypes_e type; + int flags; + const char *type_c; /* Don't free it ! it is static */ + int ecdsa_nid; +#if defined(HAVE_LIBGCRYPT) + gcry_sexp_t dsa; + gcry_sexp_t rsa; + gcry_sexp_t ecdsa; +#elif defined(HAVE_LIBMBEDCRYPTO) + mbedtls_pk_context *rsa; + mbedtls_ecdsa_context *ecdsa; + void *dsa; +#elif defined(HAVE_LIBCRYPTO) + DSA *dsa; + RSA *rsa; +# if defined(HAVE_OPENSSL_ECC) + EC_KEY *ecdsa; +# else + void *ecdsa; +# endif /* HAVE_OPENSSL_EC_H */ +#endif /* HAVE_LIBGCRYPT */ +#ifdef HAVE_OPENSSL_ED25519 + uint8_t *ed25519_pubkey; + uint8_t *ed25519_privkey; +#else + ed25519_pubkey *ed25519_pubkey; + ed25519_privkey *ed25519_privkey; +#endif + void *cert; + enum ssh_keytypes_e cert_type; +}; + +struct ssh_signature_struct { + enum ssh_keytypes_e type; + enum ssh_digest_e hash_type; + const char *type_c; +#if defined(HAVE_LIBGCRYPT) + gcry_sexp_t dsa_sig; + gcry_sexp_t rsa_sig; + gcry_sexp_t ecdsa_sig; +#elif defined(HAVE_LIBMBEDCRYPTO) + ssh_string rsa_sig; + struct mbedtls_ecdsa_sig ecdsa_sig; +#endif /* HAVE_LIBGCRYPT */ +#ifndef HAVE_OPENSSL_ED25519 + ed25519_signature *ed25519_sig; +#endif + ssh_string raw_sig; +}; + +typedef struct ssh_signature_struct *ssh_signature; + +/* SSH Key Functions */ +ssh_key ssh_key_dup(const ssh_key key); +void ssh_key_clean (ssh_key key); + +const char * +ssh_key_get_signature_algorithm(ssh_session session, + enum ssh_keytypes_e type); +enum ssh_keytypes_e ssh_key_type_from_signature_name(const char *name); +enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type); +enum ssh_digest_e ssh_key_type_to_hash(ssh_session session, + enum ssh_keytypes_e type); +enum ssh_digest_e ssh_key_hash_from_name(const char *name); + +#define is_ecdsa_key_type(t) \ + ((t) >= SSH_KEYTYPE_ECDSA_P256 && (t) <= SSH_KEYTYPE_ECDSA_P521) + +#define is_cert_type(kt)\ + ((kt) == SSH_KEYTYPE_DSS_CERT01 ||\ + (kt) == SSH_KEYTYPE_RSA_CERT01 ||\ + ((kt) >= SSH_KEYTYPE_ECDSA_P256_CERT01 &&\ + (kt) <= SSH_KEYTYPE_ED25519_CERT01)) + +/* SSH Signature Functions */ +ssh_signature ssh_signature_new(void); +void ssh_signature_free(ssh_signature sign); + +int ssh_pki_export_signature_blob(const ssh_signature sign, + ssh_string *sign_blob); +int ssh_pki_import_signature_blob(const ssh_string sig_blob, + const ssh_key pubkey, + ssh_signature *psig); +int ssh_pki_signature_verify(ssh_session session, + ssh_signature sig, + const ssh_key key, + const unsigned char *digest, + size_t dlen); + +/* SSH Public Key Functions */ +int ssh_pki_export_pubkey_blob(const ssh_key key, + ssh_string *pblob); +int ssh_pki_import_pubkey_blob(const ssh_string key_blob, + ssh_key *pkey); + +int ssh_pki_import_cert_blob(const ssh_string cert_blob, + ssh_key *pkey); + + +/* SSH Signing Functions */ +ssh_string ssh_pki_do_sign(ssh_session session, ssh_buffer sigbuf, + const ssh_key privatekey, enum ssh_digest_e hash_type); +ssh_string ssh_pki_do_sign_agent(ssh_session session, + struct ssh_buffer_struct *buf, + const ssh_key pubkey); +ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, + const ssh_key privkey, + const enum ssh_digest_e digest); + +/* Temporary functions, to be removed after migration to ssh_key */ +ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key); +ssh_private_key ssh_pki_convert_key_to_privatekey(const ssh_key key); + +int ssh_key_algorithm_allowed(ssh_session session, const char *type); +#endif /* PKI_H_ */ diff --git a/include/libssh/pki_priv.h b/include/libssh/pki_priv.h new file mode 100644 index 0000000..d365a2d --- /dev/null +++ b/include/libssh/pki_priv.h @@ -0,0 +1,159 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * 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 + */ + +#ifndef PKI_PRIV_H_ +#define PKI_PRIV_H_ + +#include "libssh/pki.h" + +/* defined in bcrypt_pbkdf.c */ +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); + +#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-----" +#define OPENSSH_HEADER_BEGIN "-----BEGIN OPENSSH PRIVATE KEY-----" +#define OPENSSH_HEADER_END "-----END OPENSSH PRIVATE KEY-----" +/* Magic defined in OpenSSH/PROTOCOL.key */ +#define OPENSSH_AUTH_MAGIC "openssh-key-v1" + +int pki_key_ecdsa_nid_from_name(const char *name); +const char *pki_key_ecdsa_nid_to_name(int nid); +const char *ssh_key_signature_to_char(enum ssh_keytypes_e type, + enum ssh_digest_e hash_type); +enum ssh_digest_e ssh_key_type_to_hash(ssh_session session, + enum ssh_keytypes_e type); + +/* SSH Key Functions */ +ssh_key pki_key_dup(const ssh_key key, int demote); +int pki_key_generate_rsa(ssh_key key, int parameter); +int pki_key_generate_dss(ssh_key key, int parameter); +int pki_key_generate_ecdsa(ssh_key key, int parameter); +int pki_key_generate_ed25519(ssh_key key); + +int pki_key_compare(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what); + +int pki_key_check_hash_compatible(ssh_key key, + enum ssh_digest_e hash_type); +/* SSH Private Key Functions */ +enum ssh_keytypes_e pki_privatekey_type_from_string(const char *privkey); +ssh_key pki_private_key_from_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data); + +ssh_string pki_private_key_to_pem(const ssh_key key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data); +int pki_import_privkey_buffer(enum ssh_keytypes_e type, + ssh_buffer buffer, + ssh_key *pkey); + +/* SSH Public Key Functions */ +int pki_pubkey_build_dss(ssh_key key, + ssh_string p, + ssh_string q, + ssh_string g, + ssh_string pubkey); +int pki_pubkey_build_rsa(ssh_key key, + ssh_string e, + ssh_string n); +int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e); +ssh_string pki_publickey_to_blob(const ssh_key key); + +/* SSH Private Key Functions */ +int pki_privkey_build_dss(ssh_key key, + ssh_string p, + ssh_string q, + ssh_string g, + ssh_string pubkey, + ssh_string privkey); +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); +int pki_privkey_build_ecdsa(ssh_key key, + int nid, + ssh_string e, + ssh_string exp); +ssh_string pki_publickey_to_blob(const ssh_key key); + +/* SSH Signature Functions */ +ssh_signature pki_sign_data(const ssh_key privkey, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len); +int pki_verify_data_signature(ssh_signature signature, + const ssh_key pubkey, + const unsigned char *input, + size_t input_len); +ssh_string pki_signature_to_blob(const ssh_signature sign); +ssh_signature pki_signature_from_blob(const ssh_key pubkey, + const ssh_string sig_blob, + enum ssh_keytypes_e type, + enum ssh_digest_e hash_type); + +/* SSH Signing Functions */ +ssh_signature pki_do_sign(const ssh_key privkey, + const unsigned char *input, + size_t input_len, + enum ssh_digest_e hash_type); +ssh_signature pki_do_sign_hash(const ssh_key privkey, + const unsigned char *hash, + size_t hlen, + enum ssh_digest_e hash_type); +int pki_ed25519_sign(const ssh_key privkey, ssh_signature sig, + const unsigned char *hash, size_t hlen); +int pki_ed25519_verify(const ssh_key pubkey, ssh_signature sig, + const unsigned char *hash, size_t hlen); +int pki_ed25519_key_cmp(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what); +int pki_ed25519_key_dup(ssh_key new, const ssh_key key); +int pki_ed25519_public_key_to_blob(ssh_buffer buffer, ssh_key key); +ssh_string pki_ed25519_signature_to_blob(ssh_signature sig); +int pki_signature_from_ed25519_blob(ssh_signature sig, ssh_string sig_blob); +int pki_privkey_build_ed25519(ssh_key key, + ssh_string pubkey, + ssh_string privkey); + +/* PKI Container OpenSSH */ +ssh_key ssh_pki_openssh_pubkey_import(const char *text_key); +ssh_key ssh_pki_openssh_privkey_import(const char *text_key, + const char *passphrase, ssh_auth_callback auth_fn, void *auth_data); +ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, + const char *passphrase, ssh_auth_callback auth_fn, void *auth_data); + +#endif /* PKI_PRIV_H_ */ diff --git a/include/libssh/poll.h b/include/libssh/poll.h new file mode 100644 index 0000000..3aa9a49 --- /dev/null +++ b/include/libssh/poll.h @@ -0,0 +1,161 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef POLL_H_ +#define POLL_H_ + +#include "config.h" + +#ifdef HAVE_POLL + +#include +typedef struct pollfd ssh_pollfd_t; + +#else /* HAVE_POLL */ + +/* poll emulation support */ + +typedef struct ssh_pollfd_struct { + socket_t fd; /* file descriptor */ + short events; /* requested events */ + short revents; /* returned events */ +} ssh_pollfd_t; + +typedef unsigned long int nfds_t; + +#ifdef _WIN32 + +#ifndef POLLRDNORM +#define POLLRDNORM 0x0100 +#endif +#ifndef POLLRDBAND +#define POLLRDBAND 0x0200 +#endif +#ifndef POLLIN +#define POLLIN (POLLRDNORM | POLLRDBAND) +#endif +#ifndef POLLPRI +#define POLLPRI 0x0400 +#endif + +#ifndef POLLWRNORM +#define POLLWRNORM 0x0010 +#endif +#ifndef POLLOUT +#define POLLOUT (POLLWRNORM) +#endif +#ifndef POLLWRBAND +#define POLLWRBAND 0x0020 +#endif + +#ifndef POLLERR +#define POLLERR 0x0001 +#endif +#ifndef POLLHUP +#define POLLHUP 0x0002 +#endif +#ifndef POLLNVAL +#define POLLNVAL 0x0004 +#endif + +#else /* _WIN32 */ + +/* poll.c */ +#ifndef POLLIN +#define POLLIN 0x001 /* There is data to read. */ +#endif +#ifndef POLLPRI +#define POLLPRI 0x002 /* There is urgent data to read. */ +#endif +#ifndef POLLOUT +#define POLLOUT 0x004 /* Writing now will not block. */ +#endif + +#ifndef POLLERR +#define POLLERR 0x008 /* Error condition. */ +#endif +#ifndef POLLHUP +#define POLLHUP 0x010 /* Hung up. */ +#endif +#ifndef POLLNVAL +#define POLLNVAL 0x020 /* Invalid polling request. */ +#endif + +#ifndef POLLRDNORM +#define POLLRDNORM 0x040 /* mapped to read fds_set */ +#endif +#ifndef POLLRDBAND +#define POLLRDBAND 0x080 /* mapped to exception fds_set */ +#endif +#ifndef POLLWRNORM +#define POLLWRNORM 0x100 /* mapped to write fds_set */ +#endif +#ifndef POLLWRBAND +#define POLLWRBAND 0x200 /* mapped to write fds_set */ +#endif + +#endif /* WIN32 */ +#endif /* HAVE_POLL */ + +void ssh_poll_init(void); +void ssh_poll_cleanup(void); +int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout); +typedef struct ssh_poll_ctx_struct *ssh_poll_ctx; +typedef struct ssh_poll_handle_struct *ssh_poll_handle; + +/** + * @brief SSH poll callback. This callback will be used when an event + * caught on the socket. + * + * @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. + * + * @return 0 on success, < 0 if you removed the poll object from + * its poll context. + */ +typedef int (*ssh_poll_callback)(ssh_poll_handle p, socket_t fd, int revents, + void *userdata); + +struct ssh_socket_struct; + +ssh_poll_handle ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, + void *userdata); +void ssh_poll_free(ssh_poll_handle p); +ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p); +short ssh_poll_get_events(ssh_poll_handle p); +void ssh_poll_set_events(ssh_poll_handle p, short events); +void ssh_poll_add_events(ssh_poll_handle p, short events); +void ssh_poll_remove_events(ssh_poll_handle p, short events); +socket_t ssh_poll_get_fd(ssh_poll_handle p); +void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd); +void ssh_poll_set_callback(ssh_poll_handle p, ssh_poll_callback cb, void *userdata); +ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size); +void ssh_poll_ctx_free(ssh_poll_ctx ctx); +int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p); +int ssh_poll_ctx_add_socket (ssh_poll_ctx ctx, struct ssh_socket_struct *s); +void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p); +int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout); +ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session); +int ssh_event_add_poll(ssh_event event, ssh_poll_handle p); +void ssh_event_remove_poll(ssh_event event, ssh_poll_handle p); + +#endif /* POLL_H_ */ diff --git a/include/libssh/poly1305.h b/include/libssh/poly1305.h new file mode 100644 index 0000000..9174bd1 --- /dev/null +++ b/include/libssh/poly1305.h @@ -0,0 +1,21 @@ +/* + * Public Domain poly1305 from Andrew Moon + * poly1305-donna-unrolled.c from https://github.com/floodyberry/poly1305-donna + */ + +#ifndef POLY1305_H +#define POLY1305_H + +#define POLY1305_KEYLEN 32 +#define POLY1305_TAGLEN 16 + +void poly1305_auth(uint8_t out[POLY1305_TAGLEN], const uint8_t *m, size_t inlen, + const uint8_t key[POLY1305_KEYLEN]) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 1, POLY1305_TAGLEN))) + __attribute__((__bounded__(__buffer__, 2, 3))) + __attribute__((__bounded__(__minbytes__, 4, POLY1305_KEYLEN))) +#endif + ; + +#endif /* POLY1305_H */ diff --git a/include/libssh/priv.h b/include/libssh/priv.h new file mode 100644 index 0000000..31405d6 --- /dev/null +++ b/include/libssh/priv.h @@ -0,0 +1,426 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * 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 + */ + +/* + * priv.h file + * This include file contains everything you shouldn't deal with in + * user programs. Consider that anything in this file might change + * without notice; libssh.h file will keep backward compatibility + * on binary & source + */ + +#ifndef _LIBSSH_PRIV_H +#define _LIBSSH_PRIV_H + +#include +#include +#include + +#if !defined(HAVE_STRTOULL) +# if defined(HAVE___STRTOULL) +# define strtoull __strtoull +# elif defined(HAVE__STRTOUI64) +# define strtoull _strtoui64 +# elif defined(__hpux) && defined(__LP64__) +# define strtoull strtoul +# else +# error "no strtoull function found" +# endif +#endif /* !defined(HAVE_STRTOULL) */ + +#if !defined(HAVE_STRNDUP) +char *strndup(const char *s, size_t n); +#endif /* ! HAVE_STRNDUP */ + +#ifdef HAVE_BYTESWAP_H +#include +#endif + +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#ifndef bswap_32 +#define bswap_32(x) \ + ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \ + (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) +#endif + +#ifdef _WIN32 + +/* Imitate define of inttypes.h */ +# ifndef PRIdS +# define PRIdS "Id" +# endif + +# ifndef PRIu64 +# if __WORDSIZE == 64 +# define PRIu64 "lu" +# else +# define PRIu64 "llu" +# endif /* __WORDSIZE */ +# endif /* PRIu64 */ + +# ifndef PRIu32 +# define PRIu32 "u" +# endif /* PRIu32 */ + +# ifndef PRIx64 +# if __WORDSIZE == 64 +# define PRIx64 "lx" +# else +# define PRIx64 "llx" +# endif /* __WORDSIZE */ +# endif /* PRIx64 */ + +# ifndef PRIx32 +# define PRIx32 "x" +# endif /* PRIx32 */ + +# ifdef _MSC_VER +# include +# include /* va_copy define check */ + +/* On Microsoft compilers define inline to __inline on all others use inline */ +# undef inline +# define inline __inline + +# ifndef va_copy +# define va_copy(dest, src) (dest = src) +# endif + +# define strcasecmp _stricmp +# define strncasecmp _strnicmp +# if ! defined(HAVE_ISBLANK) +# define isblank(ch) ((ch) == ' ' || (ch) == '\t' || (ch) == '\n' || (ch) == '\r') +# endif + +# define usleep(X) Sleep(((X)+1000)/1000) + +# undef strtok_r +# define strtok_r strtok_s + +# if defined(HAVE__SNPRINTF_S) +# undef snprintf +# define snprintf(d, n, ...) _snprintf_s((d), (n), _TRUNCATE, __VA_ARGS__) +# else /* HAVE__SNPRINTF_S */ +# if defined(HAVE__SNPRINTF) +# undef snprintf +# define snprintf _snprintf +# else /* HAVE__SNPRINTF */ +# if !defined(HAVE_SNPRINTF) +# error "no snprintf compatible function found" +# endif /* HAVE_SNPRINTF */ +# endif /* HAVE__SNPRINTF */ +# endif /* HAVE__SNPRINTF_S */ + +# if defined(HAVE__VSNPRINTF_S) +# undef vsnprintf +# define vsnprintf(s, n, f, v) _vsnprintf_s((s), (n), _TRUNCATE, (f), (v)) +# else /* HAVE__VSNPRINTF_S */ +# if defined(HAVE__VSNPRINTF) +# undef vsnprintf +# define vsnprintf _vsnprintf +# else +# if !defined(HAVE_VSNPRINTF) +# error "No vsnprintf compatible function found" +# endif /* HAVE_VSNPRINTF */ +# endif /* HAVE__VSNPRINTF */ +# endif /* HAVE__VSNPRINTF_S */ + +# ifndef _SSIZE_T_DEFINED +# undef ssize_t +# include + typedef _W64 SSIZE_T ssize_t; +# define _SSIZE_T_DEFINED +# endif /* _SSIZE_T_DEFINED */ + +# endif /* _MSC_VER */ + +struct timeval; +int gettimeofday(struct timeval *__p, void *__t); + +#define _XCLOSESOCKET closesocket + +#else /* _WIN32 */ + +#include +#define PRIdS "zd" + +#define _XCLOSESOCKET close + +#endif /* _WIN32 */ + +#include "libssh/libssh.h" +#include "libssh/callbacks.h" + +/* some constants */ +#ifndef MAX_PACKAT_LEN +#define MAX_PACKET_LEN 262144 +#endif +#ifndef ERROR_BUFFERLEN +#define ERROR_BUFFERLEN 1024 +#endif + +#ifndef CLIENT_BANNER_SSH2 +#define CLIENT_BANNER_SSH2 "SSH-2.0-libssh_" SSH_STRINGIFY(LIBSSH_VERSION) +#endif /* CLIENT_BANNER_SSH2 */ + +#ifndef KBDINT_MAX_PROMPT +#define KBDINT_MAX_PROMPT 256 /* more than openssh's :) */ +#endif +#ifndef MAX_BUF_SIZE +#define MAX_BUF_SIZE 4096 +#endif + +#ifndef HAVE_COMPILER__FUNC__ +# ifdef HAVE_COMPILER__FUNCTION__ +# define __func__ __FUNCTION__ +# else +# error "Your system must provide a __func__ macro" +# endif +#endif + +#if defined(HAVE_GCC_THREAD_LOCAL_STORAGE) +# define LIBSSH_THREAD __thread +#elif defined(HAVE_MSC_THREAD_LOCAL_STORAGE) +# define LIBSSH_THREAD __declspec(thread) +#else +# define LIBSSH_THREAD +#endif + +/* + * This makes sure that the compiler doesn't optimize out the code + * + * Use it in a macro where the provided variable is 'x'. + */ +#if defined(HAVE_GCC_VOLATILE_MEMORY_PROTECTION) +# define LIBSSH_MEM_PROTECTION __asm__ volatile("" : : "r"(&(x)) : "memory") +#else +# define LIBSSH_MEM_PROTECTION +#endif + +/* forward declarations */ +struct ssh_common_struct; +struct ssh_kex_struct; + +enum ssh_digest_e { + SSH_DIGEST_AUTO=0, + SSH_DIGEST_SHA1=1, + SSH_DIGEST_SHA256, + SSH_DIGEST_SHA384, + SSH_DIGEST_SHA512, +}; + +int ssh_get_key_params(ssh_session session, + ssh_key *privkey, + enum ssh_digest_e *digest); + +/* LOGGING */ +void ssh_log_function(int verbosity, + const char *function, + const char *buffer); +#define SSH_LOG(priority, ...) \ + _ssh_log(priority, __func__, __VA_ARGS__) + +/* LEGACY */ +void ssh_log_common(struct ssh_common_struct *common, + int verbosity, + const char *function, + const char *format, ...) PRINTF_ATTRIBUTE(4, 5); + + +/* ERROR HANDLING */ + +/* error handling structure */ +struct error_struct { + int error_code; + char error_buffer[ERROR_BUFFERLEN]; +}; + +#define ssh_set_error(error, code, ...) \ + _ssh_set_error(error, code, __func__, __VA_ARGS__) +void _ssh_set_error(void *error, + int code, + const char *function, + const char *descr, ...) PRINTF_ATTRIBUTE(4, 5); + +#define ssh_set_error_oom(error) \ + _ssh_set_error_oom(error, __func__) +void _ssh_set_error_oom(void *error, const char *function); + +#define ssh_set_error_invalid(error) \ + _ssh_set_error_invalid(error, __func__) +void _ssh_set_error_invalid(void *error, const char *function); + +void ssh_reset_error(void *error); + +/* server.c */ +#ifdef WITH_SERVER +int ssh_auth_reply_default(ssh_session session,int partial); +int ssh_auth_reply_success(ssh_session session, int partial); +#endif +/* client.c */ + +int ssh_send_banner(ssh_session session, int is_server); + +/* connect.c */ +socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, + const char *bind_addr, int port); + +/* in base64.c */ +ssh_buffer base64_to_bin(const char *source); +uint8_t *bin_to_base64(const uint8_t *source, size_t len); + +/* gzip.c */ +int compress_buffer(ssh_session session,ssh_buffer buf); +int decompress_buffer(ssh_session session,ssh_buffer buf, size_t maxlen); + +/* match.c */ +int match_pattern_list(const char *string, const char *pattern, + unsigned int len, int dolower); +int match_hostname(const char *host, const char *pattern, unsigned int len); + +/* connector.c */ +int ssh_connector_set_event(ssh_connector connector, ssh_event event); +int ssh_connector_remove_event(ssh_connector connector); + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +/** Free memory space */ +#define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x=NULL;} } while(0) + +/** Zero a structure */ +#define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x)) + +/** Zero a structure given a pointer to the structure */ +#define ZERO_STRUCTP(x) do { if ((x) != NULL) memset((char *)(x), 0, sizeof(*(x))); } while(0) + +/** Get the size of an array */ +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +#ifndef HAVE_EXPLICIT_BZERO +void explicit_bzero(void *s, size_t n); +#endif /* !HAVE_EXPLICIT_BZERO */ + +/** + * This is a hack to fix warnings. The idea is to use this everywhere that we + * get the "discarding const" warning by the compiler. That doesn't actually + * fix the real issue, but marks the place and you can search the code for + * discard_const. + * + * Please use this macro only when there is no other way to fix the warning. + * We should use this function in only in a very few places. + * + * Also, please call this via the discard_const_p() macro interface, as that + * makes the return type safe. + */ +#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) + +/** + * Type-safe version of discard_const + */ +#define discard_const_p(type, ptr) ((type *)discard_const(ptr)) + +/** + * Get the argument cound of variadic arguments + */ +/* + * Since MSVC 2010 there is a bug in passing __VA_ARGS__ to subsequent + * macros as a single token, which results in: + * warning C4003: not enough actual parameters for macro '_VA_ARG_N' + * and incorrect behavior. This fixes issue. + */ +#define VA_APPLY_VARIADIC_MACRO(macro, tuple) macro tuple + +#define __VA_NARG__(...) \ + (__VA_NARG_(__VA_ARGS__, __RSEQ_N())) +#define __VA_NARG_(...) \ + VA_APPLY_VARIADIC_MACRO(__VA_ARG_N, (__VA_ARGS__)) +#define __VA_ARG_N( \ + _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ + _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ + _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \ + _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \ + _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \ + _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \ + _61,_62,_63,N,...) N +#define __RSEQ_N() \ + 63, 62, 61, 60, \ + 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, \ + 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, \ + 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, \ + 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, \ + 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, \ + 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 + +#define CLOSE_SOCKET(s) do { if ((s) != SSH_INVALID_SOCKET) { _XCLOSESOCKET(s); (s) = SSH_INVALID_SOCKET;} } while(0) + +#ifndef HAVE_HTONLL +# ifdef WORDS_BIGENDIAN +# define htonll(x) (x) +# else +# define htonll(x) \ + (((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32)) +# endif +#endif + +#ifndef HAVE_NTOHLL +# ifdef WORDS_BIGENDIAN +# define ntohll(x) (x) +# else +# define ntohll(x) \ + (((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32)) +# endif +#endif + +#ifndef FALL_THROUGH +# ifdef HAVE_FALLTHROUGH_ATTRIBUTE +# define FALL_THROUGH __attribute__ ((fallthrough)) +# else /* HAVE_FALLTHROUGH_ATTRIBUTE */ +# define FALL_THROUGH +# endif /* HAVE_FALLTHROUGH_ATTRIBUTE */ +#endif /* FALL_THROUGH */ + +#ifndef __attr_unused__ +# ifdef HAVE_UNUSED_ATTRIBUTE +# define __attr_unused__ __attribute__((unused)) +# else /* HAVE_UNUSED_ATTRIBUTE */ +# define __attr_unused__ +# endif /* HAVE_UNUSED_ATTRIBUTE */ +#endif /* __attr_unused__ */ + +#ifndef UNUSED_PARAM +#define UNUSED_PARAM(param) param __attr_unused__ +#endif /* UNUSED_PARAM */ + +#ifndef UNUSED_VAR +#define UNUSED_VAR(var) __attr_unused__ var +#endif /* UNUSED_VAR */ + +void ssh_agent_state_free(void *data); + +#endif /* _LIBSSH_PRIV_H */ diff --git a/include/libssh/sc25519.h b/include/libssh/sc25519.h new file mode 100644 index 0000000..5a2c1b8 --- /dev/null +++ b/include/libssh/sc25519.h @@ -0,0 +1,74 @@ +/* $OpenBSD: sc25519.h,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/sc25519.h + */ + +#ifndef SC25519_H +#define SC25519_H + +#define sc25519 crypto_sign_ed25519_ref_sc25519 +#define shortsc25519 crypto_sign_ed25519_ref_shortsc25519 +#define sc25519_from32bytes crypto_sign_ed25519_ref_sc25519_from32bytes +#define shortsc25519_from16bytes crypto_sign_ed25519_ref_shortsc25519_from16bytes +#define sc25519_from64bytes crypto_sign_ed25519_ref_sc25519_from64bytes +#define sc25519_from_shortsc crypto_sign_ed25519_ref_sc25519_from_shortsc +#define sc25519_to32bytes crypto_sign_ed25519_ref_sc25519_to32bytes +#define sc25519_iszero_vartime crypto_sign_ed25519_ref_sc25519_iszero_vartime +#define sc25519_isshort_vartime crypto_sign_ed25519_ref_sc25519_isshort_vartime +#define sc25519_lt_vartime crypto_sign_ed25519_ref_sc25519_lt_vartime +#define sc25519_add crypto_sign_ed25519_ref_sc25519_add +#define sc25519_sub_nored crypto_sign_ed25519_ref_sc25519_sub_nored +#define sc25519_mul crypto_sign_ed25519_ref_sc25519_mul +#define sc25519_mul_shortsc crypto_sign_ed25519_ref_sc25519_mul_shortsc +#define sc25519_window3 crypto_sign_ed25519_ref_sc25519_window3 +#define sc25519_window5 crypto_sign_ed25519_ref_sc25519_window5 +#define sc25519_2interleave2 crypto_sign_ed25519_ref_sc25519_2interleave2 + +typedef struct { + uint32_t v[32]; +} sc25519; + +typedef struct { + uint32_t v[16]; +} shortsc25519; + +void sc25519_from32bytes(sc25519 *r, const unsigned char x[32]); + +void shortsc25519_from16bytes(shortsc25519 *r, const unsigned char x[16]); + +void sc25519_from64bytes(sc25519 *r, const unsigned char x[64]); + +void sc25519_from_shortsc(sc25519 *r, const shortsc25519 *x); + +void sc25519_to32bytes(unsigned char r[32], const sc25519 *x); + +int sc25519_iszero_vartime(const sc25519 *x); + +int sc25519_isshort_vartime(const sc25519 *x); + +int sc25519_lt_vartime(const sc25519 *x, const sc25519 *y); + +void sc25519_add(sc25519 *r, const sc25519 *x, const sc25519 *y); + +void sc25519_sub_nored(sc25519 *r, const sc25519 *x, const sc25519 *y); + +void sc25519_mul(sc25519 *r, const sc25519 *x, const sc25519 *y); + +void sc25519_mul_shortsc(sc25519 *r, const sc25519 *x, const shortsc25519 *y); + +/* Convert s into a representation of the form \sum_{i=0}^{84}r[i]2^3 + * with r[i] in {-4,...,3} + */ +void sc25519_window3(signed char r[85], const sc25519 *s); + +/* Convert s into a representation of the form \sum_{i=0}^{50}r[i]2^5 + * with r[i] in {-16,...,15} + */ +void sc25519_window5(signed char r[51], const sc25519 *s); + +void sc25519_2interleave2(unsigned char r[127], const sc25519 *s1, const sc25519 *s2); + +#endif diff --git a/include/libssh/scp.h b/include/libssh/scp.h new file mode 100644 index 0000000..d356d89 --- /dev/null +++ b/include/libssh/scp.h @@ -0,0 +1,55 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef _SCP_H +#define _SCP_H + +enum ssh_scp_states { + SSH_SCP_NEW, //Data structure just created + SSH_SCP_WRITE_INITED, //Gave our intention to write + SSH_SCP_WRITE_WRITING,//File was opened and currently writing + SSH_SCP_READ_INITED, //Gave our intention to read + SSH_SCP_READ_REQUESTED, //We got a read request + SSH_SCP_READ_READING, //File is opened and reading + SSH_SCP_ERROR, //Something bad happened + SSH_SCP_TERMINATED //Transfer finished +}; + +struct ssh_scp_struct { + ssh_session session; + int mode; + int recursive; + ssh_channel channel; + char *location; + enum ssh_scp_states state; + uint64_t filelen; + uint64_t processed; + enum ssh_scp_request_types request_type; + char *request_name; + char *warning; + int request_mode; +}; + +int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len); +int ssh_scp_integer_mode(const char *mode); +char *ssh_scp_string_mode(int mode); +int ssh_scp_response(ssh_scp scp, char **response); + +#endif diff --git a/include/libssh/server.h b/include/libssh/server.h new file mode 100644 index 0000000..41f89d5 --- /dev/null +++ b/include/libssh/server.h @@ -0,0 +1,381 @@ +/* Public include file for server support */ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * 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 + */ + +/** + * @defgroup libssh_server The libssh server API + * + * @{ + */ + +#ifndef SERVER_H +#define SERVER_H + +#include "libssh/libssh.h" +#define SERVERBANNER CLIENTBANNER + +#ifdef __cplusplus +extern "C" { +#endif + +enum ssh_bind_options_e { + SSH_BIND_OPTIONS_BINDADDR, + SSH_BIND_OPTIONS_BINDPORT, + SSH_BIND_OPTIONS_BINDPORT_STR, + SSH_BIND_OPTIONS_HOSTKEY, + SSH_BIND_OPTIONS_DSAKEY, + SSH_BIND_OPTIONS_RSAKEY, + SSH_BIND_OPTIONS_BANNER, + SSH_BIND_OPTIONS_LOG_VERBOSITY, + SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, + SSH_BIND_OPTIONS_ECDSAKEY, + SSH_BIND_OPTIONS_IMPORT_KEY, + SSH_BIND_OPTIONS_KEY_EXCHANGE, + SSH_BIND_OPTIONS_CIPHERS_C_S, + SSH_BIND_OPTIONS_CIPHERS_S_C, + SSH_BIND_OPTIONS_HMAC_C_S, + SSH_BIND_OPTIONS_HMAC_S_C, + SSH_BIND_OPTIONS_CONFIG_DIR, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + SSH_BIND_OPTIONS_PROCESS_CONFIG, +}; + +typedef struct ssh_bind_struct* ssh_bind; + +/* Callback functions */ + +/** + * @brief Incoming connection callback. This callback is called when a ssh_bind + * has a new incoming connection. + * @param sshbind Current sshbind session handler + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_bind_incoming_connection_callback) (ssh_bind sshbind, + void *userdata); + +/** + * @brief These are the callbacks exported by the ssh_bind structure. + * + * They are called by the server module when events appear on the network. + */ +struct ssh_bind_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** A new connection is available. */ + ssh_bind_incoming_connection_callback incoming_connection; +}; +typedef struct ssh_bind_callbacks_struct *ssh_bind_callbacks; + +/** + * @brief Creates a new SSH server bind. + * + * @return A newly allocated ssh_bind session pointer. + */ +LIBSSH_API ssh_bind ssh_bind_new(void); + +LIBSSH_API int ssh_bind_options_set(ssh_bind sshbind, + enum ssh_bind_options_e type, const void *value); + +LIBSSH_API int ssh_bind_options_parse_config(ssh_bind sshbind, + const char *filename); + +/** + * @brief Start listening to the socket. + * + * @param ssh_bind_o The ssh server bind to use. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int ssh_bind_listen(ssh_bind ssh_bind_o); + +/** + * @brief Set the callback for this bind. + * + * @param[in] sshbind The bind to set the callback on. + * + * @param[in] callbacks An already set up ssh_bind_callbacks instance. + * + * @param[in] userdata A pointer to private data to pass to the callbacks. + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + * + * @code + * struct ssh_callbacks_struct cb = { + * .userdata = data, + * .auth_function = my_auth_function + * }; + * ssh_callbacks_init(&cb); + * ssh_bind_set_callbacks(session, &cb); + * @endcode + */ +LIBSSH_API int ssh_bind_set_callbacks(ssh_bind sshbind, ssh_bind_callbacks callbacks, + void *userdata); + +/** + * @brief Set the session to blocking/nonblocking mode. + * + * @param ssh_bind_o The ssh server bind to use. + * + * @param blocking Zero for nonblocking mode. + */ +LIBSSH_API void ssh_bind_set_blocking(ssh_bind ssh_bind_o, int blocking); + +/** + * @brief Recover the file descriptor from the session. + * + * @param ssh_bind_o The ssh server bind to get the fd from. + * + * @return The file descriptor. + */ +LIBSSH_API socket_t ssh_bind_get_fd(ssh_bind ssh_bind_o); + +/** + * @brief Set the file descriptor for a session. + * + * @param ssh_bind_o The ssh server bind to set the fd. + * + * @param fd The file descriptssh_bind B + */ +LIBSSH_API void ssh_bind_set_fd(ssh_bind ssh_bind_o, socket_t fd); + +/** + * @brief Allow the file descriptor to accept new sessions. + * + * @param ssh_bind_o The ssh server bind to use. + */ +LIBSSH_API void ssh_bind_fd_toaccept(ssh_bind ssh_bind_o); + +/** + * @brief Accept an incoming ssh connection and initialize the session. + * + * @param ssh_bind_o The ssh server bind to accept a connection. + * @param session A preallocated ssh session + * @see ssh_new + * @return SSH_OK when a connection is established + */ +LIBSSH_API int ssh_bind_accept(ssh_bind ssh_bind_o, ssh_session session); + +/** + * @brief Accept an incoming ssh connection on the given file descriptor + * and initialize the session. + * + * @param ssh_bind_o The ssh server bind to accept a connection. + * @param session A preallocated ssh session + * @param fd A file descriptor of an already established TCP + * inbound connection + * @see ssh_new + * @see ssh_bind_accept + * @return SSH_OK when a connection is established + */ +LIBSSH_API int ssh_bind_accept_fd(ssh_bind ssh_bind_o, ssh_session session, + socket_t fd); + +LIBSSH_API ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session); + +/** + * @brief Handles the key exchange and set up encryption + * + * @param session A connected ssh session + * @see ssh_bind_accept + * @return SSH_OK if the key exchange was successful + */ +LIBSSH_API int ssh_handle_key_exchange(ssh_session session); + +/** + * @brief Initialize the set of key exchange, hostkey, ciphers, MACs, and + * compression algorithms for the given ssh_session. + * + * The selection of algorithms and keys used are determined by the + * options that are currently set in the given ssh_session structure. + * May only be called before the initial key exchange has begun. + * + * @param session The session structure to initialize. + * + * @see ssh_handle_key_exchange + * @see ssh_options_set + * + * @return SSH_OK if initialization succeeds. + */ + +LIBSSH_API int ssh_server_init_kex(ssh_session session); + +/** + * @brief Free a ssh servers bind. + * + * @param ssh_bind_o The ssh server bind to free. + */ +LIBSSH_API void ssh_bind_free(ssh_bind ssh_bind_o); + +/** + * @brief Set the acceptable authentication methods to be sent to the client. + * + * + * @param[in] session The server session + * + * @param[in] auth_methods The authentication methods we will support, which + * can be bitwise-or'd. + * + * Supported methods are: + * + * SSH_AUTH_METHOD_PASSWORD + * SSH_AUTH_METHOD_PUBLICKEY + * SSH_AUTH_METHOD_HOSTBASED + * SSH_AUTH_METHOD_INTERACTIVE + * SSH_AUTH_METHOD_GSSAPI_MIC + */ +LIBSSH_API void ssh_set_auth_methods(ssh_session session, int auth_methods); + +/********************************************************** + * SERVER MESSAGING + **********************************************************/ + +/** + * @brief Reply with a standard reject message. + * + * Use this function if you don't know what to respond or if you want to reject + * a request. + * + * @param[in] msg The message to use for the reply. + * + * @return 0 on success, -1 on error. + * + * @see ssh_message_get() + */ +LIBSSH_API int ssh_message_reply_default(ssh_message msg); + +/** + * @brief Get the name of the authenticated user. + * + * @param[in] msg The message to get the username from. + * + * @return The username or NULL if an error occured. + * + * @see ssh_message_get() + * @see ssh_message_type() + */ +LIBSSH_API const char *ssh_message_auth_user(ssh_message msg); + +/** + * @brief Get the password of the authenticated user. + * + * @param[in] msg The message to get the password from. + * + * @return The username or NULL if an error occured. + * + * @see ssh_message_get() + * @see ssh_message_type() + */ +LIBSSH_API const char *ssh_message_auth_password(ssh_message msg); + +/** + * @brief Get the publickey of the authenticated user. + * + * If you need the key for later user you should duplicate it. + * + * @param[in] msg The message to get the public key from. + * + * @return The public key or NULL. + * + * @see ssh_key_dup() + * @see ssh_key_cmp() + * @see ssh_message_get() + * @see ssh_message_type() + */ +LIBSSH_API ssh_key ssh_message_auth_pubkey(ssh_message msg); + +LIBSSH_API int ssh_message_auth_kbdint_is_response(ssh_message msg); +LIBSSH_API enum ssh_publickey_state_e ssh_message_auth_publickey_state(ssh_message msg); +LIBSSH_API int ssh_message_auth_reply_success(ssh_message msg,int partial); +LIBSSH_API int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pubkey); +LIBSSH_API int ssh_message_auth_reply_pk_ok_simple(ssh_message msg); + +LIBSSH_API int ssh_message_auth_set_methods(ssh_message msg, int methods); + +LIBSSH_API int ssh_message_auth_interactive_request(ssh_message msg, + const char *name, const char *instruction, + unsigned int num_prompts, const char **prompts, char *echo); + +LIBSSH_API int ssh_message_service_reply_success(ssh_message msg); +LIBSSH_API const char *ssh_message_service_service(ssh_message msg); + +LIBSSH_API int ssh_message_global_request_reply_success(ssh_message msg, + uint16_t bound_port); + +LIBSSH_API void ssh_set_message_callback(ssh_session session, + int(*ssh_bind_message_callback)(ssh_session session, ssh_message msg, void *data), + void *data); +LIBSSH_API int ssh_execute_message_callbacks(ssh_session session); + +LIBSSH_API const char *ssh_message_channel_request_open_originator(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_open_originator_port(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_open_destination(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_open_destination_port(ssh_message msg); + +LIBSSH_API ssh_channel ssh_message_channel_request_channel(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_pty_term(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_pty_width(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_pty_height(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_pty_pxwidth(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_pty_pxheight(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_env_name(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_env_value(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_command(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_subsystem(ssh_message msg); + +LIBSSH_API int ssh_message_channel_request_x11_single_connection(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_x11_auth_protocol(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_x11_auth_cookie(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_x11_screen_number(ssh_message msg); + +LIBSSH_API const char *ssh_message_global_request_address(ssh_message msg); +LIBSSH_API int ssh_message_global_request_port(ssh_message msg); + +LIBSSH_API int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +LIBSSH_API int ssh_channel_open_x11(ssh_channel channel, + const char *orig_addr, int orig_port); + +LIBSSH_API int ssh_channel_request_send_exit_status(ssh_channel channel, + int exit_status); +LIBSSH_API int ssh_channel_request_send_exit_signal(ssh_channel channel, + const char *signum, + int core, + const char *errmsg, + const char *lang); + +LIBSSH_API int ssh_send_keepalive(ssh_session session); + +/* deprecated functions */ +SSH_DEPRECATED LIBSSH_API int ssh_accept(ssh_session session); +SSH_DEPRECATED LIBSSH_API int channel_write_stderr(ssh_channel channel, + const void *data, uint32_t len); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SERVER_H */ + +/** @} */ diff --git a/include/libssh/session.h b/include/libssh/session.h new file mode 100644 index 0000000..2225615 --- /dev/null +++ b/include/libssh/session.h @@ -0,0 +1,254 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef SESSION_H_ +#define SESSION_H_ +#include + +#include "libssh/priv.h" +#include "libssh/kex.h" +#include "libssh/packet.h" +#include "libssh/pcap.h" +#include "libssh/auth.h" +#include "libssh/channels.h" +#include "libssh/poll.h" +#include "libssh/config.h" +#include "libssh/misc.h" + +/* These are the different states a SSH session can be into its life */ +enum ssh_session_state_e { + SSH_SESSION_STATE_NONE=0, + SSH_SESSION_STATE_CONNECTING, + SSH_SESSION_STATE_SOCKET_CONNECTED, + SSH_SESSION_STATE_BANNER_RECEIVED, + SSH_SESSION_STATE_INITIAL_KEX, + SSH_SESSION_STATE_KEXINIT_RECEIVED, + SSH_SESSION_STATE_DH, + SSH_SESSION_STATE_AUTHENTICATING, + SSH_SESSION_STATE_AUTHENTICATED, + SSH_SESSION_STATE_ERROR, + SSH_SESSION_STATE_DISCONNECTED +}; + +enum ssh_dh_state_e { + DH_STATE_INIT=0, + DH_STATE_GROUP_SENT, + DH_STATE_REQUEST_SENT, + DH_STATE_INIT_SENT, + DH_STATE_NEWKEYS_SENT, + DH_STATE_FINISHED +}; + +enum ssh_pending_call_e { + SSH_PENDING_CALL_NONE = 0, + SSH_PENDING_CALL_CONNECT, + SSH_PENDING_CALL_AUTH_NONE, + SSH_PENDING_CALL_AUTH_PASSWORD, + SSH_PENDING_CALL_AUTH_OFFER_PUBKEY, + SSH_PENDING_CALL_AUTH_PUBKEY, + SSH_PENDING_CALL_AUTH_AGENT, + SSH_PENDING_CALL_AUTH_KBDINT_INIT, + SSH_PENDING_CALL_AUTH_KBDINT_SEND, + SSH_PENDING_CALL_AUTH_GSSAPI_MIC +}; + +/* libssh calls may block an undefined amount of time */ +#define SSH_SESSION_FLAG_BLOCKING 1 + +/* Client successfully authenticated */ +#define SSH_SESSION_FLAG_AUTHENTICATED 2 + +/* codes to use with ssh_handle_packets*() */ +/* Infinite timeout */ +#define SSH_TIMEOUT_INFINITE -1 +/* Use the timeout defined by user if any. Mostly used with new connections */ +#define SSH_TIMEOUT_USER -2 +/* Use the default timeout, depending on ssh_is_blocking() */ +#define SSH_TIMEOUT_DEFAULT -3 +/* Don't block at all */ +#define SSH_TIMEOUT_NONBLOCKING 0 + +/* options flags */ +/* Authentication with *** allowed */ +#define SSH_OPT_FLAG_PASSWORD_AUTH 0x1 +#define SSH_OPT_FLAG_PUBKEY_AUTH 0x2 +#define SSH_OPT_FLAG_KBDINT_AUTH 0x4 +#define SSH_OPT_FLAG_GSSAPI_AUTH 0x8 + +/* extensions flags */ +/* negotiation enabled */ +#define SSH_EXT_NEGOTIATION 0x01 +/* server-sig-algs extension */ +#define SSH_EXT_SIG_RSA_SHA256 0x02 +#define SSH_EXT_SIG_RSA_SHA512 0x04 + +/* members that are common to ssh_session and ssh_bind */ +struct ssh_common_struct { + struct error_struct error; + ssh_callbacks callbacks; /* Callbacks to user functions */ + int log_verbosity; /* verbosity of the log functions */ +}; + +struct ssh_session_struct { + struct ssh_common_struct common; + struct ssh_socket_struct *socket; + char *serverbanner; + char *clientbanner; + int protoversion; + int server; + int client; + int openssh; + uint32_t send_seq; + uint32_t recv_seq; + struct ssh_timestamp last_rekey_time; + + int connected; + /* !=0 when the user got a session handle */ + int alive; + /* two previous are deprecated */ + /* int auth_service_asked; */ + + /* session flags (SSH_SESSION_FLAG_*) */ + int flags; + + /* Extensions negotiated using RFC 8308 */ + uint32_t extensions; + + ssh_string banner; /* that's the issue banner from + the server */ + char *discon_msg; /* disconnect message from + the remote host */ + ssh_buffer in_buffer; + PACKET in_packet; + ssh_buffer out_buffer; + struct ssh_list *out_queue; /* This list is used for delaying packets + when rekeying is required */ + + /* the states are used by the nonblocking stuff to remember */ + /* where it was before being interrupted */ + enum ssh_pending_call_e pending_call_state; + enum ssh_session_state_e session_state; + enum ssh_packet_state_e packet_state; + enum ssh_dh_state_e dh_handshake_state; + enum ssh_channel_request_state_e global_req_state; + struct ssh_agent_state_struct *agent_state; + + struct { + struct ssh_auth_auto_state_struct *auto_state; + enum ssh_auth_service_state_e service_state; + enum ssh_auth_state_e state; + uint32_t supported_methods; + uint32_t current_method; + } auth; + + /* + * RFC 4253, 7.1: if the first_kex_packet_follows flag was set in + * the received SSH_MSG_KEXINIT, but the guess was wrong, this + * field will be set such that the following guessed packet will + * be ignored. Once that packet has been received and ignored, + * this field is cleared. + */ + int first_kex_follows_guess_wrong; + + ssh_buffer in_hashbuf; + ssh_buffer out_hashbuf; + struct ssh_crypto_struct *current_crypto; + struct ssh_crypto_struct *next_crypto; /* next_crypto is going to be used after a SSH2_MSG_NEWKEYS */ + + struct ssh_list *channels; /* linked list of channels */ + int maxchannel; + ssh_agent agent; /* ssh agent */ + +/* keyb interactive data */ + struct ssh_kbdint_struct *kbdint; + struct ssh_gssapi_struct *gssapi; + + /* server host keys */ + struct { + ssh_key rsa_key; + ssh_key dsa_key; + ssh_key ecdsa_key; + ssh_key ed25519_key; + /* The type of host key wanted by client */ + enum ssh_keytypes_e hostkey; + enum ssh_digest_e hostkey_digest; + } srv; + + /* auths accepted by server */ + struct ssh_list *ssh_message_list; /* list of delayed SSH messages */ + int (*ssh_message_callback)( struct ssh_session_struct *session, ssh_message msg, void *userdata); + void *ssh_message_callback_data; + ssh_server_callbacks server_callbacks; + void (*ssh_connection_callback)( struct ssh_session_struct *session); + struct ssh_packet_callbacks_struct default_packet_callbacks; + struct ssh_list *packet_callbacks; + struct ssh_socket_callbacks_struct socket_callbacks; + ssh_poll_ctx default_poll_ctx; + /* options */ +#ifdef WITH_PCAP + ssh_pcap_context pcap_ctx; /* pcap debugging context */ +#endif + struct { + struct ssh_list *identity; + char *username; + char *host; + char *bindaddr; /* bind the client to an ip addr */ + char *sshdir; + char *knownhosts; + char *global_knownhosts; + char *wanted_methods[SSH_KEX_METHODS]; + char *pubkey_accepted_types; + char *ProxyCommand; + char *custombanner; + unsigned long timeout; /* seconds */ + unsigned long timeout_usec; + unsigned int port; + socket_t fd; + int StrictHostKeyChecking; + char compressionlevel; + char *gss_server_identity; + char *gss_client_identity; + int gss_delegate_creds; + int flags; + int nodelay; + bool config_processed; + uint8_t options_seen[SOC_MAX]; + uint64_t rekey_data; + uint32_t rekey_time; + } opts; + /* counters */ + ssh_counter socket_counter; + ssh_counter raw_counter; +}; + +/** @internal + * @brief a termination function evaluates the status of an object + * @param user[in] object to evaluate + * @returns 1 if the polling routine should terminate, 0 instead + */ +typedef int (*ssh_termination_function)(void *user); +int ssh_handle_packets(ssh_session session, int timeout); +int ssh_handle_packets_termination(ssh_session session, + long timeout, + ssh_termination_function fct, + void *user); +void ssh_socket_exception_callback(int code, int errno_code, void *user); + +#endif /* SESSION_H_ */ diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h new file mode 100644 index 0000000..8c14b21 --- /dev/null +++ b/include/libssh/sftp.h @@ -0,0 +1,1044 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * 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 + */ + +/** + * @defgroup libssh_sftp The libssh SFTP API + * + * @brief SFTP handling functions + * + * SFTP commands are channeled by the ssh sftp subsystem. Every packet is + * sent/read using a sftp_packet type structure. Related to these packets, + * most of the server answers are messages having an ID and a message + * specific part. It is described by sftp_message when reading a message, + * the sftp system puts it into the queue, so the process having asked for + * it can fetch it, while continuing to read for other messages (it is + * unspecified in which order messages may be sent back to the client + * + * @{ + */ + +#ifndef SFTP_H +#define SFTP_H + +#include + +#include "libssh.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 +#ifndef uid_t + typedef uint32_t uid_t; +#endif /* uid_t */ +#ifndef gid_t + typedef uint32_t gid_t; +#endif /* gid_t */ +#ifdef _MSC_VER + +# ifndef _SSIZE_T_DEFINED +# undef ssize_t +# include + typedef _W64 SSIZE_T ssize_t; +# define _SSIZE_T_DEFINED +# endif /* _SSIZE_T_DEFINED */ + +#endif /* _MSC_VER */ +#endif /* _WIN32 */ + +#define LIBSFTP_VERSION 3 + +typedef struct sftp_attributes_struct* sftp_attributes; +typedef struct sftp_client_message_struct* sftp_client_message; +typedef struct sftp_dir_struct* sftp_dir; +typedef struct sftp_ext_struct *sftp_ext; +typedef struct sftp_file_struct* sftp_file; +typedef struct sftp_message_struct* sftp_message; +typedef struct sftp_packet_struct* sftp_packet; +typedef struct sftp_request_queue_struct* sftp_request_queue; +typedef struct sftp_session_struct* sftp_session; +typedef struct sftp_status_message_struct* sftp_status_message; +typedef struct sftp_statvfs_struct* sftp_statvfs_t; + +struct sftp_session_struct { + ssh_session session; + ssh_channel channel; + int server_version; + int client_version; + int version; + sftp_request_queue queue; + uint32_t id_counter; + int errnum; + void **handles; + sftp_ext ext; + sftp_packet read_packet; +}; + +struct sftp_packet_struct { + sftp_session sftp; + uint8_t type; + ssh_buffer payload; +}; + +/* file handler */ +struct sftp_file_struct { + sftp_session sftp; + char *name; + uint64_t offset; + ssh_string handle; + int eof; + int nonblocking; +}; + +struct sftp_dir_struct { + sftp_session sftp; + char *name; + ssh_string handle; /* handle to directory */ + ssh_buffer buffer; /* contains raw attributes from server which haven't been parsed */ + uint32_t count; /* counts the number of following attributes structures into buffer */ + int eof; /* end of directory listing */ +}; + +struct sftp_message_struct { + sftp_session sftp; + uint8_t packet_type; + ssh_buffer payload; + uint32_t id; +}; + +/* this is a bunch of all data that could be into a message */ +struct sftp_client_message_struct { + sftp_session sftp; + uint8_t type; + uint32_t id; + char *filename; /* can be "path" */ + uint32_t flags; + sftp_attributes attr; + ssh_string handle; + uint64_t offset; + uint32_t len; + int attr_num; + ssh_buffer attrbuf; /* used by sftp_reply_attrs */ + ssh_string data; /* can be newpath of rename() */ + ssh_buffer complete_message; /* complete message in case of retransmission*/ + char *str_data; /* cstring version of data */ + char *submessage; /* for extended messages */ +}; + +struct sftp_request_queue_struct { + sftp_request_queue next; + sftp_message message; +}; + +/* SSH_FXP_MESSAGE described into .7 page 26 */ +struct sftp_status_message_struct { + uint32_t id; + uint32_t status; + ssh_string error_unused; /* not used anymore */ + ssh_string lang_unused; /* not used anymore */ + char *errormsg; + char *langmsg; +}; + +struct sftp_attributes_struct { + char *name; + char *longname; /* ls -l output on openssh, not reliable else */ + uint32_t flags; + uint8_t type; + uint64_t size; + uint32_t uid; + uint32_t gid; + char *owner; /* set if openssh and version 4 */ + char *group; /* set if openssh and version 4 */ + uint32_t permissions; + uint64_t atime64; + uint32_t atime; + uint32_t atime_nseconds; + uint64_t createtime; + uint32_t createtime_nseconds; + uint64_t mtime64; + uint32_t mtime; + uint32_t mtime_nseconds; + ssh_string acl; + uint32_t extended_count; + ssh_string extended_type; + ssh_string extended_data; +}; + +/** + * @brief SFTP statvfs structure. + */ +struct sftp_statvfs_struct { + uint64_t f_bsize; /** file system block size */ + uint64_t f_frsize; /** fundamental fs block size */ + uint64_t f_blocks; /** number of blocks (unit f_frsize) */ + uint64_t f_bfree; /** free blocks in file system */ + uint64_t f_bavail; /** free blocks for non-root */ + uint64_t f_files; /** total file inodes */ + uint64_t f_ffree; /** free file inodes */ + uint64_t f_favail; /** free file inodes for to non-root */ + uint64_t f_fsid; /** file system id */ + uint64_t f_flag; /** bit mask of f_flag values */ + uint64_t f_namemax; /** maximum filename length */ +}; + +/** + * @brief Creates a new sftp session. + * + * This function creates a new sftp session and allocates a new sftp channel + * with the server inside of the provided ssh session. This function call is + * usually followed by the sftp_init(), which initializes SFTP protocol itself. + * + * @param session The ssh session to use. + * + * @return A new sftp session or NULL on error. + * + * @see sftp_free() + * @see sftp_init() + */ +LIBSSH_API sftp_session sftp_new(ssh_session session); + +/** + * @brief Start a new sftp session with an existing channel. + * + * @param session The ssh session to use. + * @param channel An open session channel with subsystem already allocated + * + * @return A new sftp session or NULL on error. + * + * @see sftp_free() + */ +LIBSSH_API sftp_session sftp_new_channel(ssh_session session, ssh_channel channel); + + +/** + * @brief Close and deallocate a sftp session. + * + * @param sftp The sftp session handle to free. + */ +LIBSSH_API void sftp_free(sftp_session sftp); + +/** + * @brief Initialize the sftp protocol with the server. + * + * This function involves the SFTP protocol initialization (as described + * in the SFTP specification), including the version and extensions negotiation. + * + * @param sftp The sftp session to initialize. + * + * @return 0 on success, < 0 on error with ssh error set. + * + * @see sftp_new() + */ +LIBSSH_API int sftp_init(sftp_session sftp); + +/** + * @brief Get the last sftp error. + * + * Use this function to get the latest error set by a posix like sftp function. + * + * @param sftp The sftp session where the error is saved. + * + * @return The saved error (see server responses), < 0 if an error + * in the function occured. + * + * @see Server responses + */ +LIBSSH_API int sftp_get_error(sftp_session sftp); + +/** + * @brief Get the count of extensions provided by the server. + * + * @param sftp The sftp session to use. + * + * @return The count of extensions provided by the server, 0 on error or + * not available. + */ +LIBSSH_API unsigned int sftp_extensions_get_count(sftp_session sftp); + +/** + * @brief Get the name of the extension provided by the server. + * + * @param sftp The sftp session to use. + * + * @param indexn The index number of the extension name you want. + * + * @return The name of the extension. + */ +LIBSSH_API const char *sftp_extensions_get_name(sftp_session sftp, unsigned int indexn); + +/** + * @brief Get the data of the extension provided by the server. + * + * This is normally the version number of the extension. + * + * @param sftp The sftp session to use. + * + * @param indexn The index number of the extension data you want. + * + * @return The data of the extension. + */ +LIBSSH_API const char *sftp_extensions_get_data(sftp_session sftp, unsigned int indexn); + +/** + * @brief Check if the given extension is supported. + * + * @param sftp The sftp session to use. + * + * @param name The name of the extension. + * + * @param data The data of the extension. + * + * @return 1 if supported, 0 if not. + * + * Example: + * + * @code + * sftp_extension_supported(sftp, "statvfs@openssh.com", "2"); + * @endcode + */ +LIBSSH_API int sftp_extension_supported(sftp_session sftp, const char *name, + const char *data); + +/** + * @brief Open a directory used to obtain directory entries. + * + * @param session The sftp session handle to open the directory. + * @param path The path of the directory to open. + * + * @return A sftp directory handle or NULL on error with ssh and + * sftp error set. + * + * @see sftp_readdir + * @see sftp_closedir + */ +LIBSSH_API sftp_dir sftp_opendir(sftp_session session, const char *path); + +/** + * @brief Get a single file attributes structure of a directory. + * + * @param session The sftp session handle to read the directory entry. + * @param dir The opened sftp directory handle to read from. + * + * @return A file attribute structure or NULL at the end of the + * directory. + * + * @see sftp_opendir() + * @see sftp_attribute_free() + * @see sftp_closedir() + */ +LIBSSH_API sftp_attributes sftp_readdir(sftp_session session, sftp_dir dir); + +/** + * @brief Tell if the directory has reached EOF (End Of File). + * + * @param dir The sftp directory handle. + * + * @return 1 if the directory is EOF, 0 if not. + * + * @see sftp_readdir() + */ +LIBSSH_API int sftp_dir_eof(sftp_dir dir); + +/** + * @brief Get information about a file or directory. + * + * @param session The sftp session handle. + * @param path The path to the file or directory to obtain the + * information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_stat(sftp_session session, const char *path); + +/** + * @brief Get information about a file or directory. + * + * Identical to sftp_stat, but if the file or directory is a symbolic link, + * then the link itself is stated, not the file that it refers to. + * + * @param session The sftp session handle. + * @param path The path to the file or directory to obtain the + * information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_lstat(sftp_session session, const char *path); + +/** + * @brief Get information about a file or directory from a file handle. + * + * @param file The sftp file handle to get the stat information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_fstat(sftp_file file); + +/** + * @brief Free a sftp attribute structure. + * + * @param file The sftp attribute structure to free. + */ +LIBSSH_API void sftp_attributes_free(sftp_attributes file); + +/** + * @brief Close a directory handle opened by sftp_opendir(). + * + * @param dir The sftp directory handle to close. + * + * @return Returns SSH_NO_ERROR or SSH_ERROR if an error occured. + */ +LIBSSH_API int sftp_closedir(sftp_dir dir); + +/** + * @brief Close an open file handle. + * + * @param file The open sftp file handle to close. + * + * @return Returns SSH_NO_ERROR or SSH_ERROR if an error occured. + * + * @see sftp_open() + */ +LIBSSH_API int sftp_close(sftp_file file); + +/** + * @brief Open a file on the server. + * + * @param session The sftp session handle. + * + * @param file The file to be opened. + * + * @param accesstype Is one of O_RDONLY, O_WRONLY or O_RDWR which request + * opening the file read-only,write-only or read/write. + * Acesss may also be bitwise-or'd with one or more of + * the following: + * O_CREAT - If the file does not exist it will be + * created. + * O_EXCL - When used with O_CREAT, if the file already + * exists it is an error and the open will fail. + * O_TRUNC - If the file already exists it will be + * truncated. + * + * @param mode Mode specifies the permissions to use if a new file is + * created. It is modified by the process's umask in + * the usual way: The permissions of the created file are + * (mode & ~umask) + * + * @return A sftp file handle, NULL on error with ssh and sftp + * error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_file sftp_open(sftp_session session, const char *file, int accesstype, + mode_t mode); + +/** + * @brief Make the sftp communication for this file handle non blocking. + * + * @param[in] handle The file handle to set non blocking. + */ +LIBSSH_API void sftp_file_set_nonblocking(sftp_file handle); + +/** + * @brief Make the sftp communication for this file handle blocking. + * + * @param[in] handle The file handle to set blocking. + */ +LIBSSH_API void sftp_file_set_blocking(sftp_file handle); + +/** + * @brief Read from a file using an opened sftp file handle. + * + * @param file The opened sftp file handle to be read from. + * + * @param buf Pointer to buffer to recieve read data. + * + * @param count Size of the buffer in bytes. + * + * @return Number of bytes written, < 0 on error with ssh and sftp + * error set. + * + * @see sftp_get_error() + */ +LIBSSH_API ssize_t sftp_read(sftp_file file, void *buf, size_t count); + +/** + * @brief Start an asynchronous read from a file using an opened sftp file handle. + * + * Its goal is to avoid the slowdowns related to the request/response pattern + * of a synchronous read. To do so, you must call 2 functions: + * + * sftp_async_read_begin() and sftp_async_read(). + * + * The first step is to call sftp_async_read_begin(). This function returns a + * request identifier. The second step is to call sftp_async_read() using the + * returned identifier. + * + * @param file The opened sftp file handle to be read from. + * + * @param len Size to read in bytes. + * + * @return An identifier corresponding to the sent request, < 0 on + * error. + * + * @warning When calling this function, the internal offset is + * updated corresponding to the len parameter. + * + * @warning A call to sftp_async_read_begin() sends a request to + * the server. When the server answers, libssh allocates + * memory to store it until sftp_async_read() is called. + * Not calling sftp_async_read() will lead to memory + * leaks. + * + * @see sftp_async_read() + * @see sftp_open() + */ +LIBSSH_API int sftp_async_read_begin(sftp_file file, uint32_t len); + +/** + * @brief Wait for an asynchronous read to complete and save the data. + * + * @param file The opened sftp file handle to be read from. + * + * @param data Pointer to buffer to recieve read data. + * + * @param len Size of the buffer in bytes. It should be bigger or + * equal to the length parameter of the + * sftp_async_read_begin() call. + * + * @param id The identifier returned by the sftp_async_read_begin() + * function. + * + * @return Number of bytes read, 0 on EOF, SSH_ERROR if an error + * occured, SSH_AGAIN if the file is opened in nonblocking + * mode and the request hasn't been executed yet. + * + * @warning A call to this function with an invalid identifier + * will never return. + * + * @see sftp_async_read_begin() + */ +LIBSSH_API int sftp_async_read(sftp_file file, void *data, uint32_t len, uint32_t id); + +/** + * @brief Write to a file using an opened sftp file handle. + * + * @param file Open sftp file handle to write to. + * + * @param buf Pointer to buffer to write data. + * + * @param count Size of buffer in bytes. + * + * @return Number of bytes written, < 0 on error with ssh and sftp + * error set. + * + * @see sftp_open() + * @see sftp_read() + * @see sftp_close() + */ +LIBSSH_API ssize_t sftp_write(sftp_file file, const void *buf, size_t count); + +/** + * @brief Seek to a specific location in a file. + * + * @param file Open sftp file handle to seek in. + * + * @param new_offset Offset in bytes to seek. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int sftp_seek(sftp_file file, uint32_t new_offset); + +/** + * @brief Seek to a specific location in a file. This is the + * 64bit version. + * + * @param file Open sftp file handle to seek in. + * + * @param new_offset Offset in bytes to seek. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int sftp_seek64(sftp_file file, uint64_t new_offset); + +/** + * @brief Report current byte position in file. + * + * @param file Open sftp file handle. + * + * @return The offset of the current byte relative to the beginning + * of the file associated with the file descriptor. < 0 on + * error. + */ +LIBSSH_API unsigned long sftp_tell(sftp_file file); + +/** + * @brief Report current byte position in file. + * + * @param file Open sftp file handle. + * + * @return The offset of the current byte relative to the beginning + * of the file associated with the file descriptor. < 0 on + * error. + */ +LIBSSH_API uint64_t sftp_tell64(sftp_file file); + +/** + * @brief Rewinds the position of the file pointer to the beginning of the + * file. + * + * @param file Open sftp file handle. + */ +LIBSSH_API void sftp_rewind(sftp_file file); + +/** + * @brief Unlink (delete) a file. + * + * @param sftp The sftp session handle. + * + * @param file The file to unlink/delete. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_unlink(sftp_session sftp, const char *file); + +/** + * @brief Remove a directoy. + * + * @param sftp The sftp session handle. + * + * @param directory The directory to remove. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_rmdir(sftp_session sftp, const char *directory); + +/** + * @brief Create a directory. + * + * @param sftp The sftp session handle. + * + * @param directory The directory to create. + * + * @param mode Specifies the permissions to use. It is modified by the + * process's umask in the usual way: + * The permissions of the created file are (mode & ~umask) + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode); + +/** + * @brief Rename or move a file or directory. + * + * @param sftp The sftp session handle. + * + * @param original The original url (source url) of file or directory to + * be moved. + * + * @param newname The new url (destination url) of the file or directory + * after the move. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_rename(sftp_session sftp, const char *original, const char *newname); + +/** + * @brief Set file attributes on a file, directory or symbolic link. + * + * @param sftp The sftp session handle. + * + * @param file The file which attributes should be changed. + * + * @param attr The file attributes structure with the attributes set + * which should be changed. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr); + +/** + * @brief Change the file owner and group + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param owner The new owner which should be set. + * + * @param group The new group which should be set. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_chown(sftp_session sftp, const char *file, uid_t owner, gid_t group); + +/** + * @brief Change permissions of a file + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param mode Specifies the permissions to use. It is modified by the + * process's umask in the usual way: + * The permissions of the created file are (mode & ~umask) + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_chmod(sftp_session sftp, const char *file, mode_t mode); + +/** + * @brief Change the last modification and access time of a file. + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param times A timeval structure which contains the desired access + * and modification time. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_utimes(sftp_session sftp, const char *file, const struct timeval *times); + +/** + * @brief Create a symbolic link. + * + * @param sftp The sftp session handle. + * + * @param target Specifies the target of the symlink. + * + * @param dest Specifies the path name of the symlink to be created. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_symlink(sftp_session sftp, const char *target, const char *dest); + +/** + * @brief Read the value of a symbolic link. + * + * @param sftp The sftp session handle. + * + * @param path Specifies the path name of the symlink to be read. + * + * @return The target of the link, NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API char *sftp_readlink(sftp_session sftp, const char *path); + +/** + * @brief Get information about a mounted file system. + * + * @param sftp The sftp session handle. + * + * @param path The pathname of any file within the mounted file system. + * + * @return A statvfs structure or NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path); + +/** + * @brief Get information about a mounted file system. + * + * @param file An opened file. + * + * @return A statvfs structure or NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_statvfs_t sftp_fstatvfs(sftp_file file); + +/** + * @brief Free the memory of an allocated statvfs. + * + * @param statvfs_o The statvfs to free. + */ +LIBSSH_API void sftp_statvfs_free(sftp_statvfs_t statvfs_o); + +/** + * @brief Synchronize a file's in-core state with storage device + * + * This calls the "fsync@openssh.com" extention. You should check if the + * extensions is supported using: + * + * @code + * int supported = sftp_extension_supported(sftp, "fsync@openssh.com", "1"); + * @endcode + * + * @param file The opened sftp file handle to sync + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + */ +LIBSSH_API int sftp_fsync(sftp_file file); + +/** + * @brief Canonicalize a sftp path. + * + * @param sftp The sftp session handle. + * + * @param path The path to be canonicalized. + * + * @return A pointer to the newly allocated canonicalized path, + * NULL on error. The caller needs to free the memory + * using ssh_string_free_char(). + */ +LIBSSH_API char *sftp_canonicalize_path(sftp_session sftp, const char *path); + +/** + * @brief Get the version of the SFTP protocol supported by the server + * + * @param sftp The sftp session handle. + * + * @return The server version. + */ +LIBSSH_API int sftp_server_version(sftp_session sftp); + +#ifdef WITH_SERVER +/** + * @brief Create a new sftp server session. + * + * @param session The ssh session to use. + * + * @param chan The ssh channel to use. + * + * @return A new sftp server session. + */ +LIBSSH_API sftp_session sftp_server_new(ssh_session session, ssh_channel chan); + +/** + * @brief Intialize the sftp server. + * + * @param sftp The sftp session to init. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int sftp_server_init(sftp_session sftp); + +/** + * @brief Close and deallocate a sftp server session. + * + * @param sftp The sftp session handle to free. + */ +LIBSSH_API void sftp_server_free(sftp_session sftp); +#endif /* WITH_SERVER */ + +/* sftpserver.c */ + +LIBSSH_API sftp_client_message sftp_get_client_message(sftp_session sftp); +LIBSSH_API void sftp_client_message_free(sftp_client_message msg); +LIBSSH_API uint8_t sftp_client_message_get_type(sftp_client_message msg); +LIBSSH_API const char *sftp_client_message_get_filename(sftp_client_message msg); +LIBSSH_API void sftp_client_message_set_filename(sftp_client_message msg, const char *newname); +LIBSSH_API const char *sftp_client_message_get_data(sftp_client_message msg); +LIBSSH_API uint32_t sftp_client_message_get_flags(sftp_client_message msg); +LIBSSH_API const char *sftp_client_message_get_submessage(sftp_client_message msg); +LIBSSH_API int sftp_send_client_message(sftp_session sftp, sftp_client_message msg); +LIBSSH_API int sftp_reply_name(sftp_client_message msg, const char *name, + sftp_attributes attr); +LIBSSH_API int sftp_reply_handle(sftp_client_message msg, ssh_string handle); +LIBSSH_API ssh_string sftp_handle_alloc(sftp_session sftp, void *info); +LIBSSH_API int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr); +LIBSSH_API void *sftp_handle(sftp_session sftp, ssh_string handle); +LIBSSH_API int sftp_reply_status(sftp_client_message msg, uint32_t status, const char *message); +LIBSSH_API int sftp_reply_names_add(sftp_client_message msg, const char *file, + const char *longname, sftp_attributes attr); +LIBSSH_API int sftp_reply_names(sftp_client_message msg); +LIBSSH_API int sftp_reply_data(sftp_client_message msg, const void *data, int len); +LIBSSH_API void sftp_handle_remove(sftp_session sftp, void *handle); + +/* SFTP commands and constants */ +#define SSH_FXP_INIT 1 +#define SSH_FXP_VERSION 2 +#define SSH_FXP_OPEN 3 +#define SSH_FXP_CLOSE 4 +#define SSH_FXP_READ 5 +#define SSH_FXP_WRITE 6 +#define SSH_FXP_LSTAT 7 +#define SSH_FXP_FSTAT 8 +#define SSH_FXP_SETSTAT 9 +#define SSH_FXP_FSETSTAT 10 +#define SSH_FXP_OPENDIR 11 +#define SSH_FXP_READDIR 12 +#define SSH_FXP_REMOVE 13 +#define SSH_FXP_MKDIR 14 +#define SSH_FXP_RMDIR 15 +#define SSH_FXP_REALPATH 16 +#define SSH_FXP_STAT 17 +#define SSH_FXP_RENAME 18 +#define SSH_FXP_READLINK 19 +#define SSH_FXP_SYMLINK 20 + +#define SSH_FXP_STATUS 101 +#define SSH_FXP_HANDLE 102 +#define SSH_FXP_DATA 103 +#define SSH_FXP_NAME 104 +#define SSH_FXP_ATTRS 105 + +#define SSH_FXP_EXTENDED 200 +#define SSH_FXP_EXTENDED_REPLY 201 + +/* attributes */ +/* sftp draft is completely braindead : version 3 and 4 have different flags for same constants */ +/* and even worst, version 4 has same flag for 2 different constants */ +/* follow up : i won't develop any sftp4 compliant library before having a clarification */ + +#define SSH_FILEXFER_ATTR_SIZE 0x00000001 +#define SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004 +#define SSH_FILEXFER_ATTR_ACCESSTIME 0x00000008 +#define SSH_FILEXFER_ATTR_ACMODTIME 0x00000008 +#define SSH_FILEXFER_ATTR_CREATETIME 0x00000010 +#define SSH_FILEXFER_ATTR_MODIFYTIME 0x00000020 +#define SSH_FILEXFER_ATTR_ACL 0x00000040 +#define SSH_FILEXFER_ATTR_OWNERGROUP 0x00000080 +#define SSH_FILEXFER_ATTR_SUBSECOND_TIMES 0x00000100 +#define SSH_FILEXFER_ATTR_EXTENDED 0x80000000 +#define SSH_FILEXFER_ATTR_UIDGID 0x00000002 + +/* types */ +#define SSH_FILEXFER_TYPE_REGULAR 1 +#define SSH_FILEXFER_TYPE_DIRECTORY 2 +#define SSH_FILEXFER_TYPE_SYMLINK 3 +#define SSH_FILEXFER_TYPE_SPECIAL 4 +#define SSH_FILEXFER_TYPE_UNKNOWN 5 + +/** + * @name Server responses + * + * @brief Responses returned by the sftp server. + * @{ + */ + +/** No error */ +#define SSH_FX_OK 0 +/** End-of-file encountered */ +#define SSH_FX_EOF 1 +/** File doesn't exist */ +#define SSH_FX_NO_SUCH_FILE 2 +/** Permission denied */ +#define SSH_FX_PERMISSION_DENIED 3 +/** Generic failure */ +#define SSH_FX_FAILURE 4 +/** Garbage received from server */ +#define SSH_FX_BAD_MESSAGE 5 +/** No connection has been set up */ +#define SSH_FX_NO_CONNECTION 6 +/** There was a connection, but we lost it */ +#define SSH_FX_CONNECTION_LOST 7 +/** Operation not supported by the server */ +#define SSH_FX_OP_UNSUPPORTED 8 +/** Invalid file handle */ +#define SSH_FX_INVALID_HANDLE 9 +/** No such file or directory path exists */ +#define SSH_FX_NO_SUCH_PATH 10 +/** An attempt to create an already existing file or directory has been made */ +#define SSH_FX_FILE_ALREADY_EXISTS 11 +/** We are trying to write on a write-protected filesystem */ +#define SSH_FX_WRITE_PROTECT 12 +/** No media in remote drive */ +#define SSH_FX_NO_MEDIA 13 + +/** @} */ + +/* file flags */ +#define SSH_FXF_READ 0x01 +#define SSH_FXF_WRITE 0x02 +#define SSH_FXF_APPEND 0x04 +#define SSH_FXF_CREAT 0x08 +#define SSH_FXF_TRUNC 0x10 +#define SSH_FXF_EXCL 0x20 +#define SSH_FXF_TEXT 0x40 + +/* file type flags */ +#define SSH_S_IFMT 00170000 +#define SSH_S_IFSOCK 0140000 +#define SSH_S_IFLNK 0120000 +#define SSH_S_IFREG 0100000 +#define SSH_S_IFBLK 0060000 +#define SSH_S_IFDIR 0040000 +#define SSH_S_IFCHR 0020000 +#define SSH_S_IFIFO 0010000 + +/* rename flags */ +#define SSH_FXF_RENAME_OVERWRITE 0x00000001 +#define SSH_FXF_RENAME_ATOMIC 0x00000002 +#define SSH_FXF_RENAME_NATIVE 0x00000004 + +#define SFTP_OPEN SSH_FXP_OPEN +#define SFTP_CLOSE SSH_FXP_CLOSE +#define SFTP_READ SSH_FXP_READ +#define SFTP_WRITE SSH_FXP_WRITE +#define SFTP_LSTAT SSH_FXP_LSTAT +#define SFTP_FSTAT SSH_FXP_FSTAT +#define SFTP_SETSTAT SSH_FXP_SETSTAT +#define SFTP_FSETSTAT SSH_FXP_FSETSTAT +#define SFTP_OPENDIR SSH_FXP_OPENDIR +#define SFTP_READDIR SSH_FXP_READDIR +#define SFTP_REMOVE SSH_FXP_REMOVE +#define SFTP_MKDIR SSH_FXP_MKDIR +#define SFTP_RMDIR SSH_FXP_RMDIR +#define SFTP_REALPATH SSH_FXP_REALPATH +#define SFTP_STAT SSH_FXP_STAT +#define SFTP_RENAME SSH_FXP_RENAME +#define SFTP_READLINK SSH_FXP_READLINK +#define SFTP_SYMLINK SSH_FXP_SYMLINK +#define SFTP_EXTENDED SSH_FXP_EXTENDED + +/* openssh flags */ +#define SSH_FXE_STATVFS_ST_RDONLY 0x1 /* read-only */ +#define SSH_FXE_STATVFS_ST_NOSUID 0x2 /* no setuid */ + +#ifdef __cplusplus +} +#endif + +#endif /* SFTP_H */ + +/** @} */ diff --git a/include/libssh/sftp_priv.h b/include/libssh/sftp_priv.h new file mode 100644 index 0000000..8392519 --- /dev/null +++ b/include/libssh/sftp_priv.h @@ -0,0 +1,32 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * 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 + */ + +#ifndef SFTP_PRIV_H +#define SFTP_PRIV_H + +sftp_packet sftp_packet_read(sftp_session sftp); +ssize_t sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload); +void sftp_packet_free(sftp_packet packet); +int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr); +sftp_attributes sftp_parse_attr(sftp_session session, + ssh_buffer buf, + int expectname); + +#endif /* SFTP_PRIV_H */ diff --git a/include/libssh/socket.h b/include/libssh/socket.h new file mode 100644 index 0000000..5e345c6 --- /dev/null +++ b/include/libssh/socket.h @@ -0,0 +1,71 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef SOCKET_H_ +#define SOCKET_H_ + +#include "libssh/callbacks.h" +struct ssh_poll_handle_struct; +/* socket.c */ + +struct ssh_socket_struct; +typedef struct ssh_socket_struct* ssh_socket; + +int ssh_socket_init(void); +void ssh_socket_cleanup(void); +ssh_socket ssh_socket_new(ssh_session session); +void ssh_socket_reset(ssh_socket s); +void ssh_socket_free(ssh_socket s); +void ssh_socket_set_fd(ssh_socket s, socket_t fd); +socket_t ssh_socket_get_fd(ssh_socket s); +#ifndef _WIN32 +int ssh_socket_unix(ssh_socket s, const char *path); +void ssh_execute_command(const char *command, socket_t in, socket_t out); +int ssh_socket_connect_proxycommand(ssh_socket s, const char *command); +#endif +void ssh_socket_close(ssh_socket s); +int ssh_socket_write(ssh_socket s,const void *buffer, int len); +int ssh_socket_is_open(ssh_socket s); +int ssh_socket_fd_isset(ssh_socket s, fd_set *set); +void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd); +void ssh_socket_set_fd_in(ssh_socket s, socket_t fd); +void ssh_socket_set_fd_out(ssh_socket s, socket_t fd); +int ssh_socket_nonblocking_flush(ssh_socket s); +void ssh_socket_set_write_wontblock(ssh_socket s); +void ssh_socket_set_read_wontblock(ssh_socket s); +void ssh_socket_set_except(ssh_socket s); +int ssh_socket_get_status(ssh_socket s); +int ssh_socket_get_poll_flags(ssh_socket s); +int ssh_socket_buffered_write_bytes(ssh_socket s); +int ssh_socket_data_available(ssh_socket s); +int ssh_socket_data_writable(ssh_socket s); +int ssh_socket_set_nonblocking(socket_t fd); +int ssh_socket_set_blocking(socket_t fd); + +void ssh_socket_set_callbacks(ssh_socket s, ssh_socket_callbacks callbacks); +int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, socket_t fd, int revents, void *v_s); +struct ssh_poll_handle_struct * ssh_socket_get_poll_handle(ssh_socket s); + +int ssh_socket_connect(ssh_socket s, + const char *host, + uint16_t port, + const char *bind_addr); + +#endif /* SOCKET_H_ */ diff --git a/include/libssh/ssh2.h b/include/libssh/ssh2.h new file mode 100644 index 0000000..3521433 --- /dev/null +++ b/include/libssh/ssh2.h @@ -0,0 +1,81 @@ +#ifndef __SSH2_H +#define __SSH2_H + +#define SSH2_MSG_DISCONNECT 1 +#define SSH2_MSG_IGNORE 2 +#define SSH2_MSG_UNIMPLEMENTED 3 +#define SSH2_MSG_DEBUG 4 +#define SSH2_MSG_SERVICE_REQUEST 5 +#define SSH2_MSG_SERVICE_ACCEPT 6 +#define SSH2_MSG_EXT_INFO 7 + +#define SSH2_MSG_KEXINIT 20 +#define SSH2_MSG_NEWKEYS 21 + +#define SSH2_MSG_KEXDH_INIT 30 +#define SSH2_MSG_KEXDH_REPLY 31 +#define SSH2_MSG_KEX_ECDH_INIT 30 +#define SSH2_MSG_KEX_ECDH_REPLY 31 +#define SSH2_MSG_ECMQV_INIT 30 +#define SSH2_MSG_ECMQV_REPLY 31 + +#define SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30 +#define SSH2_MSG_KEX_DH_GEX_GROUP 31 +#define SSH2_MSG_KEX_DH_GEX_INIT 32 +#define SSH2_MSG_KEX_DH_GEX_REPLY 33 +#define SSH2_MSG_KEX_DH_GEX_REQUEST 34 +#define SSH2_MSG_USERAUTH_REQUEST 50 +#define SSH2_MSG_USERAUTH_FAILURE 51 +#define SSH2_MSG_USERAUTH_SUCCESS 52 +#define SSH2_MSG_USERAUTH_BANNER 53 +#define SSH2_MSG_USERAUTH_PK_OK 60 +#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 +#define SSH2_MSG_USERAUTH_INFO_REQUEST 60 +#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60 +#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 +#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61 +#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63 +#define SSH2_MSG_USERAUTH_GSSAPI_ERROR 64 +#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65 +#define SSH2_MSG_USERAUTH_GSSAPI_MIC 66 + +#define SSH2_MSG_GLOBAL_REQUEST 80 +#define SSH2_MSG_REQUEST_SUCCESS 81 +#define SSH2_MSG_REQUEST_FAILURE 82 +#define SSH2_MSG_CHANNEL_OPEN 90 +#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 +#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 +#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 +#define SSH2_MSG_CHANNEL_DATA 94 +#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 +#define SSH2_MSG_CHANNEL_EOF 96 +#define SSH2_MSG_CHANNEL_CLOSE 97 +#define SSH2_MSG_CHANNEL_REQUEST 98 +#define SSH2_MSG_CHANNEL_SUCCESS 99 +#define SSH2_MSG_CHANNEL_FAILURE 100 + +#define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 +#define SSH2_DISCONNECT_PROTOCOL_ERROR 2 +#define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED 3 +#define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4 +#define SSH2_DISCONNECT_RESERVED 4 +#define SSH2_DISCONNECT_MAC_ERROR 5 +#define SSH2_DISCONNECT_COMPRESSION_ERROR 6 +#define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE 7 +#define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 +#define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 +#define SSH2_DISCONNECT_CONNECTION_LOST 10 +#define SSH2_DISCONNECT_BY_APPLICATION 11 +#define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS 12 +#define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER 13 +#define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 +#define SSH2_DISCONNECT_ILLEGAL_USER_NAME 15 + +#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED 1 +#define SSH2_OPEN_CONNECT_FAILED 2 +#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE 3 +#define SSH2_OPEN_RESOURCE_SHORTAGE 4 + +#define SSH2_EXTENDED_DATA_STDERR 1 + +#endif diff --git a/include/libssh/string.h b/include/libssh/string.h new file mode 100644 index 0000000..8c7db1d --- /dev/null +++ b/include/libssh/string.h @@ -0,0 +1,41 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef STRING_H_ +#define STRING_H_ +#include "libssh/priv.h" + +/* must be 32 bits number + immediately our data */ +#ifdef _MSC_VER +#pragma pack(1) +#endif +struct ssh_string_struct { + uint32_t size; + unsigned char data[1]; +} +#if defined(__GNUC__) +__attribute__ ((packed)) +#endif +#ifdef _MSC_VER +#pragma pack() +#endif +; + +#endif /* STRING_H_ */ diff --git a/include/libssh/threads.h b/include/libssh/threads.h new file mode 100644 index 0000000..522f91d --- /dev/null +++ b/include/libssh/threads.h @@ -0,0 +1,63 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * 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 + */ + +#ifndef THREADS_H_ +#define THREADS_H_ + +#include +#include + +#if HAVE_PTHREAD + +#include +#define SSH_MUTEX pthread_mutex_t + +#if defined(PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP) +#define SSH_MUTEX_STATIC_INIT PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP +#else +#define SSH_MUTEX_STATIC_INIT PTHREAD_MUTEX_INITIALIZER +#endif + +#elif (defined _WIN32) || (defined _WIN64) + +#include +#include +#define SSH_MUTEX CRITICAL_SECTION * +#define SSH_MUTEX_STATIC_INIT NULL + +#else + +# define SSH_MUTEX void * +#define SSH_MUTEX_STATIC_INIT NULL + +#endif + +int ssh_threads_init(void); +void ssh_threads_finalize(void); +const char *ssh_threads_get_type(void); + +void ssh_mutex_lock(SSH_MUTEX *mutex); +void ssh_mutex_unlock(SSH_MUTEX *mutex); + +struct ssh_threads_callbacks_struct *ssh_threads_get_default(void); +int crypto_thread_init(struct ssh_threads_callbacks_struct *user_callbacks); +void crypto_thread_finalize(void); + +#endif /* THREADS_H_ */ diff --git a/include/libssh/token.h b/include/libssh/token.h new file mode 100644 index 0000000..9896fb0 --- /dev/null +++ b/include/libssh/token.h @@ -0,0 +1,48 @@ +/* + * token.h - Tokens list handling + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * 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. + */ + +#ifndef TOKEN_H_ +#define TOKEN_H_ + +struct ssh_tokens_st { + char *buffer; + char **tokens; +}; + +struct ssh_tokens_st *ssh_tokenize(const char *chain, char separator); + +void ssh_tokens_free(struct ssh_tokens_st *tokens); + +char *ssh_find_matching(const char *available_d, + const char *preferred_d); + +char *ssh_find_all_matching(const char *available_d, + const char *preferred_d); + +char *ssh_remove_duplicates(const char *list); + +char *ssh_append_without_duplicates(const char *list, + const char *appended_list); +#endif /* TOKEN_H_ */ diff --git a/include/libssh/wrapper.h b/include/libssh/wrapper.h new file mode 100644 index 0000000..ba64939 --- /dev/null +++ b/include/libssh/wrapper.h @@ -0,0 +1,122 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * 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 + */ + +#ifndef WRAPPER_H_ +#define WRAPPER_H_ + +#include + +#include "config.h" +#include "libssh/libssh.h" +#include "libssh/libcrypto.h" +#include "libssh/libgcrypt.h" +#include "libssh/libmbedcrypto.h" + +enum ssh_kdf_digest { + SSH_KDF_SHA1=1, + SSH_KDF_SHA256, + SSH_KDF_SHA384, + SSH_KDF_SHA512 +}; + +enum ssh_hmac_e { + SSH_HMAC_SHA1 = 1, + SSH_HMAC_SHA256, + SSH_HMAC_SHA512, + SSH_HMAC_MD5, + SSH_HMAC_AEAD_POLY1305, + SSH_HMAC_AEAD_GCM +}; + +enum ssh_des_e { + SSH_3DES, + SSH_DES +}; + +struct ssh_hmac_struct { + const char* name; + enum ssh_hmac_e hmac_type; + bool etm; +}; + +enum ssh_crypto_direction_e { + SSH_DIRECTION_IN = 1, + SSH_DIRECTION_OUT = 2, + SSH_DIRECTION_BOTH = 3, +}; + +struct ssh_cipher_struct; +struct ssh_crypto_struct; + +typedef struct ssh_mac_ctx_struct *ssh_mac_ctx; +MD5CTX md5_init(void); +void md5_update(MD5CTX c, const void *data, unsigned long len); +void md5_final(unsigned char *md,MD5CTX c); + +SHACTX sha1_init(void); +void sha1_update(SHACTX c, const void *data, unsigned long len); +void sha1_final(unsigned char *md,SHACTX c); +void sha1(const unsigned char *digest,int len,unsigned char *hash); + +SHA256CTX sha256_init(void); +void sha256_update(SHA256CTX c, const void *data, unsigned long len); +void sha256_final(unsigned char *md,SHA256CTX c); +void sha256(const unsigned char *digest, int len, unsigned char *hash); + +SHA384CTX sha384_init(void); +void sha384_update(SHA384CTX c, const void *data, unsigned long len); +void sha384_final(unsigned char *md,SHA384CTX c); +void sha384(const unsigned char *digest, int len, unsigned char *hash); + +SHA512CTX sha512_init(void); +void sha512_update(SHA512CTX c, const void *data, unsigned long len); +void sha512_final(unsigned char *md,SHA512CTX c); +void sha512(const unsigned char *digest, int len, unsigned char *hash); + +void evp(int nid, unsigned char *digest, int len, unsigned char *hash, unsigned int *hlen); +EVPCTX evp_init(int nid); +void evp_update(EVPCTX ctx, const void *data, unsigned long len); +void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen); + +HMACCTX hmac_init(const void *key,int len, enum ssh_hmac_e type); +void hmac_update(HMACCTX c, const void *data, unsigned long len); +void hmac_final(HMACCTX ctx,unsigned char *hashmacbuf,unsigned int *len); +size_t hmac_digest_len(enum ssh_hmac_e type); + +int ssh_kdf(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + int key_type, unsigned char *output, + size_t requested_len); + +int crypt_set_algorithms_client(ssh_session session); +int crypt_set_algorithms_server(ssh_session session); +struct ssh_crypto_struct *crypto_new(void); +void crypto_free(struct ssh_crypto_struct *crypto); + +void ssh_reseed(void); +int ssh_crypto_init(void); +void ssh_crypto_finalize(void); + +void ssh_cipher_clear(struct ssh_cipher_struct *cipher); +struct ssh_hmac_struct *ssh_get_hmactab(void); +struct ssh_cipher_struct *ssh_get_ciphertab(void); +const char *ssh_hmac_type_to_string(enum ssh_hmac_e hmac_type, bool etm); + +#endif /* WRAPPER_H_ */ diff --git a/libssh.pc.cmake b/libssh.pc.cmake new file mode 100644 index 0000000..b37cb3f --- /dev/null +++ b/libssh.pc.cmake @@ -0,0 +1,6 @@ +Name: ${PROJECT_NAME} +Description: The SSH Library +Version: ${PROJECT_VERSION} +Libs: -L${CMAKE_INSTALL_FULL_LIBDIR} -lssh +Cflags: -I${CMAKE_INSTALL_FULL_INCLUDEDIR} + diff --git a/src/ABI/current b/src/ABI/current new file mode 100644 index 0000000..dc24a89 --- /dev/null +++ b/src/ABI/current @@ -0,0 +1 @@ +4.8.5 \ No newline at end of file diff --git a/src/ABI/libssh-4.5.0.symbols b/src/ABI/libssh-4.5.0.symbols new file mode 100644 index 0000000..1848fe0 --- /dev/null +++ b/src/ABI/libssh-4.5.0.symbols @@ -0,0 +1,411 @@ +ssh_set_server_callbacks +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_add_channel_callbacks +ssh_remove_channel_callbacks +ssh_threads_set_callbacks +ssh_threads_get_pthread +ssh_threads_get_noop +ssh_set_log_callback +ssh_get_log_callback +ssh_auth_list +ssh_userauth_offer_pubkey +ssh_userauth_pubkey +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_privatekey_file +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_forward_accept +channel_close +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_shell +channel_request_send_signal +channel_request_sftp +channel_request_subsystem +channel_request_x11 +channel_send_eof +channel_select +channel_set_blocking +channel_write +privatekey_free +privatekey_from_file +publickey_free +ssh_publickey_to_file +publickey_from_file +publickey_from_privatekey +publickey_to_string +ssh_try_publickey_from_file +ssh_privatekey_type +ssh_get_pubkey +ssh_message_retrieve +ssh_message_auth_publickey +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char +ssh_blocking_flush +ssh_channel_accept_x11 +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_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_timeout +ssh_channel_read_nonblocking +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_shell +ssh_channel_request_send_signal +ssh_channel_request_send_break +ssh_channel_request_sftp +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_request_auth_agent +ssh_channel_send_eof +ssh_channel_select +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_write +ssh_channel_write_stderr +ssh_channel_window_size +ssh_basename +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_new +ssh_connector_free +ssh_connector_set_in_channel +ssh_connector_set_out_channel +ssh_connector_set_in_fd +ssh_connector_set_out_fd +ssh_copyright +ssh_disconnect +ssh_dirname +ssh_finalize +ssh_channel_accept_forward +ssh_channel_cancel_forward +ssh_channel_listen_forward +ssh_free +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_hexa +ssh_get_issue_banner +ssh_get_openssh_version +ssh_get_server_publickey +ssh_get_publickey_hash +ssh_get_pubkey_hash +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_get_publickey +ssh_get_random +ssh_get_version +ssh_get_status +ssh_get_poll_flags +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_knownhosts_entry_free +ssh_known_hosts_parse_line +ssh_session_has_known_hosts_entry +ssh_session_export_known_hosts_entry +ssh_session_update_known_hosts +ssh_session_is_known_server +ssh_set_log_level +ssh_get_log_level +ssh_get_log_userdata +ssh_set_log_userdata +_ssh_log +ssh_log +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_reply_success +ssh_message_free +ssh_message_get +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_options_get +ssh_options_get_port +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_key_new +ssh_key_free +ssh_key_type +ssh_key_type_to_char +ssh_key_type_from_name +ssh_key_is_public +ssh_key_is_private +ssh_key_cmp +ssh_pki_generate +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_export_privkey_file +ssh_pki_copy_cert_to_privkey +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hexa +ssh_send_ignore +ssh_send_debug +ssh_gssapi_set_creds +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_service_request +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_blocking +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_silent_disconnect +ssh_set_pcap_file +ssh_userauth_none +ssh_userauth_list +ssh_userauth_try_publickey +ssh_userauth_publickey +ssh_userauth_agent +ssh_userauth_publickey_auto +ssh_userauth_password +ssh_userauth_kbdint +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_setanswer +ssh_userauth_gssapi +ssh_version +ssh_write_knownhost +ssh_dump_knownhost +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_from_char +ssh_string_len +ssh_string_new +ssh_string_get_char +ssh_string_to_char +ssh_string_free_char +ssh_getpass +ssh_event_new +ssh_event_add_fd +ssh_event_add_session +ssh_event_add_connector +ssh_event_dopoll +ssh_event_remove_fd +ssh_event_remove_session +ssh_event_remove_connector +ssh_event_free +ssh_get_clientbanner +ssh_get_serverbanner +ssh_get_kex_algo +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_hmac_in +ssh_get_hmac_out +ssh_buffer_new +ssh_buffer_free +ssh_buffer_reinit +ssh_buffer_add_data +ssh_buffer_get_data +ssh_buffer_get +ssh_buffer_get_len +ssh_bind_new +ssh_bind_options_set +ssh_bind_listen +ssh_bind_set_callbacks +ssh_bind_set_blocking +ssh_bind_get_fd +ssh_bind_set_fd +ssh_bind_fd_toaccept +ssh_bind_accept +ssh_bind_accept_fd +ssh_gssapi_get_creds +ssh_handle_key_exchange +ssh_server_init_kex +ssh_bind_free +ssh_set_auth_methods +ssh_message_reply_default +ssh_message_auth_user +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_kbdint_is_response +ssh_message_auth_publickey_state +ssh_message_auth_reply_success +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_set_methods +ssh_message_auth_interactive_request +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_global_request_reply_success +ssh_set_message_callback +ssh_execute_message_callbacks +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_channel +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_command +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_single_connection +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_screen_number +ssh_message_global_request_address +ssh_message_global_request_port +ssh_channel_open_reverse_forward +ssh_channel_request_send_exit_status +ssh_channel_request_send_exit_signal +ssh_send_keepalive +ssh_accept +channel_write_stderr +sftp_new +sftp_new_channel +sftp_free +sftp_init +sftp_get_error +sftp_extensions_get_count +sftp_extensions_get_name +sftp_extensions_get_data +sftp_extension_supported +sftp_opendir +sftp_readdir +sftp_dir_eof +sftp_stat +sftp_lstat +sftp_fstat +sftp_attributes_free +sftp_closedir +sftp_close +sftp_open +sftp_file_set_nonblocking +sftp_file_set_blocking +sftp_read +sftp_async_read_begin +sftp_async_read +sftp_write +sftp_seek +sftp_seek64 +sftp_tell +sftp_tell64 +sftp_rewind +sftp_unlink +sftp_rmdir +sftp_mkdir +sftp_rename +sftp_setstat +sftp_chown +sftp_chmod +sftp_utimes +sftp_symlink +sftp_readlink +sftp_statvfs +sftp_fstatvfs +sftp_statvfs_free +sftp_fsync +sftp_canonicalize_path +sftp_server_version +sftp_server_new +sftp_server_init +sftp_get_client_message +sftp_client_message_free +sftp_client_message_get_type +sftp_client_message_get_filename +sftp_client_message_set_filename +sftp_client_message_get_data +sftp_client_message_get_flags +sftp_send_client_message +sftp_reply_name +sftp_reply_handle +sftp_handle_alloc +sftp_reply_attr +sftp_handle +sftp_reply_status +sftp_reply_names_add +sftp_reply_names +sftp_reply_data +sftp_handle_remove \ No newline at end of file diff --git a/src/ABI/libssh-4.5.1.symbols b/src/ABI/libssh-4.5.1.symbols new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/ABI/libssh-4.5.1.symbols diff --git a/src/ABI/libssh-4.6.0.symbols b/src/ABI/libssh-4.6.0.symbols new file mode 100644 index 0000000..1fd6095 --- /dev/null +++ b/src/ABI/libssh-4.6.0.symbols @@ -0,0 +1,412 @@ +_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_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_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_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_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_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_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_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_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_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +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_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_try_publickey +ssh_version +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/ABI/libssh-4.7.0.symbols b/src/ABI/libssh-4.7.0.symbols new file mode 100644 index 0000000..0e67b4e --- /dev/null +++ b/src/ABI/libssh-4.7.0.symbols @@ -0,0 +1,415 @@ +_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_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_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_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_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_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_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +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_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_try_publickey +ssh_version +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/ABI/libssh-4.7.1.symbols b/src/ABI/libssh-4.7.1.symbols new file mode 100644 index 0000000..0e67b4e --- /dev/null +++ b/src/ABI/libssh-4.7.1.symbols @@ -0,0 +1,415 @@ +_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_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_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_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_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_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_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +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_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_try_publickey +ssh_version +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/ABI/libssh-4.7.2.symbols b/src/ABI/libssh-4.7.2.symbols new file mode 100644 index 0000000..0e67b4e --- /dev/null +++ b/src/ABI/libssh-4.7.2.symbols @@ -0,0 +1,415 @@ +_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_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_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_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_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_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_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +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_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_try_publickey +ssh_version +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/ABI/libssh-4.7.3.symbols b/src/ABI/libssh-4.7.3.symbols new file mode 100644 index 0000000..0e67b4e --- /dev/null +++ b/src/ABI/libssh-4.7.3.symbols @@ -0,0 +1,415 @@ +_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_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_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_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_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_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_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +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_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_try_publickey +ssh_version +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/ABI/libssh-4.7.4.symbols b/src/ABI/libssh-4.7.4.symbols new file mode 100644 index 0000000..0e67b4e --- /dev/null +++ b/src/ABI/libssh-4.7.4.symbols @@ -0,0 +1,415 @@ +_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_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_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_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_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_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_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +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_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_try_publickey +ssh_version +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/ABI/libssh-4.8.0.symbols b/src/ABI/libssh-4.8.0.symbols new file mode 100644 index 0000000..6ef8943 --- /dev/null +++ b/src/ABI/libssh-4.8.0.symbols @@ -0,0 +1,419 @@ +_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_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_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_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +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_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_try_publickey +ssh_version +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/ABI/libssh-4.8.1.symbols b/src/ABI/libssh-4.8.1.symbols new file mode 100644 index 0000000..dce4add --- /dev/null +++ b/src/ABI/libssh-4.8.1.symbols @@ -0,0 +1,421 @@ +_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_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_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_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_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_try_publickey +ssh_version +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/ABI/libssh-4.8.2.symbols b/src/ABI/libssh-4.8.2.symbols new file mode 100644 index 0000000..dce4add --- /dev/null +++ b/src/ABI/libssh-4.8.2.symbols @@ -0,0 +1,421 @@ +_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_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_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_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_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_try_publickey +ssh_version +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/ABI/libssh-4.8.3.symbols b/src/ABI/libssh-4.8.3.symbols new file mode 100644 index 0000000..dce4add --- /dev/null +++ b/src/ABI/libssh-4.8.3.symbols @@ -0,0 +1,421 @@ +_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_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_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_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_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_try_publickey +ssh_version +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/ABI/libssh-4.8.4.symbols b/src/ABI/libssh-4.8.4.symbols new file mode 100644 index 0000000..dce4add --- /dev/null +++ b/src/ABI/libssh-4.8.4.symbols @@ -0,0 +1,421 @@ +_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_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_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_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_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_try_publickey +ssh_version +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/ABI/libssh-4.8.5.symbols b/src/ABI/libssh-4.8.5.symbols new file mode 100644 index 0000000..dce4add --- /dev/null +++ b/src/ABI/libssh-4.8.5.symbols @@ -0,0 +1,421 @@ +_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_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_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_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_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_try_publickey +ssh_version +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 new file mode 100644 index 0000000..532cc14 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,422 @@ +set(LIBSSH_PUBLIC_INCLUDE_DIRS ${libssh_SOURCE_DIR}/include) + +set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${libssh_BINARY_DIR} +) + +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 (MBEDTLS_CRYPTO_LIBRARY) + set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${LIBSSH_PRIVATE_INCLUDE_DIRS} + ${MBEDTLS_INCLUDE_DIR} + ) + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + ${MBEDTLS_CRYPTO_LIBRARY} + ) +endif (MBEDTLS_CRYPTO_LIBRARY) + +if (GCRYPT_LIBRARIES) + set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${LIBSSH_PRIVATE_INCLUDE_DIRS} + ${GCRYPT_INCLUDE_DIR} + ) + + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + ${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} + ) +endif (WITH_ZLIB) + +if (WITH_GSSAPI AND GSSAPI_FOUND) + set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${LIBSSH_PRIVATE_INCLUDE_DIRS} + ${GSSAPI_INCLUDE_DIR} + ) + + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + ${GSSAPI_LIBRARIES} + ) +endif (WITH_GSSAPI AND GSSAPI_FOUND) + +if (WITH_NACL AND NACL_FOUND) + set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${LIBSSH_PRIVATE_INCLUDE_DIRS} + ${NACL_INCLUDE_DIR} + ) + + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + ${NACL_LIBRARY} + ) +endif (WITH_NACL AND NACL_FOUND) + +if (MINGW AND Threads_FOUND) + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + Threads::Threads + ) +endif() + +if (BUILD_STATIC_LIB) + set(LIBSSH_STATIC_LIBRARY + ssh_static + CACHE INTERNAL "libssh static library" + ) +endif (BUILD_STATIC_LIB) + +set(libssh_SRCS + agent.c + auth.c + base64.c + bignum.c + buffer.c + callbacks.c + channels.c + client.c + config.c + connect.c + connector.c + curve25519.c + dh.c + ecdh.c + error.c + getpass.c + init.c + kdf.c + kex.c + known_hosts.c + knownhosts.c + legacy.c + log.c + match.c + messages.c + misc.c + options.c + packet.c + packet_cb.c + packet_crypt.c + pcap.c + pki.c + pki_container_openssh.c + poll.c + session.c + scp.c + socket.c + string.c + threads.c + wrapper.c + external/bcrypt_pbkdf.c + external/blowfish.c + external/chacha.c + external/poly1305.c + chachapoly.c + config_parser.c + token.c + pki_ed25519_common.c +) + +if (DEFAULT_C_NO_DEPRECATION_FLAGS) + set_source_files_properties(known_hosts.c + PROPERTIES + COMPILE_FLAGS ${DEFAULT_C_NO_DEPRECATION_FLAGS}) +endif() + +if (CMAKE_USE_PTHREADS_INIT) + set(libssh_SRCS + ${libssh_SRCS} + threads/noop.c + threads/pthread.c + ) +elseif (CMAKE_USE_WIN32_THREADS_INIT) + set(libssh_SRCS + ${libssh_SRCS} + threads/noop.c + threads/winlocks.c + ) +else() + set(libssh_SRCS + ${libssh_SRCS} + threads/noop.c + ) +endif() + +if (WITH_GCRYPT) + set(libssh_SRCS + ${libssh_SRCS} + threads/libgcrypt.c + libgcrypt.c + gcrypt_missing.c + pki_gcrypt.c + ecdh_gcrypt.c + dh_key.c + pki_ed25519.c + external/ed25519.c + external/fe25519.c + external/ge25519.c + external/sc25519.c + ) +elseif (WITH_MBEDTLS) + set(libssh_SRCS + ${libssh_SRCS} + threads/mbedtls.c + libmbedcrypto.c + mbedcrypto_missing.c + pki_mbedcrypto.c + ecdh_mbedcrypto.c + dh_key.c + pki_ed25519.c + external/ed25519.c + external/fe25519.c + external/ge25519.c + external/sc25519.c + ) +else (WITH_GCRYPT) + set(libssh_SRCS + ${libssh_SRCS} + threads/libcrypto.c + pki_crypto.c + ecdh_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(OPENSSL_VERSION VERSION_LESS "1.1.0") + set(libssh_SRCS ${libssh_SRCS} libcrypto-compat.c) + endif() +endif (WITH_GCRYPT) + +if (WITH_SFTP) + set(libssh_SRCS + ${libssh_SRCS} + sftp.c + ) + + if (WITH_SERVER) + set(libssh_SRCS + ${libssh_SRCS} + sftpserver.c + ) + endif (WITH_SERVER) +endif (WITH_SFTP) + +if (WITH_SERVER) + set(libssh_SRCS + ${libssh_SRCS} + server.c + bind.c + bind_config.c + ) +endif (WITH_SERVER) + +if (WITH_GEX) + set(libssh_SRCS + ${libssh_SRCS} + dh-gex.c + ) +endif (WITH_GEX) + +if (WITH_ZLIB) + set(libssh_SRCS + ${libssh_SRCS} + gzip.c + ) +endif(WITH_ZLIB) + +if (WITH_GSSAPI AND GSSAPI_FOUND) + set(libssh_SRCS + ${libssh_SRCS} + gssapi.c + ) +endif (WITH_GSSAPI AND GSSAPI_FOUND) + +if (NOT WITH_NACL) + if (NOT HAVE_OPENSSL_ED25519) + set(libssh_SRCS + ${libssh_SRCS} + external/curve25519_ref.c + ) + endif (NOT HAVE_OPENSSL_ED25519) +endif (NOT WITH_NACL) + +# Set the path to the default map file +set(MAP_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.map") + +if (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT AND ABIMAP_FOUND) + # Get the list of header files + get_file_list(dev_header_list + DIRECTORIES "${LIBSSH_PUBLIC_INCLUDE_DIRS}/libssh" + FILES_PATTERNS "*.h") + + # Extract the symbols marked as "LIBSSH_API" from the header files + extract_symbols("${PROJECT_NAME}_dev.symbols" + HEADERS_LIST dev_header_list + FILTER_PATTERN "LIBSSH_API") + + if (WITH_ABI_BREAK) + set(ALLOW_ABI_BREAK "BREAK_ABI") + endif() + + # Generate the symbol version map file + generate_map_file("${PROJECT_NAME}_dev.map" + SYMBOLS "${PROJECT_NAME}_dev.symbols" + RELEASE_NAME_VERSION ${PROJECT_NAME}_AFTER_${LIBRARY_VERSION} + CURRENT_MAP ${MAP_PATH} + ${ALLOW_ABI_BREAK}) + + set(libssh_SRCS + ${libssh_SRCS} + ${PROJECT_NAME}_dev.map + ) +endif (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT AND ABIMAP_FOUND) + +# This gets built as a static library, if -DBUILD_SHARED_LIBS=OFF is passed to +# cmake. +add_library(ssh ${libssh_SRCS}) +target_compile_options(ssh + PRIVATE + ${DEFAULT_C_COMPILE_FLAGS} + -D_GNU_SOURCE) +target_include_directories(ssh + PUBLIC + $ + $ + PRIVATE ${LIBSSH_PRIVATE_INCLUDE_DIRS}) + +target_link_libraries(ssh + PRIVATE ${LIBSSH_LINK_LIBRARIES}) + +if (WIN32 AND NOT BUILD_SHARED_LIBS) + set_target_properties(ssh PROPERTIES COMPILE_FLAGS "-DLIBSSH_STATIC") +endif () + +add_library(ssh::ssh ALIAS ssh) + +if (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT) + if (ABIMAP_FOUND) + # Change path to devel map file + 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}\"") +endif (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT) + +set_target_properties(ssh + PROPERTIES + VERSION + ${LIBRARY_VERSION} + SOVERSION + ${LIBRARY_SOVERSION} + DEFINE_SYMBOL + LIBSSH_EXPORTS +) + +if (WITH_VISIBILITY_HIDDEN) + set_target_properties(ssh PROPERTIES COMPILE_FLAGS "-fvisibility=hidden") +endif (WITH_VISIBILITY_HIDDEN) + +if (MINGW) + set_target_properties(ssh PROPERTIES LINK_FLAGS "-Wl,--enable-stdcall-fixup") +endif () + + +install(TARGETS ssh + EXPORT libssh-config + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT libraries) + +install(EXPORT libssh-config + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) + +if (BUILD_STATIC_LIB) + add_library(ssh-static STATIC ${libssh_SRCS}) + target_compile_options(ssh-static + PRIVATE + ${DEFAULT_C_COMPILE_FLAGS} + -D_GNU_SOURCE) + + target_include_directories(ssh-static + PUBLIC + $ + $ + PRIVATE ${LIBSSH_PRIVATE_INCLUDE_DIRS}) + target_link_libraries(ssh-static + PUBLIC ${LIBSSH_LINK_LIBRARIES}) + add_library(ssh::static ALIAS ssh-static) + + if (MSVC) + set(OUTPUT_SUFFIX static) + else (MSVC) + set(OUTPUT_SUFFIX ) + endif (MSVC) + set_target_properties( + ssh-static + PROPERTIES + VERSION + ${LIBRARY_VERSION} + SOVERSION + ${LIBRARY_SOVERSION} + OUTPUT_NAME + ssh + ARCHIVE_OUTPUT_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_SUFFIX} + ) + + if (WIN32) + set_target_properties( + ssh-static + PROPERTIES + COMPILE_FLAGS + "-DLIBSSH_STATIC" + ) + endif (WIN32) +endif (BUILD_STATIC_LIB) + +message(STATUS "Threads_FOUND=${Threads_FOUND}") diff --git a/src/agent.c b/src/agent.c new file mode 100644 index 0000000..62b0093 --- /dev/null +++ b/src/agent.c @@ -0,0 +1,594 @@ +/* + * agent.c - ssh agent functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * 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 + */ + +/* This file is based on authfd.c from OpenSSH */ + +/* + * How does the ssh-agent work? + * + * a) client sends a request to get a list of all keys + * the agent returns the count and all public keys + * b) iterate over them to check if the server likes one + * c) the client sends a sign request to the agent + * type, pubkey as blob, data to sign, flags + * the agent returns the signed data + */ + +#ifndef _WIN32 + +#include "config.h" + +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include + +#include "libssh/agent.h" +#include "libssh/priv.h" +#include "libssh/socket.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/poll.h" +#include "libssh/pki.h" +#include "libssh/bytearray.h" + +/* macro to check for "agent failure" message */ +#define agent_failed(x) \ + (((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) { + char *b = buf; + size_t pos = 0; + ssize_t res; + ssh_pollfd_t pfd; + ssh_channel channel = agent->channel; + socket_t fd; + + /* Using a socket ? */ + if (channel == NULL) { + fd = ssh_socket_get_fd(agent->sock); + pfd.fd = fd; + pfd.events = do_read ? POLLIN : POLLOUT; + + while (n > pos) { + if (do_read) { + res = read(fd, b + pos, n - pos); + } else { + res = write(fd, b + pos, n - pos); + } + switch (res) { + case -1: + if (errno == EINTR) { + continue; + } +#ifdef EWOULDBLOCK + if (errno == EAGAIN || errno == EWOULDBLOCK) { +#else + if (errno == EAGAIN) { +#endif + (void) ssh_poll(&pfd, 1, -1); + continue; + } + return 0; + case 0: + /* read returns 0 on end-of-file */ + errno = do_read ? 0 : EPIPE; + return pos; + default: + pos += (size_t) res; + } + } + return pos; + } else { + /* using an SSH channel */ + while (n > pos){ + if (do_read) + res = ssh_channel_read(channel,b + pos, n-pos, 0); + else + res = ssh_channel_write(channel, b+pos, n-pos); + if (res == SSH_AGAIN) + continue; + if (res == SSH_ERROR) + return 0; + pos += (size_t)res; + } + return pos; + } +} + +ssh_agent ssh_agent_new(struct ssh_session_struct *session) { + ssh_agent agent = NULL; + + agent = malloc(sizeof(struct ssh_agent_struct)); + if (agent == NULL) { + return NULL; + } + ZERO_STRUCTP(agent); + + agent->count = 0; + agent->sock = ssh_socket_new(session); + if (agent->sock == NULL) { + SAFE_FREE(agent); + return NULL; + } + agent->channel = NULL; + return agent; +} + +static void agent_set_channel(struct ssh_agent_struct *agent, ssh_channel channel){ + agent->channel = channel; +} + +/** @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[in] channel a SSH channel from another session. + * @returns SSH_OK in case of success + * SSH_ERROR in case of an error + */ +int ssh_set_agent_channel(ssh_session session, ssh_channel channel){ + if (!session) + return SSH_ERROR; + if (!session->agent){ + ssh_set_error(session, SSH_REQUEST_DENIED, "Session has no active agent"); + return SSH_ERROR; + } + agent_set_channel(session->agent, channel); + return SSH_OK; +} + +/** @brief sets the SSH agent socket. + * The SSH agent will be used to authenticate this client using + * the given socket to communicate with the ssh-agent. The caller + * is responsible for connecting to the socket prior to calling + * this function. + * @returns SSH_OK in case of success + * SSH_ERROR in case of an error + */ +int ssh_set_agent_socket(ssh_session session, socket_t fd){ + if (!session) + return SSH_ERROR; + if (!session->agent){ + ssh_set_error(session, SSH_REQUEST_DENIED, "Session has no active agent"); + return SSH_ERROR; + } + + ssh_socket_set_fd(session->agent->sock, fd); + return SSH_OK; +} + +void ssh_agent_close(struct ssh_agent_struct *agent) { + if (agent == NULL) { + return; + } + + ssh_socket_close(agent->sock); +} + +void ssh_agent_free(ssh_agent agent) { + if (agent) { + if (agent->ident) { + SSH_BUFFER_FREE(agent->ident); + } + if (agent->sock) { + ssh_agent_close(agent); + ssh_socket_free(agent->sock); + } + SAFE_FREE(agent); + } +} + +static int agent_connect(ssh_session session) { + const char *auth_sock = NULL; + + if (session == NULL || session->agent == NULL) { + return -1; + } + + if (session->agent->channel != NULL) + return 0; + + auth_sock = getenv("SSH_AUTH_SOCK"); + + if (auth_sock && *auth_sock) { + if (ssh_socket_unix(session->agent->sock, auth_sock) < 0) { + return -1; + } + return 0; + } + + return -1; +} + +#if 0 +static int agent_decode_reply(struct ssh_session_struct *session, int type) { + switch (type) { + case SSH_AGENT_FAILURE: + case SSH2_AGENT_FAILURE: + case SSH_COM_AGENT2_FAILURE: + ssh_log(session, SSH_LOG_RARE, "SSH_AGENT_FAILURE"); + return 0; + case SSH_AGENT_SUCCESS: + return 1; + default: + ssh_set_error(session, SSH_FATAL, + "Bad response from authentication agent: %d", type); + break; + } + + return -1; +} +#endif + +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}; + + len = ssh_buffer_get_len(request); + SSH_LOG(SSH_LOG_TRACE, "Request length: %u", 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", + strerror(errno)); + return -1; + } + } else { + SSH_LOG(SSH_LOG_WARN, + "atomicio sending request length failed: %s", + strerror(errno)); + 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", + strerror(errno)); + return -1; + } + + len = PULL_BE_U32(payload, 0); + if (len > 256 * 1024) { + ssh_set_error(session, SSH_FATAL, + "Authentication response too long: %u", len); + return -1; + } + SSH_LOG(SSH_LOG_TRACE, "Response length: %u", 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; + } + + return 0; +} + +uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session) +{ + ssh_buffer request = NULL; + ssh_buffer reply = NULL; + unsigned int type = 0; + uint32_t count = 0; + int rc; + + /* send message to the agent requesting the list of identities */ + request = ssh_buffer_new(); + if (request == NULL) { + ssh_set_error_oom(session); + return 0; + } + if (ssh_buffer_add_u8(request, SSH2_AGENTC_REQUEST_IDENTITIES) < 0) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(request); + return 0; + } + + reply = ssh_buffer_new(); + if (reply == NULL) { + SSH_BUFFER_FREE(request); + ssh_set_error(session, SSH_FATAL, "Not enough space"); + return 0; + } + + if (agent_talk(session, request, reply) < 0) { + SSH_BUFFER_FREE(request); + SSH_BUFFER_FREE(reply); + return 0; + } + SSH_BUFFER_FREE(request); + + /* get message type and verify the answer */ + 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); + SSH_BUFFER_FREE(reply); + return 0; + } +#ifdef WORDS_BIGENDIAN + type = bswap_32(type); +#endif + + SSH_LOG(SSH_LOG_WARN, + "Answer type: %d, expected answer: %d", + type, SSH2_AGENT_IDENTITIES_ANSWER); + + if (agent_failed(type)) { + SSH_BUFFER_FREE(reply); + return 0; + } else if (type != SSH2_AGENT_IDENTITIES_ANSWER) { + ssh_set_error(session, SSH_FATAL, + "Bad authentication reply message type: %u", type); + SSH_BUFFER_FREE(reply); + return 0; + } + + rc = ssh_buffer_get_u32(reply, &count); + if (rc != 4) { + ssh_set_error(session, + SSH_FATAL, + "Failed to read count"); + SSH_BUFFER_FREE(reply); + return 0; + } + session->agent->count = ntohl(count); + SSH_LOG(SSH_LOG_DEBUG, "Agent count: %d", + session->agent->count); + if (session->agent->count > 1024) { + ssh_set_error(session, SSH_FATAL, + "Too many identities in authentication reply: %d", + session->agent->count); + SSH_BUFFER_FREE(reply); + return 0; + } + + if (session->agent->ident) { + ssh_buffer_reinit(session->agent->ident); + } + session->agent->ident = reply; + + return session->agent->count; +} + +/* caller has to free commment */ +ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, + char **comment) { + if (ssh_agent_get_ident_count(session) > 0) { + return ssh_agent_get_next_ident(session, comment); + } + + return NULL; +} + +/* caller has to free commment */ +ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session, + char **comment) { + struct ssh_key_struct *key; + struct ssh_string_struct *blob = NULL; + struct ssh_string_struct *tmp = NULL; + int rc; + + if (session->agent->count == 0) { + return NULL; + } + + /* get the blob */ + blob = ssh_buffer_get_ssh_string(session->agent->ident); + if (blob == NULL) { + return NULL; + } + + /* get the comment */ + tmp = ssh_buffer_get_ssh_string(session->agent->ident); + if (tmp == NULL) { + SSH_STRING_FREE(blob); + + return NULL; + } + + if (comment) { + *comment = ssh_string_to_char(tmp); + } else { + SSH_STRING_FREE(blob); + SSH_STRING_FREE(tmp); + + return NULL; + } + SSH_STRING_FREE(tmp); + + /* get key from blob */ + rc = ssh_pki_import_pubkey_blob(blob, &key); + if (rc == SSH_ERROR) { + /* Try again as a cert. */ + rc = ssh_pki_import_cert_blob(blob, &key); + } + SSH_STRING_FREE(blob); + if (rc == SSH_ERROR) { + return NULL; + } + + return key; +} + +int ssh_agent_is_running(ssh_session session) { + if (session == NULL || session->agent == NULL) { + return 0; + } + + if (ssh_socket_is_open(session->agent->sock)) { + return 1; + } else { + if (agent_connect(session) < 0) { + return 0; + } else { + return 1; + } + } + + return 0; +} + +ssh_string ssh_agent_sign_data(ssh_session session, + const ssh_key pubkey, + struct ssh_buffer_struct *data) +{ + ssh_buffer request; + ssh_buffer reply; + ssh_string key_blob; + ssh_string sig_blob; + unsigned int type = 0; + unsigned int flags = 0; + uint32_t dlen; + int rc; + + request = ssh_buffer_new(); + if (request == NULL) { + return NULL; + } + + /* create request */ + if (ssh_buffer_add_u8(request, SSH2_AGENTC_SIGN_REQUEST) < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + + rc = ssh_pki_export_pubkey_blob(pubkey, &key_blob); + if (rc < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + + /* + * make sure it already can contain all the expected content: + * - 1 x uint8_t + * - 2 x uint32_t + * - 1 x ssh_string (uint8_t + data) + */ + rc = ssh_buffer_allocate_size(request, + sizeof(uint8_t) * 2 + + sizeof(uint32_t) * 2 + + ssh_string_len(key_blob)); + if (rc < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + + /* adds len + blob */ + rc = ssh_buffer_add_ssh_string(request, key_blob); + SSH_STRING_FREE(key_blob); + if (rc < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + + /* Add data */ + dlen = ssh_buffer_get_len(data); + if (ssh_buffer_add_u32(request, htonl(dlen)) < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + if (ssh_buffer_add_data(request, ssh_buffer_get(data), dlen) < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + + /* Add Flags: SHA2 extension (RFC 8332) if negotiated */ + if (ssh_key_type_plain(pubkey->type) == SSH_KEYTYPE_RSA) { + if (session->extensions & SSH_EXT_SIG_RSA_SHA512) { + flags |= SSH_AGENT_RSA_SHA2_512; + } else if (session->extensions & SSH_EXT_SIG_RSA_SHA256) { + flags |= SSH_AGENT_RSA_SHA2_256; + } + } + if (ssh_buffer_add_u32(request, htonl(flags)) < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + + reply = ssh_buffer_new(); + if (reply == NULL) { + SSH_BUFFER_FREE(request); + return NULL; + } + + /* send the request */ + if (agent_talk(session, request, reply) < 0) { + SSH_BUFFER_FREE(request); + SSH_BUFFER_FREE(reply); + return NULL; + } + SSH_BUFFER_FREE(request); + + /* check if reply is valid */ + if (ssh_buffer_get_u8(reply, (uint8_t *) &type) != sizeof(uint8_t)) { + SSH_BUFFER_FREE(reply); + return NULL; + } +#ifdef WORDS_BIGENDIAN + type = bswap_32(type); +#endif + + if (agent_failed(type)) { + SSH_LOG(SSH_LOG_WARN, "Agent reports failure in signing the key"); + SSH_BUFFER_FREE(reply); + return NULL; + } else if (type != SSH2_AGENT_SIGN_RESPONSE) { + ssh_set_error(session, + SSH_FATAL, + "Bad authentication response: %u", + type); + SSH_BUFFER_FREE(reply); + return NULL; + } + + sig_blob = ssh_buffer_get_ssh_string(reply); + SSH_BUFFER_FREE(reply); + + return sig_blob; +} + +#endif /* _WIN32 */ diff --git a/src/auth.c b/src/auth.c new file mode 100644 index 0000000..3a5f0ef --- /dev/null +++ b/src/auth.c @@ -0,0 +1,1944 @@ +/* + * auth.c - Authentication with SSH protocols + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 by Aris Adamantiadis + * Copyright (c) 2008-2013 Andreas Schneider + * + * 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 +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/crypto.h" +#include "libssh/ssh2.h" +#include "libssh/buffer.h" +#include "libssh/agent.h" +#include "libssh/misc.h" +#include "libssh/packet.h" +#include "libssh/session.h" +#include "libssh/keys.h" +#include "libssh/auth.h" +#include "libssh/pki.h" +#include "libssh/gssapi.h" +#include "libssh/legacy.h" + +/** + * @defgroup libssh_auth The SSH authentication functions. + * @ingroup libssh + * + * Functions to authenticate with a server. + * + * @{ + */ + +/** + * @internal + * + * @brief Ask access to the ssh-userauth service. + * + * @param[in] session The SSH session handle. + * + * @returns SSH_OK on success, SSH_ERROR on error. + * @returns SSH_AGAIN on nonblocking mode, if calling that function + * again is necessary + */ +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, + "Failed to request \"ssh-userauth\" service"); + } + + return rc; +} + +static int ssh_auth_response_termination(void *user) { + ssh_session session = (ssh_session)user; + switch (session->auth.state) { + case SSH_AUTH_STATE_NONE: + case SSH_AUTH_STATE_KBDINT_SENT: + case SSH_AUTH_STATE_GSSAPI_REQUEST_SENT: + case SSH_AUTH_STATE_GSSAPI_TOKEN: + case SSH_AUTH_STATE_GSSAPI_MIC_SENT: + case SSH_AUTH_STATE_PUBKEY_AUTH_SENT: + case SSH_AUTH_STATE_PUBKEY_OFFER_SENT: + case SSH_AUTH_STATE_PASSWORD_AUTH_SENT: + case SSH_AUTH_STATE_AUTH_NONE_SENT: + return 0; + default: + return 1; + } +} + +static const char *ssh_auth_get_current_method(ssh_session session) +{ + const char *method = "unknown"; + + switch (session->auth.current_method) { + case SSH_AUTH_METHOD_NONE: + method = "none"; + break; + case SSH_AUTH_METHOD_PASSWORD: + method = "password"; + break; + case SSH_AUTH_METHOD_PUBLICKEY: + method = "publickey"; + break; + case SSH_AUTH_METHOD_HOSTBASED: + method = "hostbased"; + break; + case SSH_AUTH_METHOD_INTERACTIVE: + method = "keyboard interactive"; + break; + case SSH_AUTH_METHOD_GSSAPI_MIC: + method = "gssapi"; + break; + default: + break; + } + + return method; +} + +/** + * @internal + * @brief Wait for a response of an authentication function. + * + * @param[in] session The SSH session. + * + * @returns SSH_AUTH_SUCCESS Authentication success, or pubkey accepted + * SSH_AUTH_PARTIAL Authentication succeeded but another mean + * of authentication is needed. + * SSH_AUTH_INFO Data for keyboard-interactive + * 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) { + int rc = SSH_AUTH_ERROR; + + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, + ssh_auth_response_termination, session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + if (!ssh_auth_response_termination(session)) { + return SSH_AUTH_AGAIN; + } + + switch(session->auth.state) { + case SSH_AUTH_STATE_ERROR: + rc = SSH_AUTH_ERROR; + break; + case SSH_AUTH_STATE_FAILED: + rc = SSH_AUTH_DENIED; + break; + case SSH_AUTH_STATE_INFO: + rc = SSH_AUTH_INFO; + break; + case SSH_AUTH_STATE_PARTIAL: + rc = SSH_AUTH_PARTIAL; + break; + case SSH_AUTH_STATE_PK_OK: + case SSH_AUTH_STATE_SUCCESS: + rc = SSH_AUTH_SUCCESS; + break; + case SSH_AUTH_STATE_KBDINT_SENT: + case SSH_AUTH_STATE_GSSAPI_REQUEST_SENT: + case SSH_AUTH_STATE_GSSAPI_TOKEN: + case SSH_AUTH_STATE_GSSAPI_MIC_SENT: + case SSH_AUTH_STATE_PUBKEY_OFFER_SENT: + case SSH_AUTH_STATE_PUBKEY_AUTH_SENT: + case SSH_AUTH_STATE_PASSWORD_AUTH_SENT: + case SSH_AUTH_STATE_AUTH_NONE_SENT: + case SSH_AUTH_STATE_NONE: + /* not reached */ + rc = SSH_AUTH_ERROR; + break; + } + + return rc; +} + +/** + * @internal + * + * @brief Handles a SSH_USERAUTH_BANNER packet. + * + * This banner should be shown to user prior to authentication + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_banner) { + ssh_string banner; + (void)type; + (void)user; + + banner = ssh_buffer_get_ssh_string(packet); + if (banner == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Invalid SSH_USERAUTH_BANNER packet"); + } else { + SSH_LOG(SSH_LOG_DEBUG, + "Received SSH_USERAUTH_BANNER packet"); + if (session->banner != NULL) + SSH_STRING_FREE(session->banner); + session->banner = banner; + } + + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handles a SSH_USERAUTH_FAILURE packet. + * + * This handles the complete or partial authentication failure. + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_failure) { + const char *current_method = ssh_auth_get_current_method(session); + char *auth_methods = NULL; + uint8_t partial = 0; + int rc; + (void) type; + (void) user; + + rc = ssh_buffer_unpack(packet, "sb", &auth_methods, &partial); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, + "Invalid SSH_MSG_USERAUTH_FAILURE message"); + session->auth.state = SSH_AUTH_STATE_ERROR; + goto end; + } + + if (partial) { + session->auth.state = SSH_AUTH_STATE_PARTIAL; + SSH_LOG(SSH_LOG_INFO, + "Partial success for '%s'. Authentication that can continue: %s", + current_method, + auth_methods); + } else { + session->auth.state = SSH_AUTH_STATE_FAILED; + ssh_set_error(session, SSH_REQUEST_DENIED, + "Access denied for '%s'. Authentication that can continue: %s", + current_method, + auth_methods); + SSH_LOG(SSH_LOG_INFO, + "%s", + ssh_get_error(session)); + + } + session->auth.supported_methods = 0; + if (strstr(auth_methods, "password") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_PASSWORD; + } + if (strstr(auth_methods, "keyboard-interactive") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_INTERACTIVE; + } + if (strstr(auth_methods, "publickey") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_PUBLICKEY; + } + if (strstr(auth_methods, "hostbased") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_HOSTBASED; + } + if (strstr(auth_methods, "gssapi-with-mic") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_GSSAPI_MIC; + } + +end: + session->auth.current_method = SSH_AUTH_METHOD_UNKNOWN; + SAFE_FREE(auth_methods); + + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handles a SSH_USERAUTH_SUCCESS packet. + * + * It is also used to communicate the new to the upper levels. + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_success) +{ + struct ssh_crypto_struct *crypto = NULL; + + (void)packet; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_DEBUG, "Authentication successful"); + SSH_LOG(SSH_LOG_TRACE, "Received SSH_USERAUTH_SUCCESS"); + + session->auth.state = SSH_AUTH_STATE_SUCCESS; + session->session_state = SSH_SESSION_STATE_AUTHENTICATED; + session->flags |= SSH_SESSION_FLAG_AUTHENTICATED; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); + if (crypto != NULL && crypto->delayed_compress_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_DEBUG, "Enabling delayed compression IN"); + crypto->do_compress_in = 1; + } + + /* Reset errors by previous authentication methods. */ + ssh_reset_error(session); + session->auth.current_method = SSH_AUTH_METHOD_UNKNOWN; + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handles a SSH_USERAUTH_PK_OK or SSH_USERAUTH_INFO_REQUEST packet. + * + * Since the two types of packets share the same code, additional work is done + * to understand if we are in a public key or keyboard-interactive context. + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok) { + int rc; + + SSH_LOG(SSH_LOG_TRACE, + "Received SSH_USERAUTH_PK_OK/INFO_REQUEST/GSSAPI_RESPONSE"); + + if (session->auth.state == SSH_AUTH_STATE_KBDINT_SENT) { + /* Assuming we are in keyboard-interactive context */ + SSH_LOG(SSH_LOG_TRACE, + "keyboard-interactive context, " + "assuming SSH_USERAUTH_INFO_REQUEST"); + rc = ssh_packet_userauth_info_request(session,type,packet,user); +#ifdef WITH_GSSAPI + } else if (session->auth.state == SSH_AUTH_STATE_GSSAPI_REQUEST_SENT) { + rc = ssh_packet_userauth_gssapi_response(session, type, packet, user); +#endif + } else if (session->auth.state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT) { + session->auth.state = SSH_AUTH_STATE_PK_OK; + SSH_LOG(SSH_LOG_TRACE, "Assuming SSH_USERAUTH_PK_OK"); + rc = SSH_PACKET_USED; + } else { + session->auth.state = SSH_AUTH_STATE_ERROR; + SSH_LOG(SSH_LOG_TRACE, "SSH_USERAUTH_PK_OK received in wrong state"); + rc = SSH_PACKET_USED; + } + + return rc; +} + +/** + * @brief Get available authentication methods from the server. + * + * This requires the function ssh_userauth_none() to be called before the + * methods are available. The server MAY return a list of methods that may + * continue. + * + * @param[in] session The SSH session. + * + * @param[in] username Deprecated, set to NULL. + * + * @returns A bitfield of the fllowing values: + * - SSH_AUTH_METHOD_PASSWORD + * - SSH_AUTH_METHOD_PUBLICKEY + * - SSH_AUTH_METHOD_HOSTBASED + * - SSH_AUTH_METHOD_INTERACTIVE + * + * @warning Other reserved flags may appear in future versions. + * @see ssh_userauth_none() + */ +int ssh_userauth_list(ssh_session session, const char *username) +{ + (void) username; /* unused */ + + if (session == NULL) { + return 0; + } + + return session->auth.supported_methods; +} + +/** + * @brief Try to authenticate through the "none" method. + * + * @param[in] session The ssh session to use. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @returns SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: Authentication failed: use another method\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method\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. + */ +int ssh_userauth_none(ssh_session session, const char *username) { + int rc; + + switch(session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_NONE: + goto pending; + default: + ssh_set_error(session, SSH_FATAL, + "Wrong state (%d) during pending SSH call", + session->pending_call_state); + return SSH_AUTH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* request */ + rc = ssh_buffer_pack(session->out_buffer, "bsss", + SSH2_MSG_USERAUTH_REQUEST, + username ? username : session->opts.username, + "ssh-connection", + "none" + ); + if (rc < 0) { + goto fail; + } + + session->auth.current_method = SSH_AUTH_METHOD_NONE; + session->auth.state = SSH_AUTH_STATE_AUTH_NONE_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_NONE; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/** + * @brief Try to authenticate with the given public key. + * + * To avoid unnecessary processing and user interaction, the following method + * is provided for querying whether authentication using the 'pubkey' would + * be possible. + * + * @param[in] session The SSH session. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @param[in] pubkey The public key to try. + * + * @return SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: The server doesn't accept that public key as an + * authentication token. Try another key or another + * 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_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. + */ +int ssh_userauth_try_publickey(ssh_session session, + const char *username, + const ssh_key pubkey) +{ + ssh_string pubkey_s = NULL; + const char *sig_type_c = NULL; + int rc; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + if (pubkey == NULL || !ssh_key_is_public(pubkey)) { + ssh_set_error(session, SSH_FATAL, "Invalid pubkey"); + return SSH_AUTH_ERROR; + } + + switch(session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_OFFER_PUBKEY: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Wrong state (%d) during pending SSH call", + session->pending_call_state); + return SSH_ERROR; + } + + /* Check if the given public key algorithm is allowed */ + sig_type_c = ssh_key_get_signature_algorithm(session, pubkey->type); + if (sig_type_c == NULL) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Invalid key type (unknown)"); + return SSH_AUTH_DENIED; + } + if (!ssh_key_algorithm_allowed(session, sig_type_c)) { + 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; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* public key */ + rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_s); + if (rc < 0) { + goto fail; + } + + /* request */ + rc = ssh_buffer_pack(session->out_buffer, "bsssbsS", + SSH2_MSG_USERAUTH_REQUEST, + username ? username : session->opts.username, + "ssh-connection", + "publickey", + 0, /* private key ? */ + sig_type_c, /* algo */ + pubkey_s /* public key */ + ); + if (rc < 0) { + goto fail; + } + + SSH_STRING_FREE(pubkey_s); + + session->auth.current_method = SSH_AUTH_METHOD_PUBLICKEY; + session->auth.state = SSH_AUTH_STATE_PUBKEY_OFFER_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_OFFER_PUBKEY; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + SSH_STRING_FREE(pubkey_s); + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/** + * @brief Authenticate with public/private key or certificate. + * + * @param[in] session The SSH session. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @param[in] privkey The private key for authentication. + * + * @return SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: The server doesn't accept that public key as an + * authentication token. Try another key or another + * 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.\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. + */ +int ssh_userauth_publickey(ssh_session session, + const char *username, + const ssh_key privkey) +{ + ssh_string str = NULL; + int rc; + const char *sig_type_c = NULL; + enum ssh_keytypes_e key_type; + enum ssh_digest_e hash_type; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + if (privkey == NULL || !ssh_key_is_private(privkey)) { + ssh_set_error(session, SSH_FATAL, "Invalid private key"); + return SSH_AUTH_ERROR; + } + + switch(session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_PUBKEY: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Bad call during pending SSH call in ssh_userauth_try_publickey"); + return SSH_AUTH_ERROR; + } + + /* Cert auth requires presenting the cert type name (*-cert@openssh.com) */ + key_type = privkey->cert != NULL ? privkey->cert_type : privkey->type; + + /* Check if the given public key algorithm is allowed */ + sig_type_c = ssh_key_get_signature_algorithm(session, key_type); + if (sig_type_c == NULL) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Invalid key type (unknown)"); + return SSH_AUTH_DENIED; + } + if (!ssh_key_algorithm_allowed(session, sig_type_c)) { + 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; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* get public key or cert */ + rc = ssh_pki_export_pubkey_blob(privkey, &str); + if (rc < 0) { + goto fail; + } + + /* 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 */ + str /* public key or cert */ + ); + if (rc < 0) { + goto fail; + } + SSH_STRING_FREE(str); + + /* Get the hash type to be used in the signature based on the key type */ + hash_type = ssh_key_type_to_hash(session, privkey->type); + + /* sign the buffer with the private key */ + str = ssh_pki_do_sign(session, session->out_buffer, privkey, hash_type); + if (str == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, str); + SSH_STRING_FREE(str); + str = NULL; + if (rc < 0) { + goto fail; + } + + session->auth.current_method = SSH_AUTH_METHOD_PUBLICKEY; + session->auth.state = SSH_AUTH_STATE_PUBKEY_AUTH_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_PUBKEY; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + SSH_STRING_FREE(str); + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +#ifndef _WIN32 +static int ssh_userauth_agent_publickey(ssh_session session, + const char *username, + ssh_key pubkey) +{ + ssh_string pubkey_s = NULL; + ssh_string sig_blob = NULL; + const char *sig_type_c = NULL; + 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; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* public key */ + rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_s); + if (rc < 0) { + goto fail; + } + + /* Check if the given public key algorithm is allowed */ + sig_type_c = ssh_key_get_signature_algorithm(session, pubkey->type); + if (sig_type_c == NULL) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Invalid key type (unknown)"); + SSH_STRING_FREE(pubkey_s); + return SSH_AUTH_DENIED; + } + if (!ssh_key_algorithm_allowed(session, sig_type_c)) { + 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); + 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 */ + ); + SSH_STRING_FREE(pubkey_s); + if (rc < 0) { + goto fail; + } + + /* sign the buffer with the private key */ + sig_blob = ssh_pki_do_sign_agent(session, session->out_buffer, pubkey); + if (sig_blob == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, sig_blob); + SSH_STRING_FREE(sig_blob); + if (rc < 0) { + goto fail; + } + + session->auth.current_method = SSH_AUTH_METHOD_PUBLICKEY; + session->auth.state = SSH_AUTH_STATE_PUBKEY_AUTH_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_AGENT; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + SSH_STRING_FREE(pubkey_s); + + return SSH_AUTH_ERROR; +} + +enum ssh_agent_state_e { + SSH_AGENT_STATE_NONE = 0, + SSH_AGENT_STATE_PUBKEY, + SSH_AGENT_STATE_AUTH +}; + +struct ssh_agent_state_struct { + enum ssh_agent_state_e state; + ssh_key pubkey; + char *comment; +}; + +/* Internal function */ +void ssh_agent_state_free(void *data) { + struct ssh_agent_state_struct *state = data; + + if (state) { + SSH_STRING_FREE_CHAR(state->comment); + ssh_key_free(state->pubkey); + free (state); + } +} + +/** + * @brief Try to do public key authentication with ssh agent. + * + * @param[in] session The ssh session to use. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @return SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: The server doesn't accept that public key as an + * authentication token. Try another key or another + * 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_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. + */ +int ssh_userauth_agent(ssh_session session, + const char *username) { + int rc = SSH_AUTH_ERROR; + struct ssh_agent_state_struct *state; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + if (!ssh_agent_is_running(session)) { + return SSH_AUTH_DENIED; + } + + if (!session->agent_state) { + session->agent_state = malloc(sizeof(struct ssh_agent_state_struct)); + if (!session->agent_state) { + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + ZERO_STRUCTP(session->agent_state); + session->agent_state->state=SSH_AGENT_STATE_NONE; + } + + state = session->agent_state; + if (state->pubkey == NULL) { + state->pubkey = ssh_agent_get_first_ident(session, &state->comment); + } + + if (state->pubkey == NULL) { + return SSH_AUTH_DENIED; + } + + while (state->pubkey != NULL) { + if (state->state == SSH_AGENT_STATE_NONE) { + SSH_LOG(SSH_LOG_DEBUG, + "Trying identity %s", state->comment); + } + if (state->state == SSH_AGENT_STATE_NONE || + state->state == SSH_AGENT_STATE_PUBKEY) { + rc = ssh_userauth_try_publickey(session, username, state->pubkey); + if (rc == SSH_AUTH_ERROR) { + ssh_agent_state_free (state); + session->agent_state = NULL; + return rc; + } else if (rc == SSH_AUTH_AGAIN) { + state->state = SSH_AGENT_STATE_PUBKEY; + return rc; + } else if (rc != SSH_AUTH_SUCCESS) { + SSH_LOG(SSH_LOG_DEBUG, + "Public key of %s refused by server", 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); + state->state = SSH_AGENT_STATE_NONE; + continue; + } + + SSH_LOG(SSH_LOG_DEBUG, + "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; + 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); + session->agent_state = NULL; + return rc; + } else if (rc != SSH_AUTH_SUCCESS) { + SSH_LOG(SSH_LOG_INFO, + "Server accepted public key but refused the signature"); + 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; + } + } + + ssh_agent_state_free (session->agent_state); + session->agent_state = NULL; + 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_PUBKEY_ACCEPTED +}; + +struct ssh_auth_auto_state_struct { + enum ssh_auth_auto_state_e state; + struct ssh_iterator *it; + ssh_key privkey; + ssh_key pubkey; +}; + +/** + * @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 + * asker for passphrases (in case the private key is encrypted). + * + * @param[in] session The SSH session. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @param[in] passphrase Use this passphrase to unlock the privatekey. Use NULL + * if you don't want to use a passphrase or the user + * should be asked. + * + * @return SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: The server doesn't accept that public key as an + * authentication token. Try another key or another + * 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_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. + */ +int ssh_userauth_publickey_auto(ssh_session session, + const char *username, + const char *passphrase) +{ + ssh_auth_callback auth_fn = NULL; + void *auth_data = NULL; + struct ssh_auth_auto_state_struct *state; + int rc; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + if (! (session->opts.flags & SSH_OPT_FLAG_PUBKEY_AUTH)) { + session->auth.supported_methods &= ~SSH_AUTH_METHOD_PUBLICKEY; + return SSH_AUTH_DENIED; + } + if (session->common.callbacks) { + auth_fn = session->common.callbacks->auth_function; + auth_data = session->common.callbacks->userdata; + } + if (!session->auth.auto_state) { + session->auth.auto_state = + calloc(1, sizeof(struct ssh_auth_auto_state_struct)); + if (!session->auth.auto_state) { + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + + /* Set state explicitly */ + session->auth.auto_state->state = SSH_AUTH_AUTO_STATE_NONE; + } + 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 ) { + return rc; + } +#endif + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + } + if (state->it == NULL) { + state->it = ssh_list_get_iterator(session->opts.identity); + } + + while (state->it != NULL) { + const char *privkey_file = state->it->data; + char pubkey_file[1024] = {0}; + if (state->state == SSH_AUTH_AUTO_STATE_PUBKEY) { + SSH_LOG(SSH_LOG_DEBUG, + "Trying to authenticate with %s", privkey_file); + state->privkey = NULL; + state->pubkey = NULL; + 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); + 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); + if (rc == SSH_ERROR) { + ssh_set_error(session, + 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; + continue; + } + + rc = ssh_pki_export_privkey_to_pubkey(state->privkey, &state->pubkey); + if (rc == SSH_ERROR) { + 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, + "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 (rc == SSH_AUTH_ERROR) { + SSH_LOG(SSH_LOG_WARN, + "Public key authentication error for %s", + privkey_file); + ssh_key_free(state->privkey); + state->privkey = NULL; + ssh_key_free(state->pubkey); + state->pubkey = NULL; + SAFE_FREE(session->auth.auto_state); + return rc; + } else if (rc == SSH_AUTH_AGAIN) { + return rc; + } else if (rc != SSH_AUTH_SUCCESS) { + 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; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + continue; + } + state->state = SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED; + } + if (state->state == SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED) { + /* 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); + if (rc == SSH_ERROR) { + ssh_key_free(state->pubkey); + state->pubkey=NULL; + ssh_set_error(session, + 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, + "Private key %s doesn't exist.", + privkey_file); + 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); + SAFE_FREE(session->auth.auto_state); + if (rc == SSH_AUTH_SUCCESS) { + SSH_LOG(SSH_LOG_INFO, + "Successfully authenticated using %s", + privkey_file); + } + return rc; + } + if (rc == SSH_AUTH_AGAIN) { + return rc; + } + + ssh_key_free(state->privkey); + ssh_key_free(state->pubkey); + + SSH_LOG(SSH_LOG_WARN, + "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"); + SAFE_FREE(session->auth.auto_state); + return SSH_AUTH_DENIED; +} + +/** + * @brief Try to authenticate by password. + * + * This authentication method is normally disabled on SSHv2 server. You should + * use keyboard-interactive mode. + * + * The 'password' value MUST be encoded UTF-8. It is up to the server how to + * interpret the password and validate it against the password database. + * However, if you read the password in some other encoding, you MUST convert + * the password to UTF-8. + * + * @param[in] session The ssh session to use. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @param[in] password The password to authenticate in UTF-8. + * + * @returns SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: Authentication failed: use another method\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method\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. + * + * @see ssh_userauth_none() + * @see ssh_userauth_kbdint() + */ +int ssh_userauth_password(ssh_session session, + const char *username, + const char *password) { + int rc; + + switch(session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_PASSWORD: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Wrong state (%d) during pending SSH call", + session->pending_call_state); + return SSH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* request */ + rc = ssh_buffer_pack(session->out_buffer, "bsssbs", + SSH2_MSG_USERAUTH_REQUEST, + username ? username : session->opts.username, + "ssh-connection", + "password", + 0, /* false */ + password + ); + if (rc < 0) { + goto fail; + } + + /* Set the buffer as secure to be explicitly zeroed when freed */ + ssh_buffer_set_secure(session->out_buffer); + + session->auth.current_method = SSH_AUTH_METHOD_PASSWORD; + session->auth.state = SSH_AUTH_STATE_PASSWORD_AUTH_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_PASSWORD; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +#ifndef _WIN32 +/* LEGACY */ +int ssh_userauth_agent_pubkey(ssh_session session, + const char *username, + ssh_public_key publickey) +{ + ssh_key key; + int rc; + + key = ssh_key_new(); + if (key == NULL) { + return SSH_AUTH_ERROR; + } + + 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; + key->rsa = publickey->rsa_pub; + + rc = ssh_userauth_agent_publickey(session, username, key); + + key->dsa = NULL; + key->rsa = NULL; + ssh_key_free(key); + + return rc; +} +#endif /* _WIN32 */ + +ssh_kbdint ssh_kbdint_new(void) { + ssh_kbdint kbd; + + kbd = calloc(1, sizeof(struct ssh_kbdint_struct)); + if (kbd == NULL) { + return NULL; + } + + return kbd; +} + + +void ssh_kbdint_free(ssh_kbdint kbd) { + size_t i, n; + + if (kbd == NULL) { + return; + } + + SAFE_FREE(kbd->name); + SAFE_FREE(kbd->instruction); + SAFE_FREE(kbd->echo); + + n = kbd->nprompts; + if (kbd->prompts) { + for (i = 0; i < n; i++) { + if (kbd->prompts[i] != NULL) { + explicit_bzero(kbd->prompts[i], strlen(kbd->prompts[i])); + } + SAFE_FREE(kbd->prompts[i]); + } + SAFE_FREE(kbd->prompts); + } + + n = kbd->nanswers; + if (kbd->answers) { + for (i = 0; i < n; i++) { + if (kbd->answers[i] != NULL) { + explicit_bzero(kbd->answers[i], strlen(kbd->answers[i])); + } + SAFE_FREE(kbd->answers[i]); + } + SAFE_FREE(kbd->answers); + } + + SAFE_FREE(kbd); +} + +void ssh_kbdint_clean(ssh_kbdint kbd) { + size_t i, n; + + if (kbd == NULL) { + return; + } + + SAFE_FREE(kbd->name); + SAFE_FREE(kbd->instruction); + SAFE_FREE(kbd->echo); + + n = kbd->nprompts; + if (kbd->prompts) { + for (i = 0; i < n; i++) { + explicit_bzero(kbd->prompts[i], strlen(kbd->prompts[i])); + SAFE_FREE(kbd->prompts[i]); + } + SAFE_FREE(kbd->prompts); + } + + n = kbd->nanswers; + + if (kbd->answers) { + for (i = 0; i < n; i++) { + explicit_bzero(kbd->answers[i], strlen(kbd->answers[i])); + SAFE_FREE(kbd->answers[i]); + } + SAFE_FREE(kbd->answers); + } + + kbd->nprompts = 0; + kbd->nanswers = 0; +} + +/* + * This function sends the first packet as explained in RFC 3066 section 3.1. + */ +static int ssh_userauth_kbdint_init(ssh_session session, + const char *username, + const char *submethods) +{ + int rc; + + if (session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_INIT) { + goto pending; + } + if (session->pending_call_state != SSH_PENDING_CALL_NONE) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } + if (rc != SSH_OK) { + return SSH_AUTH_ERROR; + } + + /* request */ + rc = ssh_buffer_pack(session->out_buffer, "bsssss", + SSH2_MSG_USERAUTH_REQUEST, + username ? username : session->opts.username, + "ssh-connection", + "keyboard-interactive", + "", /* lang (ignore it) */ + submethods ? submethods : "" + ); + if (rc < 0) { + goto fail; + } + + + session->auth.state = SSH_AUTH_STATE_KBDINT_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_KBDINT_INIT; + + SSH_LOG(SSH_LOG_DEBUG, + "Sending keyboard-interactive init request"); + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) + session->pending_call_state = SSH_PENDING_CALL_NONE; + return rc; +fail: + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/** + * @internal + * + * @brief Send the current challenge response and wait for a reply from the + * server. + * + * @returns SSH_AUTH_INFO if more info is needed + * @returns SSH_AUTH_SUCCESS + * @returns SSH_AUTH_FAILURE + * @returns SSH_AUTH_PARTIAL + */ +static int ssh_userauth_kbdint_send(ssh_session session) +{ + uint32_t i; + int rc; + if (session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_SEND) + goto pending; + if (session->pending_call_state != SSH_PENDING_CALL_NONE) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + rc = ssh_buffer_pack(session->out_buffer, "bd", + SSH2_MSG_USERAUTH_INFO_RESPONSE, + session->kbdint->nprompts); + if (rc < 0) { + goto fail; + } + + for (i = 0; i < session->kbdint->nprompts; i++) { + rc = ssh_buffer_pack(session->out_buffer, "s", + session->kbdint->answers && session->kbdint->answers[i] ? + session->kbdint->answers[i]:""); + if (rc < 0) { + goto fail; + } + } + + session->auth.current_method = SSH_AUTH_METHOD_INTERACTIVE; + session->auth.state = SSH_AUTH_STATE_KBDINT_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_KBDINT_SEND; + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + SSH_LOG(SSH_LOG_DEBUG, + "Sending keyboard-interactive response packet"); + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) + session->pending_call_state = SSH_PENDING_CALL_NONE; + return rc; +fail: + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/** + * @internal + * @brief handles a SSH_USERAUTH_INFO_REQUEST packet, as used in + * keyboard-interactive authentication, and changes the + * authentication state. + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request) { + ssh_string tmp = NULL; + uint32_t nprompts; + uint32_t i; + int rc; + (void)user; + (void)type; + + + if (session->kbdint == NULL) { + session->kbdint = ssh_kbdint_new(); + if (session->kbdint == NULL) { + ssh_set_error_oom(session); + return SSH_PACKET_USED; + } + } else { + ssh_kbdint_clean(session->kbdint); + } + + rc = ssh_buffer_unpack(packet, "ssSd", + &session->kbdint->name, /* name of the "asking" window shown to client */ + &session->kbdint->instruction, + &tmp, /* to ignore */ + &nprompts + ); + + /* We don't care about tmp */ + SSH_STRING_FREE(tmp); + + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Invalid USERAUTH_INFO_REQUEST msg"); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_DEBUG, + "%d 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)", + nprompts, nprompts); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + return SSH_PACKET_USED; + } + + session->kbdint->nprompts = nprompts; + session->kbdint->nanswers = nprompts; + session->kbdint->prompts = calloc(nprompts, sizeof(char *)); + if (session->kbdint->prompts == NULL) { + session->kbdint->nprompts = 0; + ssh_set_error_oom(session); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + return SSH_PACKET_USED; + } + + session->kbdint->echo = calloc(nprompts, sizeof(unsigned char)); + if (session->kbdint->echo == NULL) { + session->kbdint->nprompts = 0; + ssh_set_error_oom(session); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + return SSH_PACKET_USED; + } + + for (i = 0; i < nprompts; i++) { + rc = ssh_buffer_unpack(packet, "sb", + &session->kbdint->prompts[i], + &session->kbdint->echo[i]); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "Short INFO_REQUEST packet"); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + return SSH_PACKET_USED; + } + } + session->auth.state=SSH_AUTH_STATE_INFO; + + return SSH_PACKET_USED; +} + +/** + * @brief Try to authenticate through the "keyboard-interactive" method. + * + * @param[in] session The ssh session to use. + * + * @param[in] user The username to authenticate. You can specify NULL if + * ssh_option_set_username() has been used. You cannot try + * two different logins in a row. + * + * @param[in] submethods Undocumented. Set it to NULL. + * + * @returns SSH_AUTH_ERROR: A serious error happened\n + * SSH_AUTH_DENIED: Authentication failed : use another method\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method\n + * SSH_AUTH_SUCCESS: Authentication success\n + * SSH_AUTH_INFO: The server asked some questions. Use + * ssh_userauth_kbdint_getnprompts() and such.\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @see ssh_userauth_kbdint_getnprompts() + * @see ssh_userauth_kbdint_getname() + * @see ssh_userauth_kbdint_getinstruction() + * @see ssh_userauth_kbdint_getprompt() + * @see ssh_userauth_kbdint_setanswer() + */ +int ssh_userauth_kbdint(ssh_session session, const char *user, + const char *submethods) { + int rc = SSH_AUTH_ERROR; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + if ((session->pending_call_state == SSH_PENDING_CALL_NONE && session->kbdint == NULL) || + session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_INIT) + rc = ssh_userauth_kbdint_init(session, user, submethods); + else if (session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_SEND || + session->kbdint != NULL) { + /* + * If we are at this point, it is because session->kbdint exists. + * It means the user has set some information there we need to send + * the server and then we need to ack the status (new questions or ok + * pass in). + * It is possible that session->kbdint is NULL while we're waiting for + * a reply, hence the test for the pending call. + */ + rc = ssh_userauth_kbdint_send(session); + } else { + /* We are here because session->kbdint == NULL & state != NONE. + * This should not happen + */ + rc = SSH_AUTH_ERROR; + ssh_set_error(session, SSH_FATAL, "Invalid state in %s", __func__); + } + return rc; +} + +/** + * @brief Get the number of prompts (questions) the server has given. + * + * Once you have called ssh_userauth_kbdint() and received SSH_AUTH_INFO return + * code, this function can be used to retrieve information about the keyboard + * interactive authentication questions sent by the remote host. + * + * @param[in] session The ssh session to use. + * + * @returns The number of prompts. + */ +int ssh_userauth_kbdint_getnprompts(ssh_session session) { + if (session == NULL) { + return SSH_ERROR; + } + if (session->kbdint == NULL) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + return session->kbdint->nprompts; +} + +/** + * @brief Get the "name" of the message block. + * + * Once you have called ssh_userauth_kbdint() and received SSH_AUTH_INFO return + * code, this function can be used to retrieve information about the keyboard + * interactive authentication questions sent by the remote host. + * + * @param[in] session The ssh session to use. + * + * @returns The name of the message block. Do not free it. + */ +const char *ssh_userauth_kbdint_getname(ssh_session session) { + if (session == NULL) { + return NULL; + } + if (session->kbdint == NULL) { + ssh_set_error_invalid(session); + return NULL; + } + return session->kbdint->name; +} + +/** + * @brief Get the "instruction" of the message block. + * + * Once you have called ssh_userauth_kbdint() and received SSH_AUTH_INFO return + * code, this function can be used to retrieve information about the keyboard + * interactive authentication questions sent by the remote host. + * + * @param[in] session The ssh session to use. + * + * @returns The instruction of the message block. + */ + +const char *ssh_userauth_kbdint_getinstruction(ssh_session session) { + if (session == NULL) + return NULL; + if (session->kbdint == NULL) { + ssh_set_error_invalid(session); + return NULL; + } + return session->kbdint->instruction; +} + +/** + * @brief Get a prompt from a message block. + * + * Once you have called ssh_userauth_kbdint() and received SSH_AUTH_INFO return + * code, this function can be used to retrieve information about the keyboard + * interactive authentication questions sent by the remote host. + * + * @param[in] session The ssh session to use. + * + * @param[in] i The index number of the i'th prompt. + * + * @param[out] echo This is an optional variable. You can obtain a + * boolean if the user input should be echoed or + * hidden. For passwords it is usually hidden. + * + * @returns A pointer to the prompt. Do not free it. + * + * @code + * const char prompt; + * char echo; + * + * prompt = ssh_userauth_kbdint_getprompt(session, 0, &echo); + * if (echo) ... + * @endcode + */ +const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, + char *echo) { + if (session == NULL) + return NULL; + if (session->kbdint == NULL) { + ssh_set_error_invalid(session); + return NULL; + } + if (i > session->kbdint->nprompts) { + ssh_set_error_invalid(session); + return NULL; + } + + if (echo) { + *echo = (char)session->kbdint->echo[i]; + } + + return session->kbdint->prompts[i]; +} + +#ifdef WITH_SERVER +/** + * @brief Get the number of answers the client has given. + * + * @param[in] session The ssh session to use. + * + * @returns The number of answers. + */ +int ssh_userauth_kbdint_getnanswers(ssh_session session) { + if (session == NULL || session->kbdint == NULL) { + return SSH_ERROR; + } + return session->kbdint->nanswers; +} + +/** + * @brief Get the answer for 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. + */ +const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) { + if (session == NULL || session->kbdint == NULL + || session->kbdint->answers == NULL) { + return NULL; + } + if (i >= session->kbdint->nanswers) { + return NULL; + } + + return session->kbdint->answers[i]; +} +#endif + +/** + * @brief Set the answer for a question from a message block. + * + * If you have called ssh_userauth_kbdint() and got SSH_AUTH_INFO, this + * function returns the questions from the server. + * + * @param[in] session The ssh session to use. + * + * @param[in] i index The number of the ith prompt. + * + * @param[in] answer The answer to give to the server. The answer MUST be + * encoded UTF-8. It is up to the server how to interpret + * the value and validate it. However, if you read the + * answer in some other encoding, you MUST convert it to + * UTF-8. + * + * @return 0 on success, < 0 on error. + */ +int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, + const char *answer) { + if (session == NULL) { + return -1; + } + if (answer == NULL || session->kbdint == NULL || + i >= session->kbdint->nprompts) { + ssh_set_error_invalid(session); + return -1; + } + + if (session->kbdint->answers == NULL) { + session->kbdint->answers = calloc(session->kbdint->nprompts, sizeof(char *)); + if (session->kbdint->answers == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + + if (session->kbdint->answers[i]) { + explicit_bzero(session->kbdint->answers[i], + strlen(session->kbdint->answers[i])); + SAFE_FREE(session->kbdint->answers[i]); + } + + session->kbdint->answers[i] = strdup(answer); + if (session->kbdint->answers[i] == NULL) { + ssh_set_error_oom(session); + return -1; + } + + return 0; +} + +/** + * @brief Try to authenticate through the "gssapi-with-mic" method. + * + * @param[in] session The ssh session to use. + * + * @returns SSH_AUTH_ERROR: A serious error happened\n + * SSH_AUTH_DENIED: Authentication failed : use another method\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method\n + * SSH_AUTH_SUCCESS: Authentication success\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + */ +int ssh_userauth_gssapi(ssh_session session) { + int rc = SSH_AUTH_DENIED; +#ifdef WITH_GSSAPI + switch(session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_GSSAPI_MIC: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Wrong state (%d) during pending SSH call", + session->pending_call_state); + return SSH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + SSH_LOG(SSH_LOG_PROTOCOL, "Authenticating with gssapi-with-mic"); + + session->auth.current_method = SSH_AUTH_METHOD_GSSAPI_MIC; + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_AUTH_GSSAPI_MIC; + rc = ssh_gssapi_auth_mic(session); + + if (rc == SSH_AUTH_ERROR || rc == SSH_AUTH_DENIED) { + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_NONE; + return rc; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } +#else + (void) session; /* unused */ +#endif + return rc; +} + +/** @} */ diff --git a/src/base64.c b/src/base64.c new file mode 100644 index 0000000..4148f49 --- /dev/null +++ b/src/base64.c @@ -0,0 +1,298 @@ +/* + * base64.c - support for base64 alphabet system, described in RFC1521 + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2005 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. + */ + +/* just the dirtiest part of code i ever made */ +#include "config.h" + +#include + +#include "libssh/priv.h" +#include "libssh/buffer.h" + +static +const uint8_t alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +/* Transformations */ +#define SET_A(n, i) do { (n) |= ((i) & 63) <<18; } while (0) +#define SET_B(n, i) do { (n) |= ((i) & 63) <<12; } while (0) +#define SET_C(n, i) do { (n) |= ((i) & 63) << 6; } while (0) +#define SET_D(n, i) do { (n) |= ((i) & 63); } while (0) + +#define GET_A(n) (unsigned char) (((n) & 0xff0000) >> 16) +#define GET_B(n) (unsigned char) (((n) & 0xff00) >> 8) +#define GET_C(n) (unsigned char) ((n) & 0xff) + +static int _base64_to_bin(unsigned char dest[3], const char *source, int num); +static int get_equals(char *string); + +/* First part: base64 to binary */ + +/** + * @internal + * + * @brief Translates a base64 string into a binary one. + * + * @returns A buffer containing the decoded string, NULL if something went + * wrong (e.g. incorrect char). + */ +ssh_buffer base64_to_bin(const char *source) { + ssh_buffer buffer = NULL; + unsigned char block[3]; + char *base64; + char *ptr; + size_t len; + int equals; + + base64 = strdup(source); + if (base64 == NULL) { + return NULL; + } + ptr = base64; + + /* Get the number of equals signs, which mirrors the padding */ + equals = get_equals(ptr); + if (equals > 2) { + SAFE_FREE(base64); + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + SAFE_FREE(base64); + return NULL; + } + /* + * The base64 buffer often contains sensitive data. Make sure we don't leak + * sensitive data + */ + ssh_buffer_set_secure(buffer); + + len = strlen(ptr); + while (len > 4) { + if (_base64_to_bin(block, ptr, 3) < 0) { + goto error; + } + if (ssh_buffer_add_data(buffer, block, 3) < 0) { + goto error; + } + len -= 4; + ptr += 4; + } + + /* + * Depending on the number of bytes resting, there are 3 possibilities + * from the RFC. + */ + switch (len) { + /* + * (1) The final quantum of encoding input is an integral multiple of + * 24 bits. Here, the final unit of encoded output will be an integral + * multiple of 4 characters with no "=" padding + */ + case 4: + if (equals != 0) { + goto error; + } + if (_base64_to_bin(block, ptr, 3) < 0) { + goto error; + } + if (ssh_buffer_add_data(buffer, block, 3) < 0) { + goto error; + } + SAFE_FREE(base64); + + return buffer; + /* + * (2) The final quantum of encoding input is exactly 8 bits; here, the + * final unit of encoded output will be two characters followed by + * two "=" padding characters. + */ + case 2: + if (equals != 2){ + goto error; + } + + if (_base64_to_bin(block, ptr, 1) < 0) { + goto error; + } + if (ssh_buffer_add_data(buffer, block, 1) < 0) { + goto error; + } + SAFE_FREE(base64); + + return buffer; + /* + * The final quantum of encoding input is exactly 16 bits. Here, the final + * unit of encoded output will be three characters followed by one "=" + * padding character. + */ + case 3: + if (equals != 1) { + goto error; + } + if (_base64_to_bin(block, ptr, 2) < 0) { + goto error; + } + if (ssh_buffer_add_data(buffer,block,2) < 0) { + goto error; + } + SAFE_FREE(base64); + + return buffer; + default: + /* 4,3,2 are the only padding size allowed */ + goto error; + } + +error: + SAFE_FREE(base64); + SSH_BUFFER_FREE(buffer); + return NULL; +} + +#define BLOCK(letter, n) do {ptr = strchr((const char *)alphabet, source[n]); \ + if(!ptr) return -1; \ + i = ptr - (const char *)alphabet; \ + SET_##letter(*block, i); \ + } while(0) + +/* Returns 0 if ok, -1 if not (ie invalid char into the stuff) */ +static int to_block4(unsigned long *block, const char *source, int num) { + const char *ptr = NULL; + unsigned int i; + + *block = 0; + if (num < 1) { + return 0; + } + + BLOCK(A, 0); /* 6 bit */ + BLOCK(B,1); /* 12 bit */ + + if (num < 2) { + return 0; + } + + BLOCK(C, 2); /* 18 bit */ + + if (num < 3) { + return 0; + } + + BLOCK(D, 3); /* 24 bit */ + + return 0; +} + +/* num = numbers of final bytes to be decoded */ +static int _base64_to_bin(unsigned char dest[3], const char *source, int num) { + unsigned long block; + + if (to_block4(&block, source, num) < 0) { + return -1; + } + dest[0] = GET_A(block); + dest[1] = GET_B(block); + dest[2] = GET_C(block); + + return 0; +} + +/* Count the number of "=" signs and replace them by zeroes */ +static int get_equals(char *string) { + char *ptr = string; + int num = 0; + + while ((ptr=strchr(ptr,'=')) != NULL) { + num++; + *ptr = '\0'; + ptr++; + } + + return num; +} + +/* thanks sysk for debugging my mess :) */ +static void _bin_to_base64(uint8_t *dest, + const uint8_t source[3], + size_t len) +{ +#define BITS(n) ((1 << (n)) - 1) + switch (len) { + case 1: + dest[0] = alphabet[(source[0] >> 2)]; + dest[1] = alphabet[((source[0] & BITS(2)) << 4)]; + dest[2] = '='; + dest[3] = '='; + break; + case 2: + dest[0] = alphabet[source[0] >> 2]; + dest[1] = alphabet[(source[1] >> 4) | ((source[0] & BITS(2)) << 4)]; + dest[2] = alphabet[(source[1] & BITS(4)) << 2]; + dest[3] = '='; + break; + case 3: + dest[0] = alphabet[(source[0] >> 2)]; + dest[1] = alphabet[(source[1] >> 4) | ((source[0] & BITS(2)) << 4)]; + dest[2] = alphabet[(source[2] >> 6) | (source[1] & BITS(4)) << 2]; + dest[3] = alphabet[source[2] & BITS(6)]; + break; + } +#undef BITS +} + +/** + * @internal + * + * @brief Converts binary data to a base64 string. + * + * @returns the converted string + */ +uint8_t *bin_to_base64(const uint8_t *source, size_t len) +{ + uint8_t *base64 = NULL; + uint8_t *ptr = NULL; + size_t flen = len + (3 - (len % 3)); /* round to upper 3 multiple */ + flen = (4 * flen) / 3 + 1; + + base64 = malloc(flen); + if (base64 == NULL) { + return NULL; + } + ptr = base64; + + while(len > 0){ + _bin_to_base64(ptr, source, len > 3 ? 3 : len); + ptr += 4; + if (len < 3) { + break; + } + source += 3; + len -= 3; + } + ptr[0] = '\0'; + + return base64; +} diff --git a/src/bignum.c b/src/bignum.c new file mode 100644 index 0000000..ef8de31 --- /dev/null +++ b/src/bignum.c @@ -0,0 +1,97 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2014 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 + +#include "libssh/priv.h" +#include "libssh/bignum.h" +#include "libssh/string.h" + +ssh_string ssh_make_bignum_string(bignum num) { + ssh_string ptr = NULL; + size_t pad = 0; + size_t len = bignum_num_bytes(num); + size_t bits = bignum_num_bits(num); + + if (len == 0) { + return NULL; + } + + /* If the first bit is set we have a negative number */ + if (!(bits % 8) && bignum_is_bit_set(num, bits - 1)) { + pad++; + } + +#ifdef DEBUG_CRYPTO + SSH_LOG(SSH_LOG_TRACE, + "%zu bits, %zu bytes, %zu padding\n", + bits, len, pad); +#endif /* DEBUG_CRYPTO */ + + ptr = ssh_string_new(len + pad); + if (ptr == NULL) { + return NULL; + } + + /* We have a negative number so we need a leading zero */ + if (pad) { + ptr->data[0] = 0; + } + + bignum_bn2bin(num, len, ptr->data + pad); + + return ptr; +} + +bignum ssh_make_string_bn(ssh_string string) +{ + bignum bn = NULL; + size_t len = ssh_string_len(string); + +#ifdef DEBUG_CRYPTO + SSH_LOG(SSH_LOG_TRACE, + "Importing a %zu bits, %zu bytes object ...\n", + len * 8, len); +#endif /* DEBUG_CRYPTO */ + + bignum_bin2bn(string->data, len, &bn); + + return bn; +} + +/* prints the bignum on stderr */ +void ssh_print_bignum(const char *name, const_bignum num) +{ + unsigned char *hex = NULL; + 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 +} diff --git a/src/bind.c b/src/bind.c new file mode 100644 index 0000000..fa8df9e --- /dev/null +++ b/src/bind.c @@ -0,0 +1,586 @@ +/* + * bind.c : all ssh_bind functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2004-2005 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 +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/bind.h" +#include "libssh/libssh.h" +#include "libssh/server.h" +#include "libssh/pki.h" +#include "libssh/buffer.h" +#include "libssh/socket.h" +#include "libssh/session.h" +#include "libssh/token.h" + +/** + * @addtogroup libssh_server + * + * @{ + */ + + +#ifdef _WIN32 +#include +#include +#include + +/* + * is necessary for getaddrinfo before Windows XP, but it isn't + * available on some platforms like MinGW. + */ +#ifdef HAVE_WSPIAPI_H +# include +#endif + +#define SOCKOPT_TYPE_ARG4 char + +#else /* _WIN32 */ + +#include +#include +#include +#define SOCKOPT_TYPE_ARG4 int + +#endif /* _WIN32 */ + +static socket_t bind_socket(ssh_bind sshbind, const char *hostname, + int port) { + char port_c[6]; + struct addrinfo *ai; + struct addrinfo hints; + int opt = 1; + socket_t s; + int rc; + + ZERO_STRUCT(hints); + + hints.ai_flags = AI_PASSIVE; + hints.ai_socktype = SOCK_STREAM; + + snprintf(port_c, 6, "%d", port); + rc = getaddrinfo(hostname, port_c, &hints, &ai); + if (rc != 0) { + ssh_set_error(sshbind, + SSH_FATAL, + "Resolving %s: %s", hostname, gai_strerror(rc)); + return -1; + } + + s = socket (ai->ai_family, + ai->ai_socktype, + ai->ai_protocol); + if (s == SSH_INVALID_SOCKET) { + ssh_set_error(sshbind, SSH_FATAL, "%s", strerror(errno)); + freeaddrinfo (ai); + return -1; + } + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (char *)&opt, sizeof(opt)) < 0) { + ssh_set_error(sshbind, + SSH_FATAL, + "Setting socket options failed: %s", + strerror(errno)); + freeaddrinfo (ai); + CLOSE_SOCKET(s); + return -1; + } + + if (bind(s, ai->ai_addr, ai->ai_addrlen) != 0) { + ssh_set_error(sshbind, + SSH_FATAL, + "Binding to %s:%d: %s", + hostname, + port, + strerror(errno)); + freeaddrinfo (ai); + CLOSE_SOCKET(s); + return -1; + } + + freeaddrinfo (ai); + return s; +} + +ssh_bind ssh_bind_new(void) { + ssh_bind ptr; + + ptr = calloc(1, sizeof(struct ssh_bind_struct)); + if (ptr == NULL) { + return NULL; + } + ptr->bindfd = SSH_INVALID_SOCKET; + ptr->bindport = 22; + ptr->common.log_verbosity = 0; + + return ptr; +} + +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, + NULL, + NULL, + NULL, + &sshbind->ecdsa); + if (rc == SSH_ERROR || rc == SSH_EOF) { + ssh_set_error(sshbind, SSH_FATAL, + "Failed to import private ECDSA host key"); + return SSH_ERROR; + } + + if (!is_ecdsa_key_type(ssh_key_type(sshbind->ecdsa))) { + ssh_set_error(sshbind, SSH_FATAL, + "The ECDSA host key has the wrong type"); + ssh_key_free(sshbind->ecdsa); + sshbind->ecdsa = NULL; + return SSH_ERROR; + } + } +#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, + NULL, + NULL, + &sshbind->rsa); + if (rc == SSH_ERROR || rc == SSH_EOF) { + ssh_set_error(sshbind, SSH_FATAL, + "Failed to import private RSA host key"); + return SSH_ERROR; + } + + if (ssh_key_type(sshbind->rsa) != SSH_KEYTYPE_RSA) { + ssh_set_error(sshbind, SSH_FATAL, + "The RSA host key has the wrong type"); + ssh_key_free(sshbind->rsa); + sshbind->rsa = NULL; + return SSH_ERROR; + } + } + + if (sshbind->ed25519 == NULL && sshbind->ed25519key != NULL) { + rc = ssh_pki_import_privkey_file(sshbind->ed25519key, + NULL, + NULL, + NULL, + &sshbind->ed25519); + if (rc == SSH_ERROR || rc == SSH_EOF) { + ssh_set_error(sshbind, SSH_FATAL, + "Failed to import private ED25519 host key"); + return SSH_ERROR; + } + + if (ssh_key_type(sshbind->ed25519) != SSH_KEYTYPE_ED25519) { + ssh_set_error(sshbind, SSH_FATAL, + "The ED25519 host key has the wrong type"); + ssh_key_free(sshbind->ed25519); + sshbind->ed25519 = NULL; + return SSH_ERROR; + } + } + + return SSH_OK; +} + +int ssh_bind_listen(ssh_bind sshbind) { + 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; + } + } + + 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) { + 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; + } + + 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; + } + + sshbind->bindfd = fd; + } else { + SSH_LOG(SSH_LOG_INFO, "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; +} + +/** @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); + } + } + return 0; +} + +/** @internal + * @brief returns the current poll handle, or create it + * @param sshbind the ssh_bind object + * @returns a ssh_poll handle suitable for operation + */ +ssh_poll_handle ssh_bind_get_poll(ssh_bind sshbind) +{ + short events = POLLIN; + + if (sshbind->poll) { + return sshbind->poll; + } + +#ifdef POLLRDHUP + events |= POLLRDHUP; +#endif /* POLLRDHUP */ + + sshbind->poll = ssh_poll_new(sshbind->bindfd, + events, + ssh_bind_poll_callback, + sshbind); + + return sshbind->poll; +} + +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; +} + +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_free(ssh_bind sshbind){ + int i; + + if (sshbind == NULL) { + return; + } + + if (sshbind->bindfd >= 0) { + CLOSE_SOCKET(sshbind->bindfd); + } + sshbind->bindfd = SSH_INVALID_SOCKET; + + /* options */ + SAFE_FREE(sshbind->banner); + 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); + sshbind->ecdsa = NULL; + ssh_key_free(sshbind->ed25519); + sshbind->ed25519 = NULL; + + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (sshbind->wanted_methods[i]) { + SAFE_FREE(sshbind->wanted_methods[i]); + } + } + + SAFE_FREE(sshbind); +} + +int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd){ + int i, rc; + + if (sshbind == NULL) { + return SSH_ERROR; + } + + if (session == NULL){ + ssh_set_error(sshbind, SSH_FATAL,"session is null"); + 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 */ + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (sshbind->wanted_methods[i]) { + session->opts.wanted_methods[i] = strdup(sshbind->wanted_methods[i]); + if (session->opts.wanted_methods[i] == NULL) { + return SSH_ERROR; + } + } + } + + if (sshbind->bindaddr == NULL) + session->opts.bindaddr = NULL; + else { + SAFE_FREE(session->opts.bindaddr); + session->opts.bindaddr = strdup(sshbind->bindaddr); + if (session->opts.bindaddr == NULL) { + return SSH_ERROR; + } + } + + if (sshbind->pubkey_accepted_key_types != NULL) { + if (session->opts.pubkey_accepted_types == NULL) { + session->opts.pubkey_accepted_types = strdup(sshbind->pubkey_accepted_key_types); + if (session->opts.pubkey_accepted_types == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } else { + char *p; + /* If something was set to the session prior to calling this + * function, keep only what is allowed by the options set in + * sshbind */ + p = ssh_find_all_matching(sshbind->pubkey_accepted_key_types, + session->opts.pubkey_accepted_types); + if (p == NULL) { + return SSH_ERROR; + } + + SAFE_FREE(session->opts.pubkey_accepted_types); + session->opts.pubkey_accepted_types = p; + } + } + + session->common.log_verbosity = sshbind->common.log_verbosity; + if(sshbind->banner != NULL) + session->opts.custombanner = strdup(sshbind->banner); + ssh_socket_free(session->socket); + session->socket = ssh_socket_new(session); + if (session->socket == NULL) { + /* perhaps it may be better to copy the error from session to sshbind */ + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + ssh_socket_set_fd(session->socket, fd); + ssh_socket_get_poll_handle(session->socket); + + /* 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 + * where keys can be imported) on this ssh_bind and are instead + * 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); + if (rc != SSH_OK) { + return SSH_ERROR; + } + } + +#ifdef HAVE_ECC + if (sshbind->ecdsa) { + session->srv.ecdsa_key = ssh_key_dup(sshbind->ecdsa); + if (session->srv.ecdsa_key == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } +#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) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + if (sshbind->ed25519 != NULL) { + session->srv.ed25519_key = ssh_key_dup(sshbind->ed25519); + if (session->srv.ed25519_key == NULL){ + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + + /* force PRNG to change state in case we fork after ssh_bind_accept */ + ssh_reseed(); + return SSH_OK; +} + +int ssh_bind_accept(ssh_bind sshbind, ssh_session session) { + socket_t fd = SSH_INVALID_SOCKET; + int rc; + if (sshbind->bindfd == SSH_INVALID_SOCKET) { + ssh_set_error(sshbind, SSH_FATAL, + "Can't accept new clients on a not bound socket."); + return SSH_ERROR; + } + + if (session == NULL){ + ssh_set_error(sshbind, SSH_FATAL,"session is null"); + return SSH_ERROR; + } + + fd = accept(sshbind->bindfd, NULL, NULL); + if (fd == SSH_INVALID_SOCKET) { + ssh_set_error(sshbind, SSH_FATAL, + "Accepting a new connection: %s", + strerror(errno)); + return SSH_ERROR; + } + rc = ssh_bind_accept_fd(sshbind, session, fd); + + if(rc == SSH_ERROR){ + CLOSE_SOCKET(fd); + ssh_socket_free(session->socket); + } + return rc; +} + + +/** + * @} + */ diff --git a/src/bind_config.c b/src/bind_config.c new file mode 100644 index 0000000..14b84db --- /dev/null +++ b/src/bind_config.c @@ -0,0 +1,638 @@ +/* + * bind_config.c - Parse the SSH server configuration file + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * 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 +#include +#include +#include +#ifdef HAVE_GLOB_H +# include +#endif + +#include "libssh/bind.h" +#include "libssh/bind_config.h" +#include "libssh/config_parser.h" +#include "libssh/priv.h" +#include "libssh/server.h" +#include "libssh/options.h" + +#define MAX_LINE_SIZE 1024 + +/* Flags used for the parser state */ +#define PARSING 1 +#define IN_MATCH (1<<1) + +struct ssh_bind_config_keyword_table_s { + const char *name; + enum ssh_bind_config_opcode_e opcode; + bool allowed_in_match; +}; + +static struct ssh_bind_config_keyword_table_s +ssh_bind_config_keyword_table[] = { + { + .name = "include", + .opcode = BIND_CFG_INCLUDE + }, + { + .name = "hostkey", + .opcode = BIND_CFG_HOSTKEY + }, + { + .name = "listenaddress", + .opcode = BIND_CFG_LISTENADDRESS + }, + { + .name = "port", + .opcode = BIND_CFG_PORT + }, + { + .name = "loglevel", + .opcode = BIND_CFG_LOGLEVEL, + .allowed_in_match = true, + }, + { + .name = "ciphers", + .opcode = BIND_CFG_CIPHERS + }, + { + .name = "macs", + .opcode = BIND_CFG_MACS + }, + { + .name = "kexalgorithms", + .opcode = BIND_CFG_KEXALGORITHMS + }, + { + .name = "match", + .opcode = BIND_CFG_MATCH, + .allowed_in_match = true + }, + { + .name = "pubkeyacceptedkeytypes", + .opcode = BIND_CFG_PUBKEY_ACCEPTED_KEY_TYPES, + .allowed_in_match = true + }, + { + .name = "hostkeyalgorithms", + .opcode = BIND_CFG_HOSTKEY_ALGORITHMS, + .allowed_in_match = true + }, + { + .opcode = BIND_CFG_UNKNOWN, + } +}; + +enum ssh_bind_config_match_e { + BIND_MATCH_UNKNOWN = -1, + BIND_MATCH_ALL, + BIND_MATCH_USER, + BIND_MATCH_GROUP, + BIND_MATCH_HOST, + BIND_MATCH_LOCALADDRESS, + BIND_MATCH_LOCALPORT, + BIND_MATCH_RDOMAIN, + BIND_MATCH_ADDRESS, +}; + +struct ssh_bind_config_match_keyword_table_s { + const char *name; + enum ssh_bind_config_match_e opcode; +}; + +static struct ssh_bind_config_match_keyword_table_s +ssh_bind_config_match_keyword_table[] = { + { + .name = "all", + .opcode = BIND_MATCH_ALL + }, + { + .name = "user", + .opcode = BIND_MATCH_USER + }, + { + .name = "group", + .opcode = BIND_MATCH_GROUP + }, + { + .name = "host", + .opcode = BIND_MATCH_HOST + }, + { + .name = "localaddress", + .opcode = BIND_MATCH_LOCALADDRESS + }, + { + .name = "localport", + .opcode = BIND_MATCH_LOCALPORT + }, + { + .name = "rdomain", + .opcode = BIND_MATCH_RDOMAIN + }, + { + .name = "address", + .opcode = BIND_MATCH_ADDRESS + }, + { + .opcode = BIND_MATCH_UNKNOWN + }, +}; + +static enum ssh_bind_config_opcode_e +ssh_bind_config_get_opcode(char *keyword, uint32_t *parser_flags) +{ + int i; + + for (i = 0; ssh_bind_config_keyword_table[i].name != NULL; i++) { + if (strcasecmp(keyword, ssh_bind_config_keyword_table[i].name) == 0) { + if ((*parser_flags & IN_MATCH) && + !(ssh_bind_config_keyword_table[i].allowed_in_match)) + { + return BIND_CFG_NOT_ALLOWED_IN_MATCH; + } + return ssh_bind_config_keyword_table[i].opcode; + } + } + + return BIND_CFG_UNKNOWN; +} + +static int +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) +{ + FILE *f; + char line[MAX_LINE_SIZE] = {0}; + unsigned int count = 0; + int rv; + + f = fopen(filename, "r"); + if (f == NULL) { + SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", + filename); + return; + } + + SSH_LOG(SSH_LOG_PACKET, "Reading additional configuration data from %s", + filename); + + while (fgets(line, sizeof(line), f)) { + count++; + rv = ssh_bind_config_parse_line(bind, line, count, parser_flags, seen); + if (rv < 0) { + fclose(f); + return; + } + } + + fclose(f); + return; +} + +#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) +static void local_parse_glob(ssh_bind bind, + const char *fileglob, + uint32_t *parser_flags, + uint8_t *seen) +{ + glob_t globbuf = { + .gl_flags = 0, + }; + int rt; + u_int i; + + rt = glob(fileglob, GLOB_TILDE, NULL, &globbuf); + if (rt == GLOB_NOMATCH) { + globfree(&globbuf); + return; + } else if (rt != 0) { + SSH_LOG(SSH_LOG_RARE, "Glob error: %s", + fileglob); + globfree(&globbuf); + return; + } + + for (i = 0; i < globbuf.gl_pathc; i++) { + local_parse_file(bind, globbuf.gl_pathv[i], parser_flags, seen); + } + + globfree(&globbuf); +} +#endif /* HAVE_GLOB HAVE_GLOB_GL_FLAGS_MEMBER */ + +static enum ssh_bind_config_match_e +ssh_bind_config_get_match_opcode(const char *keyword) +{ + size_t i; + + for (i = 0; ssh_bind_config_match_keyword_table[i].name != NULL; i++) { + if (strcasecmp(keyword, ssh_bind_config_match_keyword_table[i].name) == 0) { + return ssh_bind_config_match_keyword_table[i].opcode; + } + } + + return BIND_MATCH_UNKNOWN; +} + +static int +ssh_bind_config_parse_line(ssh_bind bind, + const char *line, + unsigned int count, + uint32_t *parser_flags, + uint8_t *seen) +{ + enum ssh_bind_config_opcode_e opcode; + const char *p = NULL; + char *s = NULL, *x = NULL; + char *keyword = NULL; + size_t len; + + int rc = 0; + + if (bind == NULL) { + return -1; + } + + if ((line == NULL) || (parser_flags == NULL)) { + ssh_set_error_invalid(bind); + return -1; + } + + x = s = strdup(line); + if (s == NULL) { + ssh_set_error_oom(bind); + return -1; + } + + /* Remove trailing spaces */ + for (len = strlen(s) - 1; len > 0; len--) { + if (! isspace(s[len])) { + break; + } + s[len] = '\0'; + } + + keyword = ssh_config_get_token(&s); + if (keyword == NULL || *keyword == '#' || + *keyword == '\0' || *keyword == '\n') { + SAFE_FREE(x); + return 0; + } + + opcode = ssh_bind_config_get_opcode(keyword, parser_flags); + if ((*parser_flags & PARSING) && + opcode != BIND_CFG_HOSTKEY && + opcode != BIND_CFG_INCLUDE && + opcode != BIND_CFG_MATCH && + opcode > BIND_CFG_UNSUPPORTED) { /* Ignore all unknown types here */ + /* Skip all the options that were already applied */ + if (seen[opcode] != 0) { + SAFE_FREE(x); + return 0; + } + seen[opcode] = 1; + } + + switch (opcode) { + case BIND_CFG_INCLUDE: + 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); +#else + local_parse_file(bind, p, parser_flags, seen); +#endif /* HAVE_GLOB */ + } + break; + + case BIND_CFG_HOSTKEY: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARN, + "line %d: Failed to set Hostkey value '%s'", + count, p); + } + } + break; + case BIND_CFG_LISTENADDRESS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDADDR, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARN, + "line %d: Failed to set ListenAddress value '%s'", + count, p); + } + } + break; + case BIND_CFG_PORT: + p = ssh_config_get_str_tok(&s, NULL); + 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, + "line %d: Failed to set Port value '%s'", + count, p); + } + } + break; + case BIND_CFG_CIPHERS: + p = ssh_config_get_str_tok(&s, NULL); + 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, + "line %d: Failed to set C->S Ciphers value '%s'", + count, p); + break; + } + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_S_C, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARN, + "line %d: Failed to set S->C Ciphers value '%s'", + count, p); + } + } + break; + case BIND_CFG_MACS: + p = ssh_config_get_str_tok(&s, NULL); + 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, + "line %d: Failed to set C->S MAC value '%s'", + count, p); + break; + } + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_S_C, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARN, + "line %d: Failed to set S->C MAC value '%s'", + count, p); + } + } + break; + case BIND_CFG_LOGLEVEL: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + int value = -1; + + if (strcasecmp(p, "quiet") == 0) { + value = SSH_LOG_NONE; + } else if (strcasecmp(p, "fatal") == 0 || + strcasecmp(p, "error")== 0 || + strcasecmp(p, "info") == 0) { + value = SSH_LOG_WARN; + } else if (strcasecmp(p, "verbose") == 0) { + value = SSH_LOG_INFO; + } else if (strcasecmp(p, "DEBUG") == 0 || + strcasecmp(p, "DEBUG1") == 0) { + value = SSH_LOG_DEBUG; + } else if (strcasecmp(p, "DEBUG2") == 0 || + strcasecmp(p, "DEBUG3") == 0) { + value = SSH_LOG_TRACE; + } + if (value != -1) { + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY, + &value); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARN, + "line %d: Failed to set LogLevel value '%s'", + count, p); + } + } + } + break; + case BIND_CFG_KEXALGORITHMS: + p = ssh_config_get_str_tok(&s, NULL); + 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, + "line %d: Failed to set KexAlgorithms value '%s'", + count, p); + } + } + break; + case BIND_CFG_MATCH: { + bool negate; + int result = PARSING; + size_t args = 0; + enum ssh_bind_config_match_e opt; + const char *p2 = NULL; + + /* The options set in Match blocks should be applied when a connection + * is accepted, and not right away when parsing the file (as it is + * currently done). This means the configuration files should be parsed + * again or the options set in the Match blocks should be stored and + * applied as necessary. */ + + /* If this is the first Match block, erase the seen table to allow + * options to be overridden. Erasing the seen table was the easiest way + * to allow overriding an option, but only for the first occurrence of + * an option in a Match block. This is sufficient for the current + * implementation which supports only the 'All' criterion, meaning the + * options can be applied right away. */ + if (!(*parser_flags & IN_MATCH)) { + memset(seen, 0x00, BIND_CFG_MAX * sizeof(uint8_t)); + } + + /* In this line the PARSING bit is cleared from the flags */ + *parser_flags = IN_MATCH; + do { + p = p2 = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + break; + } + args++; + SSH_LOG(SSH_LOG_TRACE, "line %d: Processing Match keyword '%s'", + count, p); + + /* If the option is prefixed with ! the result should be negated */ + negate = false; + if (p[0] == '!') { + negate = true; + p++; + } + + opt = ssh_bind_config_get_match_opcode(p); + switch (opt) { + case BIND_MATCH_ALL: + p = ssh_config_get_str_tok(&s, NULL); + if ((args == 1) && (p == NULL || p[0] == '\0')) { + /* The "all" keyword does not accept arguments or modifiers + */ + if (negate == true) { + result = 0; + } + break; + } + ssh_set_error(bind, SSH_FATAL, + "line %d: ERROR - Match all cannot be combined with " + "other Match attributes", count); + SAFE_FREE(x); + return -1; + case BIND_MATCH_USER: + case BIND_MATCH_GROUP: + case BIND_MATCH_HOST: + case BIND_MATCH_LOCALADDRESS: + case BIND_MATCH_LOCALPORT: + case BIND_MATCH_RDOMAIN: + case BIND_MATCH_ADDRESS: + /* Only "All" is supported for now */ + /* 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 " + "'%s' requires argument\n", count, p2); + SAFE_FREE(x); + return -1; + } + args++; + SSH_LOG(SSH_LOG_WARN, + "line %d: Unsupported Match keyword '%s', ignoring\n", + count, + p2); + result = 0; + break; + case BIND_MATCH_UNKNOWN: + default: + ssh_set_error(bind, SSH_FATAL, + "ERROR - Unknown argument '%s' for Match keyword", p); + SAFE_FREE(x); + return -1; + } + } while (p != NULL && p[0] != '\0'); + if (args == 0) { + ssh_set_error(bind, SSH_FATAL, + "ERROR - Match keyword requires an argument"); + SAFE_FREE(x); + return -1; + } + /* This line only sets the PARSING flag if all checks passed */ + *parser_flags |= result; + break; + } + case BIND_CFG_PUBKEY_ACCEPTED_KEY_TYPES: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARN, + "line %d: Failed to set PubKeyAcceptedKeyTypes value '%s'", + count, p); + } + } + break; + case BIND_CFG_HOSTKEY_ALGORITHMS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARN, + "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", + keyword, count); + break; + case BIND_CFG_UNKNOWN: + SSH_LOG(SSH_LOG_WARN, "Unknown option: %s, line: %d", + keyword, count); + break; + case BIND_CFG_UNSUPPORTED: + SSH_LOG(SSH_LOG_WARN, "Unsupported option: %s, line: %d", + keyword, count); + break; + case BIND_CFG_NA: + SSH_LOG(SSH_LOG_WARN, "Option not applicable: %s, line: %d", + keyword, count); + break; + default: + ssh_set_error(bind, SSH_FATAL, "ERROR - unimplemented opcode: %d", + opcode); + SAFE_FREE(x); + return -1; + break; + } + + SAFE_FREE(x); + return rc; +} + +int ssh_bind_config_parse_file(ssh_bind bind, const char *filename) +{ + char line[MAX_LINE_SIZE] = {0}; + unsigned int count = 0; + FILE *f; + 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}; + + f = fopen(filename, "r"); + if (f == NULL) { + return 0; + } + + SSH_LOG(SSH_LOG_PACKET, "Reading configuration data from %s", filename); + + parser_flags = PARSING; + while (fgets(line, sizeof(line), f)) { + count++; + rv = ssh_bind_config_parse_line(bind, line, count, &parser_flags, seen); + if (rv) { + fclose(f); + return -1; + } + } + + fclose(f); + return 0; +} diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 0000000..a2e6246 --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,1359 @@ +/* + * buffer.c - buffer functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-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 +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/misc.h" +#include "libssh/bignum.h" + +/* + * Describes a buffer state + * [XXXXXXXXXXXXDATA PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXX] + * ^ ^ ^ ^] + * \_data points\_pos points here \_used points here | / + * here Allocated + */ +struct ssh_buffer_struct { + bool secure; + size_t used; + size_t allocated; + size_t pos; + uint8_t *data; +}; + +/* Buffer size maximum is 256M */ +#define BUFFER_SIZE_MAX 0x10000000 + +/** + * @defgroup libssh_buffer The SSH buffer functions. + * @ingroup libssh + * + * Functions to handle SSH buffers. + * + * @{ + */ + + +#ifdef DEBUG_BUFFER +/** + * @internal + * + * @brief Check that preconditions and postconditions are valid. + * + * @param[in] buf The buffer to check. + */ +static void buffer_verify(ssh_buffer buf) +{ + bool do_abort = false; + + if (buf->data == NULL) { + return; + } + + if (buf->used > buf->allocated) { + fprintf(stderr, + "BUFFER ERROR: allocated %zu, used %zu\n", + buf->allocated, + buf->used); + do_abort = true; + } + if (buf->pos > buf->used) { + fprintf(stderr, + "BUFFER ERROR: position %zu, used %zu\n", + buf->pos, + buf->used); + do_abort = true; + } + if (buf->pos > buf->allocated) { + fprintf(stderr, + "BUFFER ERROR: position %zu, allocated %zu\n", + buf->pos, + buf->allocated); + do_abort = true; + } + if (do_abort) { + abort(); + } +} + +#else +#define buffer_verify(x) +#endif + +/** + * @brief Create a new SSH buffer. + * + * @return A newly initialized SSH buffer, NULL on error. + */ +struct ssh_buffer_struct *ssh_buffer_new(void) +{ + struct ssh_buffer_struct *buf = NULL; + int rc; + + buf = calloc(1, sizeof(struct ssh_buffer_struct)); + if (buf == NULL) { + return NULL; + } + + /* + * Always preallocate 64 bytes. + * + * -1 for ralloc_buffer magic. + */ + rc = ssh_buffer_allocate_size(buf, 64 - 1); + if (rc != 0) { + SAFE_FREE(buf); + return NULL; + } + buffer_verify(buf); + + return buf; +} + +/** + * @brief Deallocate a SSH buffer. + * + * \param[in] buffer The buffer to free. + */ +void ssh_buffer_free(struct ssh_buffer_struct *buffer) +{ + if (buffer == NULL) { + return; + } + buffer_verify(buffer); + + if (buffer->secure && buffer->allocated > 0) { + /* burn the data */ + explicit_bzero(buffer->data, buffer->allocated); + SAFE_FREE(buffer->data); + + explicit_bzero(buffer, sizeof(struct ssh_buffer_struct)); + } else { + SAFE_FREE(buffer->data); + } + SAFE_FREE(buffer); +} + +/** + * @brief Sets the buffer as secure. + * + * A secure buffer will never leave cleartext data in the heap + * after being reallocated or freed. + * + * @param[in] buffer buffer to set secure. + */ +void ssh_buffer_set_secure(ssh_buffer buffer) +{ + buffer->secure = true; +} + +static int realloc_buffer(struct ssh_buffer_struct *buffer, size_t needed) +{ + size_t smallest = 1; + uint8_t *new = NULL; + + buffer_verify(buffer); + + /* Find the smallest power of two which is greater or equal to needed */ + while(smallest <= needed) { + if (smallest == 0) { + return -1; + } + smallest <<= 1; + } + needed = smallest; + + if (needed > BUFFER_SIZE_MAX) { + return -1; + } + + if (buffer->secure) { + new = malloc(needed); + if (new == NULL) { + return -1; + } + memcpy(new, buffer->data, buffer->used); + explicit_bzero(buffer->data, buffer->used); + SAFE_FREE(buffer->data); + } else { + new = realloc(buffer->data, needed); + if (new == NULL) { + return -1; + } + } + buffer->data = new; + buffer->allocated = needed; + + buffer_verify(buffer); + return 0; +} + +/** @internal + * @brief shifts a buffer to remove unused data in the beginning + * @param buffer SSH buffer + */ +static void buffer_shift(ssh_buffer buffer) +{ + size_t burn_pos = buffer->pos; + + buffer_verify(buffer); + + if (buffer->pos == 0) { + return; + } + memmove(buffer->data, + buffer->data + buffer->pos, + buffer->used - buffer->pos); + buffer->used -= buffer->pos; + buffer->pos = 0; + + if (buffer->secure) { + void *ptr = buffer->data + buffer->used; + explicit_bzero(ptr, burn_pos); + } + + buffer_verify(buffer); +} + +/** + * @brief Reinitialize a SSH buffer. + * + * In case the buffer has exceeded 64K in size, the buffer will be reallocated + * to 64K. + * + * @param[in] buffer The buffer to reinitialize. + * + * @return 0 on success, < 0 on error. + */ +int ssh_buffer_reinit(struct ssh_buffer_struct *buffer) +{ + if (buffer == NULL) { + return -1; + } + + buffer_verify(buffer); + + if (buffer->secure && buffer->allocated > 0) { + explicit_bzero(buffer->data, buffer->allocated); + } + buffer->used = 0; + buffer->pos = 0; + + /* If the buffer is bigger then 64K, reset it to 64K */ + if (buffer->allocated > 65536) { + int rc; + + /* -1 for realloc_buffer magic */ + rc = realloc_buffer(buffer, 65536 - 1); + if (rc != 0) { + return -1; + } + } + + buffer_verify(buffer); + + return 0; +} + +/** + * @brief Add data at the tail of a buffer. + * + * @param[in] buffer The buffer to add the data. + * + * @param[in] data A pointer to the data to add. + * + * @param[in] len The length of the data to add. + * + * @return 0 on success, < 0 on error. + */ +int ssh_buffer_add_data(struct ssh_buffer_struct *buffer, const void *data, uint32_t len) +{ + buffer_verify(buffer); + + if (data == NULL) { + return -1; + } + + if (buffer->used + len < len) { + return -1; + } + + if (buffer->allocated < (buffer->used + len)) { + if(buffer->pos > 0) + buffer_shift(buffer); + if (realloc_buffer(buffer, buffer->used + len) < 0) { + return -1; + } + } + + memcpy(buffer->data+buffer->used, data, len); + buffer->used+=len; + buffer_verify(buffer); + return 0; +} + +/** + * @brief Ensure the buffer has at least a certain preallocated size. + * + * @param[in] buffer The buffer to enlarge. + * + * @param[in] len The length to ensure as allocated. + * + * @return 0 on success, < 0 on error. + */ +int ssh_buffer_allocate_size(struct ssh_buffer_struct *buffer, + uint32_t len) +{ + buffer_verify(buffer); + + if (buffer->allocated < len) { + if (buffer->pos > 0) { + buffer_shift(buffer); + } + if (realloc_buffer(buffer, len) < 0) { + return -1; + } + } + + buffer_verify(buffer); + + return 0; +} + +/** + * @internal + * + * @brief Allocate space for data at the tail of a buffer. + * + * @param[in] buffer The buffer to add the data. + * + * @param[in] len The length of the data to add. + * + * @return Pointer on the allocated space + * NULL on error. + */ +void *ssh_buffer_allocate(struct ssh_buffer_struct *buffer, uint32_t len) +{ + void *ptr; + buffer_verify(buffer); + + if (buffer->used + len < len) { + return NULL; + } + + if (buffer->allocated < (buffer->used + len)) { + if (buffer->pos > 0) { + buffer_shift(buffer); + } + + if (realloc_buffer(buffer, buffer->used + len) < 0) { + return NULL; + } + } + + ptr = buffer->data + buffer->used; + buffer->used+=len; + buffer_verify(buffer); + + return ptr; +} + +/** + * @internal + * + * @brief Add a SSH string to the tail of a buffer. + * + * @param[in] buffer The buffer to add the string. + * + * @param[in] string The SSH String to add. + * + * @return 0 on success, < 0 on error. + */ +int ssh_buffer_add_ssh_string(struct ssh_buffer_struct *buffer, + struct ssh_string_struct *string) { + uint32_t len = 0; + + if (string == NULL) { + return -1; + } + + len = ssh_string_len(string); + if (ssh_buffer_add_data(buffer, string, len + sizeof(uint32_t)) < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add a 32 bits unsigned integer to the tail of a buffer. + * + * @param[in] buffer The buffer to add the integer. + * + * @param[in] data The 32 bits integer to add. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_add_u32(struct ssh_buffer_struct *buffer,uint32_t data) +{ + int rc; + + rc = ssh_buffer_add_data(buffer, &data, sizeof(data)); + if (rc < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add a 16 bits unsigned integer to the tail of a buffer. + * + * @param[in] buffer The buffer to add the integer. + * + * @param[in] data The 16 bits integer to add. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_add_u16(struct ssh_buffer_struct *buffer,uint16_t data) +{ + int rc; + + rc = ssh_buffer_add_data(buffer, &data, sizeof(data)); + if (rc < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add a 64 bits unsigned integer to the tail of a buffer. + * + * @param[in] buffer The buffer to add the integer. + * + * @param[in] data The 64 bits integer to add. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_add_u64(struct ssh_buffer_struct *buffer, uint64_t data) +{ + int rc; + + rc = ssh_buffer_add_data(buffer, &data, sizeof(data)); + if (rc < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add a 8 bits unsigned integer to the tail of a buffer. + * + * @param[in] buffer The buffer to add the integer. + * + * @param[in] data The 8 bits integer to add. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_add_u8(struct ssh_buffer_struct *buffer,uint8_t data) +{ + int rc; + + rc = ssh_buffer_add_data(buffer, &data, sizeof(uint8_t)); + if (rc < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add data at the head of a buffer. + * + * @param[in] buffer The buffer to add the data. + * + * @param[in] data The data to prepend. + * + * @param[in] len The length of data to prepend. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_prepend_data(struct ssh_buffer_struct *buffer, const void *data, + uint32_t len) { + buffer_verify(buffer); + + if(len <= buffer->pos){ + /* It's possible to insert data between begin and pos */ + memcpy(buffer->data + (buffer->pos - len), data, len); + buffer->pos -= len; + buffer_verify(buffer); + return 0; + } + /* pos isn't high enough */ + if (buffer->used - buffer->pos + len < len) { + return -1; + } + + if (buffer->allocated < (buffer->used - buffer->pos + len)) { + if (realloc_buffer(buffer, buffer->used - buffer->pos + len) < 0) { + return -1; + } + } + memmove(buffer->data + len, buffer->data + buffer->pos, buffer->used - buffer->pos); + memcpy(buffer->data, data, len); + buffer->used += len - buffer->pos; + buffer->pos = 0; + buffer_verify(buffer); + return 0; +} + +/** + * @internal + * + * @brief Append data from a buffer to the tail of another buffer. + * + * @param[in] buffer The destination buffer. + * + * @param[in] source The source buffer to append. It doesn't take the + * position of the buffer into account. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_add_buffer(struct ssh_buffer_struct *buffer, + struct ssh_buffer_struct *source) +{ + int rc; + + rc = ssh_buffer_add_data(buffer, + ssh_buffer_get(source), + ssh_buffer_get_len(source)); + if (rc < 0) { + return -1; + } + + return 0; +} + +/** + * @brief Get a pointer to the head of a buffer at the current position. + * + * @param[in] buffer The buffer to get the head pointer. + * + * @return A pointer to the data from current position. + * + * @see ssh_buffer_get_len() + */ +void *ssh_buffer_get(struct ssh_buffer_struct *buffer){ + return buffer->data + buffer->pos; +} + +/** + * @brief Get the length of the buffer from the current position. + * + * @param[in] buffer The buffer to get the length from. + * + * @return The length of the buffer. + * + * @see ssh_buffer_get() + */ +uint32_t ssh_buffer_get_len(struct ssh_buffer_struct *buffer){ + buffer_verify(buffer); + return buffer->used - buffer->pos; +} + +/** + * @internal + * + * @brief Advance the position in the buffer. + * + * This has effect to "eat" bytes at head of the buffer. + * + * @param[in] buffer The buffer to advance the position. + * + * @param[in] len The number of bytes to eat. + * + * @return The new size of the buffer. + */ +uint32_t ssh_buffer_pass_bytes(struct ssh_buffer_struct *buffer, uint32_t len){ + buffer_verify(buffer); + + if (buffer->pos + len < len || buffer->used < buffer->pos + len) { + return 0; + } + + buffer->pos+=len; + /* if the buffer is empty after having passed the whole bytes into it, we can clean it */ + if(buffer->pos==buffer->used){ + buffer->pos=0; + buffer->used=0; + } + buffer_verify(buffer); + return len; +} + +/** + * @internal + * + * @brief Cut the end of the buffer. + * + * @param[in] buffer The buffer to cut. + * + * @param[in] len The number of bytes to remove from the tail. + * + * @return The new size of the buffer. + */ +uint32_t ssh_buffer_pass_bytes_end(struct ssh_buffer_struct *buffer, uint32_t len){ + buffer_verify(buffer); + + if (buffer->used < len) { + return 0; + } + + buffer->used-=len; + buffer_verify(buffer); + return len; +} + +/** + * @brief Get the remaining data out of the buffer and adjust the read pointer. + * + * @param[in] buffer The buffer to read. + * + * @param[in] data The data buffer where to store the data. + * + * @param[in] len The length to read from the buffer. + * + * @returns 0 if there is not enough data in buffer, len otherwise. + */ +uint32_t ssh_buffer_get_data(struct ssh_buffer_struct *buffer, void *data, uint32_t len) +{ + int rc; + + /* + * Check for a integer overflow first, then check if not enough data is in + * the buffer. + */ + rc = ssh_buffer_validate_length(buffer, len); + if (rc != SSH_OK) { + return 0; + } + memcpy(data,buffer->data+buffer->pos,len); + buffer->pos+=len; + return len; /* no yet support for partial reads (is it really needed ?? ) */ +} + +/** + * @internal + * + * @brief Get a 8 bits unsigned int out of the buffer and adjusts the read + * pointer. + * + * @param[in] buffer The buffer to read. + * + * @param[in] data A pointer to a uint8_t where to store the data. + * + * @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){ + return ssh_buffer_get_data(buffer,data,sizeof(uint8_t)); +} + +/** + * @internal + * + * @brief gets a 32 bits unsigned int out of the buffer. Adjusts the read pointer. + * + * @param[in] buffer The buffer to read. + * + * @param[in] data A pointer to a uint32_t where to store the 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){ + return ssh_buffer_get_data(buffer,data,sizeof(uint32_t)); +} +/** + * @internal + * + * @brief Get a 64 bits unsigned int out of the buffer and adjusts the read + * pointer. + * + * @param[in] buffer The buffer to read. + * + * @param[in] data A pointer to a uint64_t where to store the 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){ + return ssh_buffer_get_data(buffer,data,sizeof(uint64_t)); +} + +/** + * @brief Valdiates that the given length can be obtained from the buffer. + * + * @param[in] buffer The buffer to read from. + * + * @param[in] len The length to be checked. + * + * @return SSH_OK if the length is valid, SSH_ERROR otherwise. + */ +int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len) +{ + if (buffer->pos + len < len || buffer->pos + len > buffer->used) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @internal + * + * @brief Get a SSH String out of the buffer and adjusts the read pointer. + * + * @param[in] buffer The buffer to read. + * + * @returns The SSH String, NULL on error. + */ +struct ssh_string_struct * +ssh_buffer_get_ssh_string(struct ssh_buffer_struct *buffer) +{ + uint32_t stringlen; + uint32_t hostlen; + struct ssh_string_struct *str = NULL; + int rc; + + rc = ssh_buffer_get_u32(buffer, &stringlen); + if (rc == 0) { + return NULL; + } + hostlen = ntohl(stringlen); + /* verify if there is enough space in buffer to get it */ + rc = ssh_buffer_validate_length(buffer, hostlen); + if (rc != SSH_OK) { + return NULL; /* it is indeed */ + } + str = ssh_string_new(hostlen); + if (str == NULL) { + return NULL; + } + + stringlen = ssh_buffer_get_data(buffer, ssh_string_data(str), hostlen); + if (stringlen != hostlen) { + /* should never happen */ + SAFE_FREE(str); + return NULL; + } + + return str; +} + +/** + * @brief Pre-calculate the size we need for packing the buffer. + * + * This makes sure that enough memory is allocated for packing the buffer and + * we only have to do one memory allocation. + * + * @param[in] buffer The buffer to allocate + * + * @param[in] format A format string of arguments. + * + * @param[in] argc The number of arguments. + * + * @param[in] ap The va_list of arguments. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + va_list ap) +{ + const char *p = NULL; + ssh_string string = NULL; + char *cstring = NULL; + size_t needed_size = 0; + size_t len; + size_t count; + int rc = SSH_OK; + + for (p = format, count = 0; *p != '\0'; p++, count++) { + /* Invalid number of arguments passed */ + if (count > argc) { + return SSH_ERROR; + } + + switch(*p) { + case 'b': + va_arg(ap, unsigned int); + needed_size += sizeof(uint8_t); + break; + case 'w': + va_arg(ap, unsigned int); + needed_size += sizeof(uint16_t); + break; + case 'd': + va_arg(ap, uint32_t); + needed_size += sizeof(uint32_t); + break; + case 'q': + va_arg(ap, uint64_t); + needed_size += sizeof(uint64_t); + break; + case 'S': + string = va_arg(ap, ssh_string); + needed_size += 4 + ssh_string_len(string); + string = NULL; + break; + case 's': + cstring = va_arg(ap, char *); + needed_size += sizeof(uint32_t) + strlen(cstring); + cstring = NULL; + break; + case 'P': + len = va_arg(ap, size_t); + needed_size += len; + va_arg(ap, void *); + count++; /* increase argument count */ + break; + case 'B': + va_arg(ap, bignum); + /* + * Use a fixed size for a bignum + * (they should normaly be around 32) + */ + needed_size += 64; + break; + case 't': + cstring = va_arg(ap, char *); + needed_size += strlen(cstring); + cstring = NULL; + break; + default: + SSH_LOG(SSH_LOG_WARN, "Invalid buffer format %c", *p); + rc = SSH_ERROR; + } + if (rc != SSH_OK){ + break; + } + } + + if (argc != count) { + return SSH_ERROR; + } + + if (rc != SSH_ERROR){ + /* + * Check if our canary is intact, if not, something really bad happened. + */ + uint32_t canary = va_arg(ap, uint32_t); + if (canary != SSH_BUFFER_PACK_END) { + abort(); + } + } + + rc = ssh_buffer_allocate_size(buffer, needed_size); + if (rc != 0) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/** @internal + * @brief Add multiple values in a buffer on a single function call + * @param[in] buffer The buffer to add to + * @param[in] format A format string of arguments. + * @param[in] ap A va_list of arguments. + * @returns SSH_OK on success + * SSH_ERROR on error + * @see ssh_buffer_add_format() for format list values. + */ +int ssh_buffer_pack_va(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + va_list ap) +{ + int rc = SSH_ERROR; + const char *p; + union { + uint8_t byte; + uint16_t word; + uint32_t dword; + uint64_t qword; + ssh_string string; + void *data; + } o; + char *cstring; + bignum b; + size_t len; + size_t count; + + if (argc > 256) { + return SSH_ERROR; + } + + for (p = format, count = 0; *p != '\0'; p++, count++) { + /* Invalid number of arguments passed */ + if (count > argc) { + return SSH_ERROR; + } + + switch(*p) { + case 'b': + o.byte = (uint8_t)va_arg(ap, unsigned int); + rc = ssh_buffer_add_u8(buffer, o.byte); + break; + case 'w': + o.word = (uint16_t)va_arg(ap, unsigned int); + o.word = htons(o.word); + rc = ssh_buffer_add_u16(buffer, o.word); + break; + case 'd': + o.dword = va_arg(ap, uint32_t); + o.dword = htonl(o.dword); + rc = ssh_buffer_add_u32(buffer, o.dword); + break; + case 'q': + o.qword = va_arg(ap, uint64_t); + o.qword = htonll(o.qword); + rc = ssh_buffer_add_u64(buffer, o.qword); + break; + case 'S': + o.string = va_arg(ap, ssh_string); + rc = ssh_buffer_add_ssh_string(buffer, o.string); + o.string = NULL; + break; + case 's': + cstring = va_arg(ap, char *); + len = strlen(cstring); + rc = ssh_buffer_add_u32(buffer, htonl(len)); + if (rc == SSH_OK){ + rc = ssh_buffer_add_data(buffer, cstring, len); + } + cstring = NULL; + break; + case 'P': + len = va_arg(ap, size_t); + + o.data = va_arg(ap, void *); + count++; /* increase argument count */ + + rc = ssh_buffer_add_data(buffer, o.data, len); + o.data = NULL; + break; + case 'B': + b = va_arg(ap, bignum); + o.string = ssh_make_bignum_string(b); + if(o.string == NULL){ + rc = SSH_ERROR; + break; + } + rc = ssh_buffer_add_ssh_string(buffer, o.string); + SAFE_FREE(o.string); + break; + case 't': + cstring = va_arg(ap, char *); + len = strlen(cstring); + rc = ssh_buffer_add_data(buffer, cstring, len); + cstring = NULL; + break; + default: + SSH_LOG(SSH_LOG_WARN, "Invalid buffer format %c", *p); + rc = SSH_ERROR; + } + if (rc != SSH_OK){ + break; + } + } + + if (argc != count) { + return SSH_ERROR; + } + + if (rc != SSH_ERROR){ + /* Check if our canary is intact, if not something really bad happened */ + uint32_t canary = va_arg(ap, uint32_t); + if (canary != SSH_BUFFER_PACK_END) { + abort(); + } + } + return rc; +} + +/** @internal + * @brief Add multiple values in a buffer on a single function call + * @param[in] buffer The buffer to add to + * @param[in] format A format string of arguments. This string contains single + * letters describing the order and type of arguments: + * 'b': uint8_t (pushed in network byte order) + * 'w': uint16_t (pushed in network byte order) + * 'd': uint32_t (pushed in network byte order) + * 'q': uint64_t (pushed in network byte order) + * 'S': ssh_string + * 's': char * (C string, pushed as SSH string) + * 't': char * (C string, pushed as free text) + * 'P': size_t, void * (len of data, pointer to data) + * only pushes data. + * 'B': bignum (pushed as SSH string) + * @returns SSH_OK on success + * SSH_ERROR on error + * @warning when using 'P' with a constant size (e.g. 8), do not + * forget to cast to (size_t). + */ +int _ssh_buffer_pack(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + ...) +{ + va_list ap; + int rc; + + if (argc > 256) { + return SSH_ERROR; + } + + va_start(ap, argc); + rc = ssh_buffer_pack_allocate_va(buffer, format, argc, ap); + va_end(ap); + + if (rc != SSH_OK) { + return rc; + } + + va_start(ap, argc); + rc = ssh_buffer_pack_va(buffer, format, argc, ap); + va_end(ap); + + return rc; +} + +/** @internal + * @brief Get multiple values from a buffer on a single function call + * @param[in] buffer The buffer to get from + * @param[in] format A format string of arguments. + * @param[in] ap A va_list of arguments. + * @returns SSH_OK on success + * SSH_ERROR on error + * @see ssh_buffer_get_format() for format list values. + */ +int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + va_list ap) +{ + int rc = SSH_ERROR; + const char *p = format, *last; + union { + uint8_t *byte; + uint16_t *word; + uint32_t *dword; + uint64_t *qword; + ssh_string *string; + char **cstring; + bignum *bignum; + void **data; + } o; + size_t len, rlen, max_len; + ssh_string tmp_string = NULL; + va_list ap_copy; + size_t count; + + max_len = ssh_buffer_get_len(buffer); + + /* copy the argument list in case a rollback is needed */ + va_copy(ap_copy, ap); + + if (argc > 256) { + rc = SSH_ERROR; + goto cleanup; + } + + for (count = 0; *p != '\0'; p++, count++) { + /* Invalid number of arguments passed */ + if (count > argc) { + rc = SSH_ERROR; + goto cleanup; + } + + rc = SSH_ERROR; + switch (*p) { + case 'b': + o.byte = va_arg(ap, uint8_t *); + rlen = ssh_buffer_get_u8(buffer, o.byte); + rc = rlen==1 ? SSH_OK : SSH_ERROR; + break; + case 'w': + o.word = va_arg(ap, uint16_t *); + rlen = ssh_buffer_get_data(buffer, o.word, sizeof(uint16_t)); + if (rlen == 2) { + *o.word = ntohs(*o.word); + rc = SSH_OK; + } + break; + case 'd': + o.dword = va_arg(ap, uint32_t *); + rlen = ssh_buffer_get_u32(buffer, o.dword); + if (rlen == 4) { + *o.dword = ntohl(*o.dword); + rc = SSH_OK; + } + break; + case 'q': + o.qword = va_arg(ap, uint64_t*); + rlen = ssh_buffer_get_u64(buffer, o.qword); + if (rlen == 8) { + *o.qword = ntohll(*o.qword); + rc = SSH_OK; + } + break; + case 'B': + o.bignum = va_arg(ap, bignum *); + *o.bignum = NULL; + tmp_string = ssh_buffer_get_ssh_string(buffer); + if (tmp_string == NULL) { + break; + } + *o.bignum = ssh_make_string_bn(tmp_string); + ssh_string_burn(tmp_string); + SSH_STRING_FREE(tmp_string); + rc = (*o.bignum != NULL) ? SSH_OK : SSH_ERROR; + break; + case 'S': + o.string = va_arg(ap, ssh_string *); + *o.string = ssh_buffer_get_ssh_string(buffer); + rc = *o.string != NULL ? SSH_OK : SSH_ERROR; + o.string = NULL; + break; + case 's': { + uint32_t u32len = 0; + + o.cstring = va_arg(ap, char **); + *o.cstring = NULL; + rlen = ssh_buffer_get_u32(buffer, &u32len); + if (rlen != 4){ + break; + } + len = ntohl(u32len); + if (len > max_len - 1) { + break; + } + + rc = ssh_buffer_validate_length(buffer, len); + if (rc != SSH_OK) { + break; + } + + *o.cstring = malloc(len + 1); + if (*o.cstring == NULL){ + rc = SSH_ERROR; + break; + } + rlen = ssh_buffer_get_data(buffer, *o.cstring, len); + if (rlen != len){ + SAFE_FREE(*o.cstring); + rc = SSH_ERROR; + break; + } + (*o.cstring)[len] = '\0'; + o.cstring = NULL; + rc = SSH_OK; + break; + } + case 'P': + len = va_arg(ap, size_t); + if (len > max_len - 1) { + rc = SSH_ERROR; + break; + } + + rc = ssh_buffer_validate_length(buffer, len); + if (rc != SSH_OK) { + break; + } + + o.data = va_arg(ap, void **); + count++; + + *o.data = malloc(len); + if(*o.data == NULL){ + rc = SSH_ERROR; + break; + } + rlen = ssh_buffer_get_data(buffer, *o.data, len); + if (rlen != len){ + SAFE_FREE(*o.data); + rc = SSH_ERROR; + break; + } + o.data = NULL; + rc = SSH_OK; + break; + default: + SSH_LOG(SSH_LOG_WARN, "Invalid buffer format %c", *p); + } + if (rc != SSH_OK) { + break; + } + } + + if (argc != count) { + rc = SSH_ERROR; + } + +cleanup: + if (rc != SSH_ERROR){ + /* Check if our canary is intact, if not something really bad happened */ + uint32_t canary = va_arg(ap, uint32_t); + if (canary != SSH_BUFFER_PACK_END){ + abort(); + } + } + + if (rc != SSH_OK){ + /* Reset the format string and erase everything that was allocated */ + last = p; + for(p=format;psecure) { + explicit_bzero(o.byte, sizeof(uint8_t)); + break; + } + break; + case 'w': + o.word = va_arg(ap_copy, uint16_t *); + if (buffer->secure) { + explicit_bzero(o.word, sizeof(uint16_t)); + break; + } + break; + case 'd': + o.dword = va_arg(ap_copy, uint32_t *); + if (buffer->secure) { + explicit_bzero(o.dword, sizeof(uint32_t)); + break; + } + break; + case 'q': + o.qword = va_arg(ap_copy, uint64_t *); + if (buffer->secure) { + explicit_bzero(o.qword, sizeof(uint64_t)); + break; + } + break; + case 'B': + o.bignum = va_arg(ap_copy, bignum *); + bignum_safe_free(*o.bignum); + break; + case 'S': + o.string = va_arg(ap_copy, ssh_string *); + if (buffer->secure) { + ssh_string_burn(*o.string); + } + SAFE_FREE(*o.string); + break; + case 's': + o.cstring = va_arg(ap_copy, char **); + if (buffer->secure) { + explicit_bzero(*o.cstring, strlen(*o.cstring)); + } + SAFE_FREE(*o.cstring); + break; + case 'P': + len = va_arg(ap_copy, size_t); + o.data = va_arg(ap_copy, void **); + if (buffer->secure) { + explicit_bzero(*o.data, len); + } + SAFE_FREE(*o.data); + break; + default: + (void)va_arg(ap_copy, void *); + break; + } + } + } + va_end(ap_copy); + + return rc; +} + +/** @internal + * @brief Get multiple values from a buffer on a single function call + * @param[in] buffer The buffer to get from + * @param[in] format A format string of arguments. This string contains single + * letters describing the order and type of arguments: + * 'b': uint8_t * (pulled in network byte order) + * 'w': uint16_t * (pulled in network byte order) + * 'd': uint32_t * (pulled in network byte order) + * 'q': uint64_t * (pulled in network byte order) + * 'S': ssh_string * + * 's': char ** (C string, pulled as SSH string) + * 'P': size_t, void ** (len of data, pointer to data) + * only pulls data. + * 'B': bignum * (pulled as SSH string) + * @returns SSH_OK on success + * SSH_ERROR on error + * @warning when using 'P' with a constant size (e.g. 8), do not + * forget to cast to (size_t). + */ +int _ssh_buffer_unpack(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + ...) +{ + va_list ap; + int rc; + + va_start(ap, argc); + rc = ssh_buffer_unpack_va(buffer, format, argc, ap); + va_end(ap); + return rc; +} + +/** @} */ diff --git a/src/callbacks.c b/src/callbacks.c new file mode 100644 index 0000000..3ed2f11 --- /dev/null +++ b/src/callbacks.c @@ -0,0 +1,147 @@ +/* + * callbacks.c - callback functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2009-2013 by Andreas Schneider + * + * 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/callbacks.h" +#include "libssh/session.h" +#include "libssh/misc.h" + +#define is_callback_valid(session, cb) \ + (cb->size <= 0 || cb->size > 1024 * sizeof(void *)) + +/* LEGACY */ +static void ssh_legacy_log_callback(int priority, + const char *function, + const char *buffer, + void *userdata) +{ + ssh_session session = (ssh_session)userdata; + ssh_log_callback log_fn = session->common.callbacks->log_function; + void *log_data = session->common.callbacks->userdata; + + (void)function; /* unused */ + + log_fn(session, priority, buffer, log_data); +} + +int ssh_set_callbacks(ssh_session session, ssh_callbacks cb) { + if (session == NULL || cb == NULL) { + return SSH_ERROR; + } + + if (is_callback_valid(session, cb)) { + ssh_set_error(session, + SSH_FATAL, + "Invalid callback passed in (badly initialized)"); + return SSH_ERROR; + }; + session->common.callbacks = cb; + + /* LEGACY */ + if (ssh_get_log_callback() == NULL && cb->log_function) { + ssh_set_log_callback(ssh_legacy_log_callback); + ssh_set_log_userdata(session); + } + + return 0; +} + +static int ssh_add_set_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb, + int prepend) +{ + ssh_session session = NULL; + int rc; + + if (channel == NULL || cb == NULL) { + return SSH_ERROR; + } + session = channel->session; + + if (is_callback_valid(session, cb)) { + ssh_set_error(session, + SSH_FATAL, + "Invalid callback passed in (badly initialized)"); + return SSH_ERROR; + }; + if (channel->callbacks == NULL) { + channel->callbacks = ssh_list_new(); + if (channel->callbacks == NULL){ + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + if (prepend) { + rc = ssh_list_prepend(channel->callbacks, cb); + } else { + rc = ssh_list_append(channel->callbacks, cb); + } + + return rc; +} + +int ssh_set_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb) +{ + return ssh_add_set_channel_callbacks(channel, cb, 1); +} + +int ssh_add_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb) +{ + return ssh_add_set_channel_callbacks(channel, cb, 0); +} + +int ssh_remove_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb) +{ + struct ssh_iterator *it; + + if (channel == NULL || channel->callbacks == NULL){ + return SSH_ERROR; + } + + it = ssh_list_find(channel->callbacks, cb); + if (it == NULL){ + return SSH_ERROR; + } + + ssh_list_remove(channel->callbacks, it); + + return SSH_OK; +} + + +int ssh_set_server_callbacks(ssh_session session, ssh_server_callbacks cb){ + if (session == NULL || cb == NULL) { + return SSH_ERROR; + } + + if (is_callback_valid(session, cb)) { + ssh_set_error(session, + SSH_FATAL, + "Invalid callback passed in (badly initialized)"); + return SSH_ERROR; + }; + session->server_callbacks = cb; + + return 0; +} diff --git a/src/chachapoly.c b/src/chachapoly.c new file mode 100644 index 0000000..820e7f6 --- /dev/null +++ b/src/chachapoly.c @@ -0,0 +1,213 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2015 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/libssh.h" +#include "libssh/crypto.h" +#include "libssh/chacha.h" +#include "libssh/poly1305.h" +#include "libssh/misc.h" + +/* size of the keys k1 and k2 as defined in specs */ +#define CHACHA20_KEYLEN 32 +struct chacha20_poly1305_keysched { + /* key used for encrypting the length field*/ + struct chacha_ctx k1; + /* key used for encrypting the packets */ + struct chacha_ctx k2; +}; + +#pragma pack(push, 1) +struct ssh_packet_header { + uint32_t length; + uint8_t payload[]; +}; +#pragma pack(pop) + +static const uint8_t zero_block_counter[8] = {0, 0, 0, 0, 0, 0, 0, 0}; +static const uint8_t payload_block_counter[8] = {1, 0, 0, 0, 0, 0, 0, 0}; + +static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + struct chacha20_poly1305_keysched *sched; + uint8_t *u8key = key; + (void)IV; + + if (cipher->chacha20_schedule == NULL) { + sched = malloc(sizeof *sched); + if (sched == NULL){ + return -1; + } + } else { + sched = cipher->chacha20_schedule; + } + + chacha_keysetup(&sched->k2, u8key, CHACHA20_KEYLEN * 8); + chacha_keysetup(&sched->k1, u8key + CHACHA20_KEYLEN, CHACHA20_KEYLEN * 8); + cipher->chacha20_schedule = sched; + + return 0; +} + +/** + * @internal + * + * @brief encrypts an outgoing packet with chacha20 and authenticate it + * with poly1305. + */ +static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + struct ssh_packet_header *in_packet = in, *out_packet = out; + uint8_t poly1305_ctx[POLY1305_KEYLEN] = {0}; + struct chacha20_poly1305_keysched *keys = cipher->chacha20_schedule; + + seq = htonll(seq); + /* step 1, prepare the poly1305 key */ + chacha_ivsetup(&keys->k2, (uint8_t *)&seq, zero_block_counter); + chacha_encrypt_bytes(&keys->k2, + poly1305_ctx, + poly1305_ctx, + POLY1305_KEYLEN); + + /* step 2, encrypt length field */ + chacha_ivsetup(&keys->k1, (uint8_t *)&seq, zero_block_counter); + chacha_encrypt_bytes(&keys->k1, + (uint8_t *)&in_packet->length, + (uint8_t *)&out_packet->length, + sizeof(uint32_t)); + + /* step 3, encrypt packet payload */ + chacha_ivsetup(&keys->k2, (uint8_t *)&seq, payload_block_counter); + chacha_encrypt_bytes(&keys->k2, + in_packet->payload, + out_packet->payload, + len - sizeof(uint32_t)); + + /* ssh_log_hexdump("poly1305_ctx", poly1305_ctx, sizeof(poly1305_ctx)); */ + /* step 4, compute the MAC */ + poly1305_auth(tag, (uint8_t *)out_packet, len, poly1305_ctx); + /* ssh_log_hexdump("poly1305 src", (uint8_t *)out_packet, len); + ssh_log_hexdump("poly1305 tag", tag, POLY1305_TAGLEN); */ +} + +static int chacha20_poly1305_aead_decrypt_length( + struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *keys = cipher->chacha20_schedule; + + if (len < sizeof(uint32_t)) { + return SSH_ERROR; + } + seq = htonll(seq); + + chacha_ivsetup(&keys->k1, (uint8_t *)&seq, zero_block_counter); + chacha_encrypt_bytes(&keys->k1, + in, + (uint8_t *)out, + sizeof(uint32_t)); + return SSH_OK; +} + +static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + uint8_t poly1305_ctx[POLY1305_KEYLEN] = {0}; + uint8_t tag[POLY1305_TAGLEN] = {0}; + struct chacha20_poly1305_keysched *keys = cipher->chacha20_schedule; + uint8_t *mac = (uint8_t *)complete_packet + sizeof(uint32_t) + encrypted_size; + int cmp; + + seq = htonll(seq); + + ZERO_STRUCT(poly1305_ctx); + chacha_ivsetup(&keys->k2, (uint8_t *)&seq, zero_block_counter); + chacha_encrypt_bytes(&keys->k2, + poly1305_ctx, + poly1305_ctx, + POLY1305_KEYLEN); +#if 0 + ssh_log_hexdump("poly1305_ctx", poly1305_ctx, sizeof(poly1305_ctx)); +#endif + + poly1305_auth(tag, (uint8_t *)complete_packet, encrypted_size + + sizeof(uint32_t), poly1305_ctx); +#if 0 + ssh_log_hexdump("poly1305 src", + (uint8_t*)complete_packet, + encrypted_size + 4); + ssh_log_hexdump("poly1305 tag", tag, POLY1305_TAGLEN); + ssh_log_hexdump("received tag", mac, POLY1305_TAGLEN); +#endif + + cmp = memcmp(tag, mac, POLY1305_TAGLEN); + if(cmp != 0) { + /* mac error */ + SSH_LOG(SSH_LOG_PACKET,"poly1305 verify error"); + return SSH_ERROR; + } + chacha_ivsetup(&keys->k2, (uint8_t *)&seq, payload_block_counter); + chacha_encrypt_bytes(&keys->k2, + (uint8_t *)complete_packet + sizeof(uint32_t), + out, + encrypted_size); + + return SSH_OK; +} + +static void chacha20_cleanup(struct ssh_cipher_struct *cipher) { + SAFE_FREE(cipher->chacha20_schedule); +} + +const struct ssh_cipher_struct chacha20poly1305_cipher = { + .ciphertype = SSH_AEAD_CHACHA20_POLY1305, + .name = "chacha20-poly1305@openssh.com", + .blocksize = 8, + .lenfield_blocksize = 4, + .keylen = sizeof(struct chacha20_poly1305_keysched), + .keysize = 512, + .tag_size = POLY1305_TAGLEN, + .set_encrypt_key = chacha20_set_encrypt_key, + .set_decrypt_key = chacha20_set_encrypt_key, + .aead_encrypt = chacha20_poly1305_aead_encrypt, + .aead_decrypt_length = chacha20_poly1305_aead_decrypt_length, + .aead_decrypt = chacha20_poly1305_aead_decrypt, + .cleanup = chacha20_cleanup +}; + +const struct ssh_cipher_struct *ssh_get_chacha20poly1305_cipher(void) +{ + return &chacha20poly1305_cipher; +} diff --git a/src/channels.c b/src/channels.c new file mode 100644 index 0000000..05dcf8f --- /dev/null +++ b/src/channels.c @@ -0,0 +1,3673 @@ +/* + * channels.c - SSH channel functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 by Aris Adamantiadis + * Copyright (c) 2009-2013 by Andreas Schneider + * + * 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 +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/socket.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/messages.h" +#if WITH_SERVER +#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 + +/** + * @defgroup libssh_channel The SSH channel functions + * @ingroup libssh + * + * Functions that manage a SSH channel. + * + * @{ + */ + +static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet); + +/** + * @brief Allocate a new channel. + * + * @param[in] session The ssh session to use. + * + * @return A pointer to a newly allocated channel, NULL on error. + */ +ssh_channel ssh_channel_new(ssh_session session) +{ + ssh_channel channel = NULL; + + if (session == NULL) { + return NULL; + } + + /* Check if we have an authenticated session */ + if (!(session->flags & SSH_SESSION_FLAG_AUTHENTICATED)) { + return NULL; + } + + channel = calloc(1, sizeof(struct ssh_channel_struct)); + if (channel == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + channel->stdout_buffer = ssh_buffer_new(); + if (channel->stdout_buffer == NULL) { + ssh_set_error_oom(session); + SAFE_FREE(channel); + return NULL; + } + + channel->stderr_buffer = ssh_buffer_new(); + if (channel->stderr_buffer == NULL) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(channel->stdout_buffer); + SAFE_FREE(channel); + return NULL; + } + + channel->session = session; + channel->exit_status = -1; + channel->flags = SSH_CHANNEL_FLAG_NOT_BOUND; + + if (session->channels == NULL) { + session->channels = ssh_list_new(); + } + + ssh_list_prepend(session->channels, channel); + + /* Set states explicitly */ + channel->state = SSH_CHANNEL_STATE_NOT_OPEN; + channel->request_state = SSH_CHANNEL_REQ_STATE_NONE; + + return channel; +} + +/** + * @internal + * + * @brief Create a new channel identifier. + * + * @param[in] session The SSH session to use. + * + * @return The new channel identifier. + */ +uint32_t ssh_channel_new_id(ssh_session session) { + return ++(session->maxchannel); +} + +/** + * @internal + * + * @brief Handle a SSH_PACKET_CHANNEL_OPEN_CONFIRMATION packet. + * + * Constructs the channel object. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf){ + uint32_t channelid=0; + ssh_channel channel; + int rc; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_PACKET,"Received SSH2_MSG_CHANNEL_OPEN_CONFIRMATION"); + + rc = ssh_buffer_unpack(packet, "d", &channelid); + if (rc != SSH_OK) + goto error; + channel=ssh_channel_from_local(session,channelid); + if(channel==NULL){ + ssh_set_error(session, SSH_FATAL, + "Unknown channel id %"PRIu32, + (uint32_t) channelid); + /* TODO: Set error marking in channel object */ + + return SSH_PACKET_USED; + } + + rc = ssh_buffer_unpack(packet, "ddd", + &channel->remote_channel, + &channel->remote_window, + &channel->remote_maxpacket); + if (rc != SSH_OK) + goto error; + + SSH_LOG(SSH_LOG_PROTOCOL, + "Received a CHANNEL_OPEN_CONFIRMATION for channel %d:%d", + channel->local_channel, + channel->remote_channel); + + if (channel->state != SSH_CHANNEL_STATE_OPENING) { + SSH_LOG(SSH_LOG_RARE, + "SSH2_MSG_CHANNEL_OPEN_CONFIRMATION received in incorrect " + "channel state %d", + channel->state); + goto error; + } + + SSH_LOG(SSH_LOG_PROTOCOL, + "Remote window : %"PRIu32", maxpacket : %"PRIu32, + (uint32_t) channel->remote_window, + (uint32_t) channel->remote_maxpacket); + + channel->state = SSH_CHANNEL_STATE_OPEN; + channel->flags &= ~SSH_CHANNEL_FLAG_NOT_BOUND; + return SSH_PACKET_USED; + +error: + ssh_set_error(session, SSH_FATAL, "Invalid packet"); + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_CHANNEL_OPEN_FAILURE and set the state of the channel. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail){ + + ssh_channel channel; + char *error = NULL; + uint32_t code; + int rc; + (void)user; + (void)type; + + channel=channel_from_msg(session,packet); + if(channel==NULL){ + SSH_LOG(SSH_LOG_RARE,"Invalid channel in packet"); + return SSH_PACKET_USED; + } + + rc = ssh_buffer_unpack(packet, "ds", &code, &error); + if (rc != SSH_OK){ + ssh_set_error(session, SSH_FATAL, "Invalid packet"); + return SSH_PACKET_USED; + } + + if (channel->state != SSH_CHANNEL_STATE_OPENING) { + SSH_LOG(SSH_LOG_RARE, + "SSH2_MSG_CHANNEL_OPEN_FAILURE received in incorrect channel " + "state %d", + channel->state); + goto error; + } + + ssh_set_error(session, SSH_REQUEST_DENIED, + "Channel opening failure: channel %u error (%"PRIu32") %s", + channel->local_channel, + (uint32_t) code, + error); + SAFE_FREE(error); + channel->state=SSH_CHANNEL_STATE_OPEN_DENIED; + return SSH_PACKET_USED; + +error: + ssh_set_error(session, SSH_FATAL, "Invalid packet"); + return SSH_PACKET_USED; +} + +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) + return 1; + else + return 0; +} + +/** + * @internal + * + * @brief Open a channel by sending a SSH_OPEN_CHANNEL message and + * wait for the reply. + * + * @param[in] channel The current channel. + * + * @param[in] type A C string describing the kind of channel (e.g. "exec"). + * + * @param[in] window The receiving window of the channel. The window is the + * maximum size of data that can stay in buffers and + * network. + * + * @param[in] maxpacket The maximum packet size allowed (like MTU). + * + * @param[in] payload The buffer containing additional payload for the query. + * + * @return SSH_OK if successful; SSH_ERROR otherwise. + */ +static int +channel_open(ssh_channel channel, + const char *type, + uint32_t window, + uint32_t maxpacket, + ssh_buffer payload) +{ + ssh_session session = channel->session; + int err = SSH_ERROR; + int rc; + + switch (channel->state) { + case SSH_CHANNEL_STATE_NOT_OPEN: + break; + case SSH_CHANNEL_STATE_OPENING: + goto pending; + case SSH_CHANNEL_STATE_OPEN: + case SSH_CHANNEL_STATE_CLOSED: + case SSH_CHANNEL_STATE_OPEN_DENIED: + goto end; + default: + ssh_set_error(session, SSH_FATAL, "Bad state in channel_open: %d", + channel->state); + } + + channel->local_channel = ssh_channel_new_id(session); + channel->local_maxpacket = maxpacket; + channel->local_window = window; + + SSH_LOG(SSH_LOG_PROTOCOL, + "Creating a channel %d with %d window and %d max packet", + channel->local_channel, window, maxpacket); + + rc = ssh_buffer_pack(session->out_buffer, + "bsddd", + SSH2_MSG_CHANNEL_OPEN, + type, + channel->local_channel, + channel->local_window, + channel->local_maxpacket); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return err; + } + + if (payload != NULL) { + if (ssh_buffer_add_buffer(session->out_buffer, payload) < 0) { + ssh_set_error_oom(session); + + return err; + } + } + channel->state = SSH_CHANNEL_STATE_OPENING; + if (ssh_packet_send(session) == SSH_ERROR) { + return err; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sent a SSH_MSG_CHANNEL_OPEN type %s for channel %d", + type, channel->local_channel); + +pending: + /* wait until channel is opened by server */ + err = ssh_handle_packets_termination(session, + SSH_TIMEOUT_DEFAULT, + ssh_channel_open_termination, + channel); + + if (session->session_state == SSH_SESSION_STATE_ERROR) { + err = SSH_ERROR; + } + +end: + /* This needs to pass the SSH_AGAIN from the above, + * but needs to catch failed channel states */ + 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 */ + err = SSH_ERROR; + } + + return err; +} + +/* return channel with corresponding local id, or NULL if not found */ +ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id) { + struct ssh_iterator *it; + ssh_channel channel; + + for (it = ssh_list_get_iterator(session->channels); it != NULL ; it=it->next) { + channel = ssh_iterator_value(ssh_channel, it); + if (channel == NULL) { + continue; + } + if (channel->local_channel == id) { + return channel; + } + } + + return NULL; +} + +/** + * @internal + * @brief grows the local window and send 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) +{ + uint32_t new_window = minimumsize > WINDOWBASE ? minimumsize : WINDOWBASE; + 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, + 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); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + if (ssh_packet_send(session) == SSH_ERROR) { + goto error; + } + + SSH_LOG(SSH_LOG_PROTOCOL, + "growing window (channel %d:%d) to %d bytes", + channel->local_channel, + channel->remote_channel, + new_window); + + channel->local_window = new_window; + + return SSH_OK; +error: + ssh_buffer_reinit(session->out_buffer); + + return SSH_ERROR; +} + +/** + * @internal + * + * @brief Parse a channel-related packet to resolve it to a ssh_channel. + * + * @param[in] session The current SSH session. + * + * @param[in] packet The buffer to parse packet from. The read pointer will + * be moved after the call. + * + * @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) { + ssh_channel channel; + uint32_t chan; + int rc; + + rc = ssh_buffer_unpack(packet,"d",&chan); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, + "Getting channel from message: short read"); + return NULL; + } + + channel = ssh_channel_from_local(session, chan); + if (channel == NULL) { + ssh_set_error(session, SSH_FATAL, + "Server specified invalid channel %"PRIu32, + (uint32_t) chan); + } + + return channel; +} + +SSH_PACKET_CALLBACK(channel_rcv_change_window) { + ssh_channel channel; + uint32_t bytes; + int rc; + (void)user; + (void)type; + + channel = channel_from_msg(session,packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + } + + rc = ssh_buffer_unpack(packet, "d", &bytes); + if (channel == NULL || rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, + "Error getting a window adjust message: invalid packet"); + + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PROTOCOL, + "Adding %d bytes to channel (%d:%d) (from %d bytes)", + bytes, + channel->local_channel, + channel->remote_channel, + channel->remote_window); + + channel->remote_window += bytes; + + 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; + + if(type==SSH2_MSG_CHANNEL_DATA) + is_stderr=0; + else + is_stderr=1; + + channel = channel_from_msg(session,packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, + "%s", ssh_get_error(session)); + + return SSH_PACKET_USED; + } + + if (is_stderr) { + uint32_t ignore; + /* uint32 data type code. we can ignore it */ + ssh_buffer_get_u32(packet, &ignore); + } + + str = ssh_buffer_get_ssh_string(packet); + if (str == NULL) { + SSH_LOG(SSH_LOG_PACKET, "Invalid data packet!"); + + return SSH_PACKET_USED; + } + len = ssh_string_len(str); + + SSH_LOG(SSH_LOG_PACKET, + "Channel receiving %" PRIdS " bytes data in %d (local win=%d remote win=%d)", + len, + is_stderr, + channel->local_window, + channel->remote_window); + + /* 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(%" PRIdS " vs %d)", + len, + channel->local_window); + } + + if (channel_default_bufferize(channel, ssh_string_data(str), 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_STRING_FREE(str); + + 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(); + + if (channel->local_window + ssh_buffer_get_len(buf) < WINDOWLIMIT) { + if (grow_window(session, channel, 0) < 0) { + return -1; + } + } + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(channel_rcv_eof) { + 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)); + + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PACKET, + "Received eof on channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + /* channel->remote_window = 0; */ + channel->remote_eof = 1; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_eof_function, + channel->session, + channel); + + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(channel_rcv_close) { + 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)); + + 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_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_close_function, + channel->session, + 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; +} + +SSH_PACKET_CALLBACK(channel_rcv_request) { + ssh_channel channel; + char *request=NULL; + uint8_t status; + int rc; + (void)user; + (void)type; + + channel = channel_from_msg(session,packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS,"%s", ssh_get_error(session)); + return SSH_PACKET_USED; + } + + rc = ssh_buffer_unpack(packet, "sb", + &request, + &status); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + return SSH_PACKET_USED; + } + + if (strcmp(request,"exit-status") == 0) { + SAFE_FREE(request); + rc = ssh_buffer_unpack(packet, "d", &channel->exit_status); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid exit-status packet"); + return SSH_PACKET_USED; + } + SSH_LOG(SSH_LOG_PACKET, "received exit-status %d", channel->exit_status); + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_exit_status_function, + channel->session, + channel, + channel->exit_status); + + return SSH_PACKET_USED; + } + + if (strcmp(request,"signal") == 0) { + char *sig = NULL; + + SAFE_FREE(request); + SSH_LOG(SSH_LOG_PACKET, "received signal"); + + rc = ssh_buffer_unpack(packet, "s", &sig); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PACKET, + "Remote connection sent a signal SIG %s", sig); + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_signal_function, + channel->session, + channel, + sig); + SAFE_FREE(sig); + + return SSH_PACKET_USED; + } + + if (strcmp(request, "exit-signal") == 0) { + const char *core = "(core dumped)"; + char *sig = NULL; + char *errmsg = NULL; + char *lang = NULL; + uint8_t core_dumped; + + SAFE_FREE(request); + + rc = ssh_buffer_unpack(packet, "sbss", + &sig, /* signal name */ + &core_dumped, /* core dumped */ + &errmsg, /* error message */ + &lang); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + return SSH_PACKET_USED; + } + + if (core_dumped == 0) { + core = ""; + } + + SSH_LOG(SSH_LOG_PACKET, + "Remote connection closed by signal SIG %s %s", sig, core); + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_exit_signal_function, + channel->session, + channel, + sig, + core_dumped, + errmsg, + lang); + + SAFE_FREE(lang); + SAFE_FREE(errmsg); + SAFE_FREE(sig); + + return SSH_PACKET_USED; + } + if(strcmp(request,"keepalive@openssh.com")==0){ + SAFE_FREE(request); + SSH_LOG(SSH_LOG_PROTOCOL,"Responding to Openssh's keepalive"); + + rc = ssh_buffer_pack(session->out_buffer, + "bd", + SSH2_MSG_CHANNEL_FAILURE, + channel->remote_channel); + if (rc != SSH_OK) { + return SSH_PACKET_USED; + } + ssh_packet_send(session); + + return SSH_PACKET_USED; + } + + if (strcmp(request, "auth-agent-req@openssh.com") == 0) { + 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); + + return SSH_PACKET_USED; + } +#ifdef WITH_SERVER + /* If we are here, that means we have a request that is not in the understood + * 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); +#else + SSH_LOG(SSH_LOG_WARNING, "Unhandled channel request %s", request); +#endif + + SAFE_FREE(request); + + return SSH_PACKET_USED; +} + +/* + * When data has been received from the ssh server, it can be applied to the + * known user function, with help of the callback, or inserted here + * + * FIXME is the window changed? + */ +int channel_default_bufferize(ssh_channel channel, + void *data, size_t len, + bool is_stderr) +{ + ssh_session session; + + if(channel == NULL) { + return -1; + } + + session = channel->session; + + if(data == NULL) { + ssh_set_error_invalid(session); + return -1; + } + + SSH_LOG(SSH_LOG_PACKET, + "placing %zu bytes into channel buffer (%s)", + len, + is_stderr ? "stderr" : "stdout"); + if (!is_stderr) { + /* stdout */ + if (channel->stdout_buffer == NULL) { + channel->stdout_buffer = ssh_buffer_new(); + if (channel->stdout_buffer == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + + if (ssh_buffer_add_data(channel->stdout_buffer, data, len) < 0) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(channel->stdout_buffer); + channel->stdout_buffer = NULL; + return -1; + } + } else { + /* stderr */ + if (channel->stderr_buffer == NULL) { + channel->stderr_buffer = ssh_buffer_new(); + if (channel->stderr_buffer == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + + if (ssh_buffer_add_data(channel->stderr_buffer, data, len) < 0) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(channel->stderr_buffer); + channel->stderr_buffer = NULL; + return -1; + } + } + + return 0; +} + +/** + * @brief Open a session channel (suited for a shell, not TCP forwarding). + * + * @param[in] channel An allocated channel. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + * + * @see ssh_channel_open_forward() + * @see ssh_channel_request_env() + * @see ssh_channel_request_shell() + * @see ssh_channel_request_exec() + */ +int ssh_channel_open_session(ssh_channel channel) { + if(channel == NULL) { + return SSH_ERROR; + } + + return channel_open(channel, + "session", + CHANNEL_INITIAL_WINDOW, + CHANNEL_MAX_PACKET, + NULL); +} + +/** + * @brief Open an agent authentication forwarding channel. This type of channel + * can be opened by a server towards a client in order to provide SSH-Agent services + * to the server-side process. This channel can only be opened if the client + * claimed support by sending a channel request beforehand. + * + * @param[in] channel An allocated channel. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + * + * @see ssh_channel_open_forward() + */ +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, + CHANNEL_MAX_PACKET, + NULL); +} + + +/** + * @brief Open a TCP/IP forwarding channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] remotehost The remote host to connected (host name or IP). + * + * @param[in] remoteport The remote port. + * + * @param[in] sourcehost The numeric IP address of the machine from where the + * connection request originates. This is mostly for + * logging purposes. + * + * @param[in] localport The port on the host from where the connection + * originated. This is mostly for logging purposes. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * 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. + */ +int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, + 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) { + return rc; + } + + session = channel->session; + + if(remotehost == NULL || sourcehost == NULL) { + ssh_set_error_invalid(session); + return rc; + } + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(payload, + "sdsd", + remotehost, + remoteport, + sourcehost, + localport); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = channel_open(channel, + "direct-tcpip", + CHANNEL_INITIAL_WINDOW, + CHANNEL_MAX_PACKET, + payload); + +error: + SSH_BUFFER_FREE(payload); + SSH_STRING_FREE(str); + + return rc; +} + +/** + * @brief Open a TCP/IP - UNIX domain socket forwarding channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] remotepath The UNIX socket path on the remote machine + * + * @param[in] sourcehost The numeric IP address of the machine from where the + * connection request originates. This is mostly for + * logging purposes. + * + * @param[in] localport The port on the host from where the connection + * originated. This is mostly for logging purposes. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * 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. + * @warning Requires support of OpenSSH for UNIX domain socket forwarding. + */ +int ssh_channel_open_forward_unix(ssh_channel channel, + const char *remotepath, + const char *sourcehost, + int localport) +{ + ssh_session session = NULL; + ssh_buffer payload = NULL; + ssh_string str = NULL; + int rc = SSH_ERROR; + int version; + + if (channel == NULL) { + return rc; + } + + session = channel->session; + + version = ssh_get_openssh_version(session); + if (version == 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "We're not connected to an OpenSSH server!"); + return SSH_ERROR; + } + + if (remotepath == NULL || sourcehost == NULL) { + ssh_set_error_invalid(session); + return rc; + } + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(payload, + "ssd", + remotepath, + sourcehost, + localport); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = channel_open(channel, + "direct-streamlocal@openssh.com", + CHANNEL_INITIAL_WINDOW, + CHANNEL_MAX_PACKET, + payload); + +error: + SSH_BUFFER_FREE(payload); + SSH_STRING_FREE(str); + + return rc; +} + +/** + * @brief Close and free a channel. + * + * @param[in] channel The channel to free. + * + * @warning Any data unread on this channel will be lost. + */ +void ssh_channel_free(ssh_channel channel) +{ + ssh_session session; + + if (channel == NULL) { + return; + } + + session = channel->session; + if (session->alive) { + bool send_close = false; + + switch (channel->state) { + case SSH_CHANNEL_STATE_OPEN: + send_close = true; + break; + case SSH_CHANNEL_STATE_CLOSED: + if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) { + send_close = true; + } + if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_LOCAL) { + send_close = false; + } + break; + default: + send_close = false; + break; + } + + if (send_close) { + ssh_channel_close(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. + * We definitively close the channel when we receive a close message *and* + * the user closed it. + */ + if ((channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) || + (channel->flags & SSH_CHANNEL_FLAG_NOT_BOUND)) { + ssh_channel_do_free(channel); + } +} + +/** + * @internal + * @brief Effectively free a channel, without caring about flags + */ + +void ssh_channel_do_free(ssh_channel channel) +{ + struct ssh_iterator *it = NULL; + ssh_session session = channel->session; + + it = ssh_list_find(session->channels, channel); + if (it != NULL) { + ssh_list_remove(session->channels, it); + } + + SSH_BUFFER_FREE(channel->stdout_buffer); + SSH_BUFFER_FREE(channel->stderr_buffer); + + if (channel->callbacks != NULL) { + ssh_list_free(channel->callbacks); + channel->callbacks = NULL; + } + + channel->session = NULL; + SAFE_FREE(channel); +} + +/** + * @brief Send an end of file on the channel. + * + * This doesn't close the channel. You may still read from it but not write. + * + * @param[in] channel The channel to send the eof to. + * + * @return SSH_OK on success, SSH_ERROR if an error occurred. + * + * Example: +@code + rc = ssh_channel_send_eof(channel); + if (rc == SSH_ERROR) { + return -1; + } + while(!ssh_channel_is_eof(channel)) { + rc = ssh_channel_read(channel, buf, sizeof(buf), 0); + if (rc == SSH_ERROR) { + return -1; + } + } + ssh_channel_close(channel); +@endcode + * + * @see ssh_channel_close() + * @see ssh_channel_free() + * @see ssh_channel_is_eof() + */ +int ssh_channel_send_eof(ssh_channel channel) +{ + ssh_session session; + int rc = SSH_ERROR; + int err; + + if (channel == NULL || channel->session == NULL) { + return rc; + } + + /* If the EOF has already been sent we're done here. */ + if (channel->local_eof != 0) { + return SSH_OK; + } + + session = channel->session; + + err = ssh_buffer_pack(session->out_buffer, + "bd", + SSH2_MSG_CHANNEL_EOF, + channel->remote_channel); + if (err != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_packet_send(session); + SSH_LOG(SSH_LOG_PACKET, + "Sent a EOF on client channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + if (rc != SSH_OK) { + goto error; + } + + rc = ssh_channel_flush(channel); + if (rc == SSH_ERROR) { + goto error; + } + channel->local_eof = 1; + + return rc; +error: + ssh_buffer_reinit(session->out_buffer); + + return rc; +} + +/** + * @brief Close a channel. + * + * This sends an end of file and then closes the channel. You won't be able + * to recover any data the server was going to send or was in buffers. + * + * @param[in] channel The channel to close. + * + * @return SSH_OK on success, SSH_ERROR if an error occurred. + * + * @see ssh_channel_free() + * @see ssh_channel_is_eof() + */ +int ssh_channel_close(ssh_channel channel) +{ + ssh_session session; + int rc = 0; + + if(channel == NULL) { + return SSH_ERROR; + } + + /* If the channel close has already been sent we're done here. */ + if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_LOCAL) { + return SSH_OK; + } + + session = channel->session; + + rc = ssh_channel_send_eof(channel); + if (rc != SSH_OK) { + return rc; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bd", + SSH2_MSG_CHANNEL_CLOSE, + channel->remote_channel); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_packet_send(session); + SSH_LOG(SSH_LOG_PACKET, + "Sent a close on client channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + + if (rc == SSH_OK) { + channel->state = SSH_CHANNEL_STATE_CLOSED; + channel->flags |= SSH_CHANNEL_FLAG_CLOSED_LOCAL; + } + + rc = ssh_channel_flush(channel); + if(rc == SSH_ERROR) { + goto error; + } + + return rc; +error: + ssh_buffer_reinit(session->out_buffer); + + return rc; +} + +/* this termination function waits for a window growing condition */ +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 || + channel->state == SSH_CHANNEL_STATE_CLOSED) + return 1; + else + return 0; +} + +/* 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){ + ssh_session session = (ssh_session)s; + switch (session->session_state){ + case SSH_SESSION_STATE_DH: + case SSH_SESSION_STATE_INITIAL_KEX: + case SSH_SESSION_STATE_KEXINIT_RECEIVED: + return 0; + default: + return 1; + } +} +/** + * @internal + * @brief Flushes a channel (and its session) until the output buffer + * is empty, or timeout elapsed. + * @param channel SSH channel + * @return SSH_OK On success, + * 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); +} + +static int channel_write_common(ssh_channel channel, + const void *data, + uint32_t len, int is_stderr) +{ + ssh_session session; + uint32_t origlen = len; + size_t effectivelen; + size_t maxpacketlen; + int rc; + + if(channel == NULL) { + return -1; + } + session = channel->session; + if(data == NULL) { + ssh_set_error_invalid(session); + return -1; + } + + if (len > INT_MAX) { + SSH_LOG(SSH_LOG_PROTOCOL, + "Length (%u) 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", + channel->local_channel, + channel->remote_channel); + return -1; + } + + if (channel->state != SSH_CHANNEL_STATE_OPEN || channel->delayed_close != 0) { + ssh_set_error(session, SSH_REQUEST_DENIED, "Remote channel is closed"); + + return -1; + } + + if (session->session_state == SSH_SESSION_STATE_ERROR) { + return SSH_ERROR; + } + + if (ssh_waitsession_unblocked(session) == 0){ + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_DEFAULT, + ssh_waitsession_unblocked, session); + if (rc == SSH_ERROR || !ssh_waitsession_unblocked(session)) + goto out; + } + while (len > 0) { + if (channel->remote_window < len) { + SSH_LOG(SSH_LOG_PROTOCOL, + "Remote window is %d bytes. going to write %d bytes", + channel->remote_window, + len); + /* What happens when the channel window is zero? */ + if(channel->remote_window == 0) { + /* nothing can be written */ + SSH_LOG(SSH_LOG_PROTOCOL, + "Wait for a growing window message..."); + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_DEFAULT, + ssh_channel_waitwindow_termination,channel); + if (rc == SSH_ERROR || + !ssh_channel_waitwindow_termination(channel) || + session->session_state == SSH_SESSION_STATE_ERROR || + channel->state == SSH_CHANNEL_STATE_CLOSED) + goto out; + continue; + } + effectivelen = MIN(len, channel->remote_window); + } else { + effectivelen = len; + } + + effectivelen = MIN(effectivelen, maxpacketlen);; + + rc = ssh_buffer_pack(session->out_buffer, + "bd", + is_stderr ? SSH2_MSG_CHANNEL_EXTENDED_DATA : SSH2_MSG_CHANNEL_DATA, + channel->remote_channel); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + /* stderr message has an extra field */ + if (is_stderr) { + rc = ssh_buffer_pack(session->out_buffer, + "d", + SSH2_EXTENDED_DATA_STDERR); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + } + + /* append payload data */ + rc = ssh_buffer_pack(session->out_buffer, + "dP", + effectivelen, + (size_t)effectivelen, data); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PACKET, + "channel_write wrote %ld bytes", (long int) effectivelen); + + channel->remote_window -= effectivelen; + len -= effectivelen; + data = ((uint8_t*)data + effectivelen); + if (channel->counter != NULL) { + channel->counter->out_bytes += effectivelen; + } + } + + /* it's a good idea to flush the socket now */ + rc = ssh_channel_flush(channel); + if (rc == SSH_ERROR) { + goto error; + } + +out: + return (int)(origlen - len); + +error: + ssh_buffer_reinit(session->out_buffer); + + return SSH_ERROR; +} + +/** + * @brief Get the remote window size. + * + * This is the maximum amounts of bytes the remote side expects us to send + * before growing the window again. + * + * @param[in] channel The channel to query. + * + * @return The remote window size + * + * @warning A nonzero return value does not guarantee the socket is ready + * to send that much data. Buffering may happen in the local SSH + * packet buffer, so beware of really big window sizes. + * + * @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) { + return channel->remote_window; +} + +/** + * @brief Blocking write on a channel. + * + * @param[in] channel The channel to write to. + * + * @param[in] data A pointer to the data to write. + * + * @param[in] len The length of the buffer to write to. + * + * @return The number of bytes written, SSH_ERROR on error. + * + * @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); +} + +/** + * @brief Check if the channel is open or not. + * + * @param[in] channel The channel to check. + * + * @return 0 if channel is closed, nonzero otherwise. + * + * @see ssh_channel_is_closed() + */ +int ssh_channel_is_open(ssh_channel channel) { + if(channel == NULL) { + return 0; + } + return (channel->state == SSH_CHANNEL_STATE_OPEN && channel->session->alive != 0); +} + +/** + * @brief Check if the channel is closed or not. + * + * @param[in] channel The channel to check. + * + * @return 0 if channel is opened, nonzero otherwise. + * + * @see ssh_channel_is_open() + */ +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); +} + +/** + * @brief Check if remote has sent an EOF. + * + * @param[in] channel The channel to check. + * + * @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; + } + + return (channel->remote_eof != 0); +} + +/** + * @brief Put the channel into blocking or nonblocking mode. + * + * @param[in] channel The channel to use. + * + * @param[in] blocking A boolean for blocking or nonblocking. + * + * @warning A side-effect of this is to put the whole session + * 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); +} + +/** + * @internal + * + * @brief handle a SSH_CHANNEL_SUCCESS packet and set the channel state. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_success){ + ssh_channel channel; + (void)type; + (void)user; + + channel=channel_from_msg(session,packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_CHANNEL_SUCCESS on channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING){ + SSH_LOG(SSH_LOG_RARE, "SSH_CHANNEL_SUCCESS received in incorrect state %d", + channel->request_state); + } else { + channel->request_state=SSH_CHANNEL_REQ_STATE_ACCEPTED; + } + + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_CHANNEL_FAILURE packet and set the channel state. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_failure){ + ssh_channel channel; + (void)type; + (void)user; + + channel=channel_from_msg(session,packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_CHANNEL_FAILURE on channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING){ + SSH_LOG(SSH_LOG_RARE, "SSH_CHANNEL_FAILURE received in incorrect state %d", + channel->request_state); + } else { + channel->request_state=SSH_CHANNEL_REQ_STATE_DENIED; + } + + return SSH_PACKET_USED; +} + +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) + return 1; + else + return 0; +} + +static int channel_request(ssh_channel channel, const char *request, + ssh_buffer buffer, int reply) { + ssh_session session = channel->session; + int rc = SSH_ERROR; + int ret; + + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + ret = ssh_buffer_pack(session->out_buffer, + "bdsb", + SSH2_MSG_CHANNEL_REQUEST, + channel->remote_channel, + request, + reply == 0 ? 0 : 1); + if (ret != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + if (buffer != NULL) { + if (ssh_buffer_add_data(session->out_buffer, ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)) < 0) { + ssh_set_error_oom(session); + goto error; + } + } + channel->request_state = SSH_CHANNEL_REQ_STATE_PENDING; + if (ssh_packet_send(session) == SSH_ERROR) { + return rc; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sent a SSH_MSG_CHANNEL_REQUEST %s", request); + if (reply == 0) { + channel->request_state = SSH_CHANNEL_REQ_STATE_NONE; + return SSH_OK; + } +pending: + rc = ssh_handle_packets_termination(session, + SSH_TIMEOUT_DEFAULT, + ssh_channel_request_termination, + channel); + + if(session->session_state == SSH_SESSION_STATE_ERROR || rc == SSH_ERROR) { + channel->request_state = SSH_CHANNEL_REQ_STATE_ERROR; + } + /* we received something */ + switch (channel->request_state){ + case SSH_CHANNEL_REQ_STATE_ERROR: + rc=SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_DENIED: + ssh_set_error(session, SSH_REQUEST_DENIED, + "Channel request %s failed", request); + rc=SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_ACCEPTED: + SSH_LOG(SSH_LOG_PROTOCOL, + "Channel request %s success",request); + rc=SSH_OK; + break; + case SSH_CHANNEL_REQ_STATE_PENDING: + rc = SSH_AGAIN; + return rc; + case SSH_CHANNEL_REQ_STATE_NONE: + /* Never reached */ + ssh_set_error(session, SSH_FATAL, "Invalid state in channel_request()"); + rc=SSH_ERROR; + break; + } + channel->request_state=SSH_CHANNEL_REQ_STATE_NONE; + + return rc; +error: + ssh_buffer_reinit(session->out_buffer); + + return rc; +} + +/** + * @brief Request a pty with a specific type and size. + * + * @param[in] channel The channel to sent the request. + * + * @param[in] terminal The terminal type ("vt100, xterm,..."). + * + * @param[in] col The number of columns. + * + * @param[in] row The number of rows. + * + * @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) { + ssh_session session; + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + session = channel->session; + + if(terminal == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(buffer, + "sdddddb", + terminal, + col, + row, + 0, /* pix */ + 0, /* pix */ + 1, /* add a 0byte string */ + 0); + + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } +pending: + rc = channel_request(channel, "pty-req", buffer, 1); +error: + SSH_BUFFER_FREE(buffer); + + return rc; +} + +/** + * @brief Request a PTY. + * + * @param[in] channel The channel to send the request. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + * + * @see ssh_channel_request_pty_size() + */ +int ssh_channel_request_pty(ssh_channel channel) { + return ssh_channel_request_pty_size(channel, "xterm", 80, 24); +} + +/** + * @brief Change the size of the terminal associated to a channel. + * + * @param[in] channel The channel to change the size. + * + * @param[in] cols The new number of columns. + * + * @param[in] rows The new number of rows. + * + * @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). + */ +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; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(buffer, + "dddd", + cols, + rows, + 0, /* pix */ + 0 /* pix */); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = channel_request(channel, "window-change", buffer, 0); +error: + SSH_BUFFER_FREE(buffer); + + return rc; +} + +/** + * @brief Request a shell. + * + * @param[in] channel The channel to send the request. + * + * @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_shell(ssh_channel channel) { + if(channel == NULL) { + return SSH_ERROR; + } + + return channel_request(channel, "shell", NULL, 1); +} + +/** + * @brief Request a subsystem (for example "sftp"). + * + * @param[in] channel The channel to send the request. + * + * @param[in] subsys The subsystem to request (for example "sftp"). + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + * + * @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) { + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + if(subsys == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "s", subsys); + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } +pending: + rc = channel_request(channel, "subsystem", buffer, 1); +error: + SSH_BUFFER_FREE(buffer); + + return rc; +} + +/** + * @brief Request sftp subsystem on the channel + * + * @param[in] channel The channel to request the sftp subsystem. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + * + * @note You should use sftp_new() which does this for you. + */ +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 const char *hex = "0123456789abcdef"; + char s[36]; + unsigned char rnd[16]; + int ok; + int i; + + ok = ssh_get_random(rnd, sizeof(rnd), 0); + if (!ok) { + return NULL; + } + + for (i = 0; i < 16; i++) { + s[i*2] = hex[rnd[i] & 0x0f]; + s[i*2+1] = hex[rnd[i] >> 4]; + } + s[32] = '\0'; + return strdup(s); +} + +/** + * @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. + * + * @param[in] channel An existing session channel where the remote X11 + * applications are going to be executed. + * + * @param[in] single_connection A boolean to mark only one X11 app will be + * redirected. + * + * @param[in] protocol A x11 authentication protocol. Pass NULL to use the + * default value MIT-MAGIC-COOKIE-1. + * + * @param[in] cookie A x11 authentication cookie. Pass NULL to generate + * a random cookie. + * + * @param[in] screen_number The screen number. + * + * @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_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number) { + ssh_buffer buffer = NULL; + char *c = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + if (cookie == NULL) { + c = generate_cookie(); + if (c == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + } + + rc = ssh_buffer_pack(buffer, + "bssd", + single_connection == 0 ? 0 : 1, + protocol ? protocol : "MIT-MAGIC-COOKIE-1", + cookie ? cookie : c, + screen_number); + if (c != NULL){ + SAFE_FREE(c); + } + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } +pending: + rc = channel_request(channel, "x11-req", buffer, 1); + +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +static ssh_channel ssh_channel_accept(ssh_session session, int channeltype, + int timeout_ms, int *destination_port) { +#ifndef _WIN32 + static const struct timespec ts = { + .tv_sec = 0, + .tv_nsec = 50000000 /* 50ms */ + }; +#endif + ssh_message msg = NULL; + ssh_channel channel = NULL; + struct ssh_iterator *iterator; + int t; + + /* + * We sleep for 50 ms in ssh_handle_packets() and later sleep for + * 50 ms. So we need to decrement by 100 ms. + */ + for (t = timeout_ms; t >= 0; t -= 100) { + if (timeout_ms == 0) { + ssh_handle_packets(session, 0); + } else { + ssh_handle_packets(session, 50); + } + + if (session->ssh_message_list) { + iterator = ssh_list_get_iterator(session->ssh_message_list); + while (iterator) { + msg = (ssh_message)iterator->data; + if (ssh_message_type(msg) == SSH_REQUEST_CHANNEL_OPEN && + ssh_message_subtype(msg) == channeltype) { + ssh_list_remove(session->ssh_message_list, iterator); + channel = ssh_message_channel_request_open_reply_accept(msg); + if(destination_port) { + *destination_port=msg->channel_request_open.destination_port; + } + + ssh_message_free(msg); + return channel; + } + iterator = iterator->next; + } + } + if(t>0){ +#ifdef _WIN32 + Sleep(50); /* 50ms */ +#else + nanosleep(&ts, NULL); +#endif + } + } + + ssh_set_error(session, SSH_NO_ERROR, "No channel request of this type from server"); + return NULL; +} + +/** + * @brief Accept an X11 forwarding channel. + * + * @param[in] channel An x11-enabled session channel. + * + * @param[in] timeout_ms Timeout in milliseconds. + * + * @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); +} + +/** + * @brief Send an "auth-agent-req" channel request over an existing session channel. + * + * This client-side request will enable forwarding the agent over an secure tunnel. + * When the server is ready to open one authentication agent channel, an + * ssh_channel_open_request_auth_agent_callback event will be generated. + * + * @param[in] channel The channel to send signal. + * + * @return SSH_OK on success, SSH_ERROR if an error occurred + */ +int ssh_channel_request_auth_agent(ssh_channel channel) { + if (channel == NULL) { + return SSH_ERROR; + } + + return channel_request(channel, "auth-agent-req@openssh.com", NULL, 0); +} + +/** + * @internal + * + * @brief Handle a SSH_REQUEST_SUCCESS packet normally sent after a global + * request. + */ +SSH_PACKET_CALLBACK(ssh_request_success){ + (void)type; + (void)user; + (void)packet; + + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_REQUEST_SUCCESS"); + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING){ + SSH_LOG(SSH_LOG_RARE, "SSH_REQUEST_SUCCESS received in incorrect state %d", + session->global_req_state); + } else { + session->global_req_state=SSH_CHANNEL_REQ_STATE_ACCEPTED; + } + + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_REQUEST_DENIED packet normally sent after a global + * request. + */ +SSH_PACKET_CALLBACK(ssh_request_denied){ + (void)type; + (void)user; + (void)packet; + + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_REQUEST_FAILURE"); + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING){ + SSH_LOG(SSH_LOG_RARE, "SSH_REQUEST_DENIED received in incorrect state %d", + session->global_req_state); + } else { + session->global_req_state=SSH_CHANNEL_REQ_STATE_DENIED; + } + + return SSH_PACKET_USED; + +} + +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) + return 1; + else + return 0; +} + +/** + * @internal + * + * @brief Send a global request (needed for forward listening) and wait for the + * result. + * + * @param[in] session The SSH session handle. + * + * @param[in] request The type of request (defined in RFC). + * + * @param[in] buffer Additional data to put in packet. + * + * @param[in] reply Set if you expect a reply from server. + * + * @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_global_request(ssh_session session, + const char *request, + ssh_buffer buffer, + int reply) +{ + int rc; + + switch (session->global_req_state) { + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bsb", + SSH2_MSG_GLOBAL_REQUEST, + request, + reply == 0 ? 0 : 1); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto error; + } + + if (buffer != NULL) { + rc = ssh_buffer_add_data(session->out_buffer, + ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); + if (rc < 0) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto error; + } + } + + session->global_req_state = SSH_CHANNEL_REQ_STATE_PENDING; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return rc; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sent a SSH_MSG_GLOBAL_REQUEST %s", request); + + if (reply == 0) { + session->global_req_state = SSH_CHANNEL_REQ_STATE_NONE; + + return SSH_OK; + } +pending: + rc = ssh_handle_packets_termination(session, + SSH_TIMEOUT_DEFAULT, + ssh_global_request_termination, + session); + + if(rc==SSH_ERROR || session->session_state == SSH_SESSION_STATE_ERROR){ + session->global_req_state = SSH_CHANNEL_REQ_STATE_ERROR; + } + switch(session->global_req_state){ + case SSH_CHANNEL_REQ_STATE_ACCEPTED: + SSH_LOG(SSH_LOG_PROTOCOL, "Global request %s success",request); + rc=SSH_OK; + break; + case SSH_CHANNEL_REQ_STATE_DENIED: + SSH_LOG(SSH_LOG_PACKET, + "Global request %s failed", request); + ssh_set_error(session, SSH_REQUEST_DENIED, + "Global request %s failed", request); + rc=SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_ERROR: + case SSH_CHANNEL_REQ_STATE_NONE: + rc = SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_PENDING: + return SSH_AGAIN; + } + session->global_req_state = SSH_CHANNEL_REQ_STATE_NONE; + + return rc; +error: + ssh_buffer_reinit(session->out_buffer); + + return rc; +} + +/** + * @brief Sends the "tcpip-forward" global request to ask the server to begin + * listening for inbound connections. + * + * @param[in] session The ssh session to send the request. + * + * @param[in] address The address to bind to on the server. Pass NULL to bind + * to all available addresses on all protocol families + * supported by the server. + * + * @param[in] port The port to bind to on the server. Pass 0 to ask the + * server to allocate the next available unprivileged port + * number + * + * @param[in] bound_port The pointer to get actual bound port. Pass NULL to + * ignore. + * + * @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_listen_forward(ssh_session session, + const char *address, + int port, + int *bound_port) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_NONE) + goto pending; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(buffer, + "sd", + address ? address : "", + port); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + goto error; + } +pending: + rc = ssh_global_request(session, "tcpip-forward", buffer, 1); + + /* TODO: FIXME no guarantee the last packet we received contains + * that info */ + if (rc == SSH_OK && port == 0 && bound_port != NULL) { + rc = ssh_buffer_unpack(session->in_buffer, "d", bound_port); + if (rc != SSH_OK) + *bound_port = 0; + } + +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +/* DEPRECATED */ +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); +} + +/** + * @brief Accept an incoming TCP/IP forwarding channel and get information + * about incomming connection + * @param[in] session The ssh session to use. + * + * @param[in] timeout_ms A timeout in milliseconds. + * + * @param[in] destination_port A pointer to destination port or NULL. + * + * @return Newly created channel, or NULL if no incoming channel request from + * 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); +} + +/** + * @brief Sends the "cancel-tcpip-forward" global request to ask the server to + * cancel the tcpip-forward request. + * + * @param[in] session The ssh session to send the request. + * + * @param[in] address The bound address on the server. + * + * @param[in] port The bound port on the server. + * + * @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_cancel_forward(ssh_session session, + const char *address, + int port) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_NONE) + goto pending; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "sd", + address ? address : "", + port); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + goto error; + } +pending: + rc = ssh_global_request(session, "cancel-tcpip-forward", buffer, 1); + +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +/* DEPRECATED */ +int ssh_forward_cancel(ssh_session session, const char *address, int port) { + return ssh_channel_cancel_forward(session, address, port); +} + +/** + * @brief Set environment variables. + * + * @param[in] channel The channel to set the environment variables. + * + * @param[in] name The name of the variable. + * + * @param[in] value The value to set. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * 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) { + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + if(name == NULL || value == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, + "ss", + name, + value); + if (rc != SSH_OK){ + ssh_set_error_oom(channel->session); + goto error; + } +pending: + rc = channel_request(channel, "env", buffer,1); +error: + SSH_BUFFER_FREE(buffer); + + return rc; +} + +/** + * @brief Run a shell command without an interactive shell. + * + * This is similar to 'sh -c command'. + * + * @param[in] channel The channel to execute the command. + * + * @param[in] cmd The command to execute + * (e.g. "ls ~/ -al | grep -i reports"). + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + * + * Example: +@code + rc = ssh_channel_request_exec(channel, "ps aux"); + if (rc > 0) { + return -1; + } + + while ((rc = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) { + if (fwrite(buffer, 1, rc, stdout) != (unsigned int) rc) { + return -1; + } + } +@endcode + * + * @see ssh_channel_request_shell() + */ +int ssh_channel_request_exec(ssh_channel channel, const char *cmd) { + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + if(cmd == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "s", cmd); + + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } +pending: + rc = channel_request(channel, "exec", buffer, 1); +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + + +/** + * @brief Send a signal to remote process (as described in RFC 4254, section 6.9). + * + * 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. + * + * @param[in] sig The signal to send (without SIG prefix) + * \n\n + * SIGABRT -> ABRT \n + * SIGALRM -> ALRM \n + * SIGFPE -> FPE \n + * SIGHUP -> HUP \n + * SIGILL -> ILL \n + * SIGINT -> INT \n + * SIGKILL -> KILL \n + * SIGPIPE -> PIPE \n + * SIGQUIT -> QUIT \n + * SIGSEGV -> SEGV \n + * SIGTERM -> TERM \n + * 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). + */ +int ssh_channel_request_send_signal(ssh_channel channel, const char *sig) { + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + if(sig == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "s", sig); + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = channel_request(channel, "signal", buffer, 0); +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + + +/** + * @brief Send a break signal to the server (as described in RFC 4335). + * + * 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) { + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if (channel == NULL) { + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "d", length); + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = channel_request(channel, "break", buffer, 0); + +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + + +/** + * @brief Read data from a channel into a buffer. + * + * @param[in] channel The channel to read from. + * + * @param[in] 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 + * return once anything is available. + * + * @param is_stderr A boolean value to mark reading from the stderr stream. + * + * @return The number of bytes read, 0 on end of file or SSH_ERROR + * on error. + * @deprecated Please use ssh_channel_read instead + * @warning This function doesn't work in nonblocking/timeout mode + * @see ssh_channel_read + */ +int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, + int is_stderr) { + ssh_session session; + char buffer_tmp[8192]; + int r; + uint32_t total=0; + + if(channel == NULL) { + return SSH_ERROR; + } + session = channel->session; + + if(buffer == NULL) { + ssh_set_error_invalid(channel->session); + return SSH_ERROR; + } + + ssh_buffer_reinit(buffer); + if(count==0){ + do { + r=ssh_channel_poll(channel, is_stderr); + if(r < 0){ + return r; + } + if(r > 0){ + r=ssh_channel_read(channel, buffer_tmp, r, is_stderr); + if(r < 0){ + return r; + } + if(ssh_buffer_add_data(buffer,buffer_tmp,r) < 0){ + ssh_set_error_oom(session); + r = SSH_ERROR; + } + + return r; + } + if(ssh_channel_is_eof(channel)){ + return 0; + } + ssh_handle_packets(channel->session, SSH_TIMEOUT_INFINITE); + } while (r == 0); + } + while(total < count){ + r=ssh_channel_read(channel, buffer_tmp, sizeof(buffer_tmp), is_stderr); + if(r<0){ + return r; + } + if(r==0){ + return total; + } + if (ssh_buffer_add_data(buffer,buffer_tmp,r) < 0) { + ssh_set_error_oom(session); + + return SSH_ERROR; + } + total += r; + } + + return total; +} + +struct ssh_channel_read_termination_struct { + ssh_channel channel; + uint32_t count; + ssh_buffer buffer; +}; + +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 || + ctx->channel->remote_eof || + ctx->channel->session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +/* TODO FIXME Fix the delayed close thing */ +/* 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[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 + * 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) +{ + return ssh_channel_read_timeout(channel, + dest, + count, + is_stderr, + SSH_TIMEOUT_DEFAULT); +} + +/** + * @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[in] count The count of bytes to be read. + * + * @param[in] is_stderr A boolean value to mark reading from the stderr flow. + * + * @param[in] timeout_ms A timeout in milliseconds. A value of -1 means + * infinite timeout. + * + * @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 + * 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_timeout(ssh_channel channel, + void *dest, + uint32_t count, + int is_stderr, + int timeout_ms) +{ + ssh_session session; + ssh_buffer stdbuf; + uint32_t len; + struct ssh_channel_read_termination_struct ctx; + int rc; + + if(channel == NULL) { + return SSH_ERROR; + } + if(dest == NULL) { + ssh_set_error_invalid(channel->session); + return SSH_ERROR; + } + + session = channel->session; + stdbuf = channel->stdout_buffer; + + if (count == 0) { + return 0; + } + + if (is_stderr) { + 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", + 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; + } + + rc = ssh_handle_packets_termination(session, + timeout_ms, + ssh_channel_read_termination, + &ctx); + if (rc == SSH_ERROR){ + return rc; + } + + /* + * If the channel is closed or in an error state, reading from it is an error + */ + if (session->session_state == SSH_SESSION_STATE_ERROR) { + return SSH_ERROR; + } + if (channel->state == SSH_CHANNEL_STATE_CLOSED) { + ssh_set_error(session, + SSH_FATAL, + "Remote channel is closed."); + return SSH_ERROR; + } + if (channel->remote_eof && ssh_buffer_get_len(stdbuf) == 0) { + return 0; + } + len = ssh_buffer_get_len(stdbuf); + /* Read count bytes if len is greater, everything otherwise */ + len = (len > count ? count : len); + memcpy(dest, ssh_buffer_get(stdbuf), len); + ssh_buffer_pass_bytes(stdbuf,len); + 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; + } + } + + return len; +} + +/** + * @brief Do a nonblocking read on the channel. + * + * A nonblocking read on the specified channel. it will return <= count bytes of + * data read atomically. + * + * @param[in] channel The channel to read from. + * + * @param[in] 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. + * + * @see ssh_channel_is_eof() + */ +int ssh_channel_read_nonblocking(ssh_channel channel, + void *dest, + uint32_t count, + int is_stderr) +{ + ssh_session session; + ssize_t to_read; + int rc; + int blocking; + + if(channel == NULL) { + return SSH_ERROR; + } + if(dest == NULL) { + ssh_set_error_invalid(channel->session); + return SSH_ERROR; + } + + session = channel->session; + + to_read = ssh_channel_poll(channel, is_stderr); + + if (to_read <= 0) { + if (session->session_state == SSH_SESSION_STATE_ERROR){ + return SSH_ERROR; + } + + return to_read; /* may be an error code */ + } + + if ((size_t)to_read > count) { + to_read = (ssize_t)count; + } + blocking = ssh_is_blocking(session); + ssh_set_blocking(session, 0); + rc = ssh_channel_read(channel, dest, (uint32_t)to_read, is_stderr); + ssh_set_blocking(session,blocking); + + return rc; +} + +/** + * @brief Polls a channel for data to read. + * + * @param[in] channel The channel to poll. + * + * @param[in] is_stderr A boolean to select the stderr stream. + * + * @return The number of bytes available for reading, 0 if nothing + * is available or SSH_ERROR on error. + * + * @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){ + ssh_buffer stdbuf; + + if(channel == NULL) { + return SSH_ERROR; + } + + stdbuf = channel->stdout_buffer; + + if (is_stderr) { + stdbuf = channel->stderr_buffer; + } + + if (ssh_buffer_get_len(stdbuf) == 0 && channel->remote_eof == 0) { + if (channel->session->session_state == SSH_SESSION_STATE_ERROR){ + return SSH_ERROR; + } + if (ssh_handle_packets(channel->session, SSH_TIMEOUT_NONBLOCKING)==SSH_ERROR) { + return SSH_ERROR; + } + } + + if (ssh_buffer_get_len(stdbuf) > 0){ + return ssh_buffer_get_len(stdbuf); + } + + if (channel->remote_eof) { + return SSH_EOF; + } + + return ssh_buffer_get_len(stdbuf); +} + +/** + * @brief Polls a channel for data to read, waiting for a certain timeout. + * + * @param[in] channel The channel to poll. + * @param[in] timeout Set an upper limit on the time for which this function + * will block, in milliseconds. Specifying a negative value + * means an infinite timeout. This parameter is passed to + * the poll() function. + * @param[in] is_stderr A boolean to select the stderr stream. + * + * @return The number of bytes available for reading, + * 0 if nothing is available (timeout elapsed), + * SSH_EOF on end of file, + * SSH_ERROR on error. + * + * @warning When the channel is in EOF state, the function returns SSH_EOF. + * + * @see ssh_channel_is_eof() + */ +int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr) +{ + ssh_session session; + ssh_buffer stdbuf; + struct ssh_channel_read_termination_struct ctx; + size_t len; + int rc; + + if(channel == NULL) { + return SSH_ERROR; + } + + session = channel->session; + stdbuf = channel->stdout_buffer; + + if (is_stderr) { + stdbuf = channel->stderr_buffer; + } + ctx.buffer = stdbuf; + ctx.channel = channel; + ctx.count = 1; + rc = ssh_handle_packets_termination(channel->session, + timeout, + ssh_channel_read_termination, + &ctx); + if (rc == SSH_ERROR || + session->session_state == SSH_SESSION_STATE_ERROR) { + rc = SSH_ERROR; + goto out; + } + len = ssh_buffer_get_len(stdbuf); + if (len > 0) { + if (len > INT_MAX) { + rc = SSH_ERROR; + } else { + rc = (int)len; + } + goto out; + } + if (channel->remote_eof) { + rc = SSH_EOF; + } + +out: + return rc; +} + +/** + * @brief Recover the session in which belongs a channel. + * + * @param[in] channel The channel to recover the session from. + * + * @return The session pointer. + */ +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){ + ssh_channel channel = c; + if(channel->exit_status != -1 || + /* When a channel is closed, no exit status message can + * come anymore */ + (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) || + channel->session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +/** + * @brief Get the exit status of the channel (error code from the executed + * instruction). + * + * @param[in] channel The channel to get the status from. + * + * @return The exit status, -1 if no exit status has been returned + * (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. + * + * 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 rc; + if(channel == NULL) { + return SSH_ERROR; + } + rc = ssh_handle_packets_termination(channel->session, + SSH_TIMEOUT_DEFAULT, + ssh_channel_exit_status_termination, + channel); + if (rc == SSH_ERROR || channel->session->session_state == + SSH_SESSION_STATE_ERROR) + return SSH_ERROR; + return channel->exit_status; +} + +/* + * This function acts as a meta select. + * + * First, channels are analyzed to seek potential can-write or can-read ones, + * then if no channel has been elected, it goes in a loop with the posix + * select(2). + * 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) { + ssh_channel chan; + int i; + int j = 0; + + for (i = 0; rchans[i] != NULL; i++) { + chan = rchans[i]; + + while (ssh_channel_is_open(chan) && ssh_socket_data_available(chan->session->socket)) { + ssh_handle_packets(chan->session, SSH_TIMEOUT_NONBLOCKING); + } + + if ((chan->stdout_buffer && ssh_buffer_get_len(chan->stdout_buffer) > 0) || + (chan->stderr_buffer && ssh_buffer_get_len(chan->stderr_buffer) > 0) || + chan->remote_eof) { + rout[j] = chan; + j++; + } + } + rout[j] = NULL; + + j = 0; + for(i = 0; wchans[i] != NULL; i++) { + chan = wchans[i]; + /* It's not our business to seek if the file descriptor is writable */ + if (ssh_socket_data_writable(chan->session->socket) && + ssh_channel_is_open(chan) && (chan->remote_window > 0)) { + wout[j] = chan; + j++; + } + } + wout[j] = NULL; + + j = 0; + for (i = 0; echans[i] != NULL; i++) { + chan = echans[i]; + + if (!ssh_socket_is_open(chan->session->socket) || ssh_channel_is_closed(chan)) { + eout[j] = chan; + j++; + } + } + eout[j] = NULL; + + return 0; +} + +/* Just count number of pointers in the array */ +static size_t count_ptrs(ssh_channel *ptrs) +{ + size_t c; + for (c = 0; ptrs[c] != NULL; c++) + ; + + return c; +} + +/** + * @brief Act like the standard select(2) on channels. + * + * The list of pointers are then actualized and will only contain pointers to + * channels that are respectively readable, writable or have an exception to + * trap. + * + * @param[in] readchans A NULL pointer or an array of channel pointers, + * terminated by a NULL. + * + * @param[in] writechans A NULL pointer or an array of channel pointers, + * terminated by a NULL. + * + * @param[in] exceptchans A NULL pointer or an array of channel pointers, + * terminated by a NULL. + * + * @param[in] timeout Timeout as defined by select(2). + * + * @return SSH_OK on a successful operation, SSH_EINTR if the + * select(2) syscall was interrupted, then relaunch the + * function, or SSH_ERROR on error. + */ +int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, + ssh_channel *exceptchans, struct timeval * timeout) { + ssh_channel *rchans, *wchans, *echans; + ssh_channel dummy = NULL; + ssh_event event = NULL; + int rc; + int i; + int tm, tm_base; + int firstround=1; + struct ssh_timestamp ts; + + if (timeout != NULL) + tm_base = timeout->tv_sec * 1000 + timeout->tv_usec/1000; + else + tm_base = SSH_TIMEOUT_INFINITE; + ssh_timestamp_init(&ts); + tm = tm_base; + /* don't allow NULL pointers */ + if (readchans == NULL) { + readchans = &dummy; + } + + if (writechans == NULL) { + writechans = &dummy; + } + + if (exceptchans == NULL) { + exceptchans = &dummy; + } + + if (readchans[0] == NULL && writechans[0] == NULL && exceptchans[0] == NULL) { + /* No channel to poll?? Go away! */ + return 0; + } + + /* Prepare the outgoing temporary arrays */ + rchans = calloc(count_ptrs(readchans) + 1, sizeof(ssh_channel)); + if (rchans == NULL) { + return SSH_ERROR; + } + + wchans = calloc(count_ptrs(writechans) + 1, sizeof(ssh_channel)); + if (wchans == NULL) { + SAFE_FREE(rchans); + return SSH_ERROR; + } + + echans = calloc(count_ptrs(exceptchans) + 1, sizeof(ssh_channel)); + if (echans == NULL) { + SAFE_FREE(rchans); + SAFE_FREE(wchans); + return SSH_ERROR; + } + + /* + * First, try without doing network stuff then, use the ssh_poll + * infrastructure to poll on all sessions. + */ + do { + channel_protocol_select(readchans, writechans, exceptchans, + rchans, wchans, echans); + if (rchans[0] != NULL || wchans[0] != NULL || echans[0] != NULL) { + /* At least one channel has an event */ + break; + } + /* Add all channels' sessions right into an event object */ + if (event == NULL) { + event = ssh_event_new(); + if (event == NULL) { + SAFE_FREE(rchans); + SAFE_FREE(wchans); + SAFE_FREE(echans); + + return SSH_ERROR; + } + for (i = 0; readchans[i] != NULL; i++) { + ssh_poll_get_default_ctx(readchans[i]->session); + ssh_event_add_session(event, readchans[i]->session); + } + for (i = 0; writechans[i] != NULL; i++) { + ssh_poll_get_default_ctx(writechans[i]->session); + ssh_event_add_session(event, writechans[i]->session); + } + for (i = 0; exceptchans[i] != NULL; i++) { + ssh_poll_get_default_ctx(exceptchans[i]->session); + ssh_event_add_session(event, exceptchans[i]->session); + } + } + /* Get out if the timeout has elapsed */ + if (!firstround && ssh_timeout_elapsed(&ts, tm_base)){ + break; + } + /* Here we go */ + rc = ssh_event_dopoll(event,tm); + if (rc != SSH_OK){ + SAFE_FREE(rchans); + SAFE_FREE(wchans); + SAFE_FREE(echans); + ssh_event_free(event); + return rc; + } + tm = ssh_timeout_update(&ts, tm_base); + 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 )); + SAFE_FREE(rchans); + SAFE_FREE(wchans); + SAFE_FREE(echans); + if(event) + ssh_event_free(event); + return 0; +} + +/** + * @brief Set the channel data counter. + * + * @code + * struct ssh_counter_struct counter = { + * .in_bytes = 0, + * .out_bytes = 0, + * .in_packets = 0, + * .out_packets = 0 + * }; + * + * ssh_channel_set_counter(channel, &counter); + * @endcode + * + * @param[in] channel The SSH channel. + * + * @param[in] counter Counter for bytes handled by the channel. + */ +void ssh_channel_set_counter(ssh_channel channel, + ssh_counter counter) { + if (channel != NULL) { + channel->counter = counter; + } +} + +/** + * @brief Blocking write on a channel stderr. + * + * @param[in] channel The channel to write to. + * + * @param[in] data A pointer to the data to write. + * + * @param[in] len The length of the buffer to write to. + * + * @return The number of bytes written, SSH_ERROR on error. + * + * @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); +} + +#if WITH_SERVER + +/** + * @brief Open a TCP/IP reverse forwarding channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] remotehost The remote host to connected (host name or IP). + * + * @param[in] remoteport The remote port. + * + * @param[in] sourcehost The source host (your local computer). It's optional + * and for logging purpose. + * + * @param[in] localport The source port (your local computer). It's optional + * and for logging purpose. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * 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. + */ +int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport) { + ssh_session session; + ssh_buffer payload = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return rc; + } + if(remotehost == NULL || sourcehost == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + + session = channel->session; + + if(channel->state != SSH_CHANNEL_STATE_NOT_OPEN) + goto pending; + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + rc = ssh_buffer_pack(payload, + "sdsd", + remotehost, + remoteport, + sourcehost, + localport); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + goto error; + } +pending: + rc = channel_open(channel, + "forwarded-tcpip", + CHANNEL_INITIAL_WINDOW, + CHANNEL_MAX_PACKET, + payload); + +error: + SSH_BUFFER_FREE(payload); + + return rc; +} + +/** + * @brief Open a X11 channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] orig_addr The source host (the local server). + * + * @param[in] orig_port The source port (the local server). + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * 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. + */ +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; + + if(channel == NULL) { + return rc; + } + if(orig_addr == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + session = channel->session; + + if(channel->state != SSH_CHANNEL_STATE_NOT_OPEN) + goto pending; + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(payload, + "sd", + orig_addr, + orig_port); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } +pending: + rc = channel_open(channel, + "x11", + CHANNEL_INITIAL_WINDOW, + CHANNEL_MAX_PACKET, + payload); + +error: + SSH_BUFFER_FREE(payload); + + return rc; +} + +/** + * @brief Send the exit status to the remote process + * + * 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) { + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "d", exit_status); + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = channel_request(channel, "exit-status", buffer, 0); +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +/** + * @brief Send an exit signal to remote process (RFC 4254, section 6.10). + * + * 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. + * + * @param[in] sig The signal to send (without SIG prefix) + * (e.g. "TERM" or "KILL"). + * @param[in] core A boolean to tell if a core was dumped + * @param[in] errmsg A CRLF explanation text about the error condition + * @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) { + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return rc; + } + if(sig == NULL || errmsg == NULL || lang == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, + "sbss", + sig, + core ? 1 : 0, + errmsg, + lang); + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = channel_request(channel, "exit-signal", buffer, 0); +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +#endif + +/* @} */ diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..ae170b9 --- /dev/null +++ b/src/client.c @@ -0,0 +1,760 @@ +/* + * client.c - SSH client functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 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 + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/options.h" +#include "libssh/socket.h" +#include "libssh/session.h" +#include "libssh/dh.h" +#ifdef WITH_GEX +#include "libssh/dh-gex.h" +#endif /* WITH_GEX */ +#include "libssh/ecdh.h" +#include "libssh/threads.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/kex.h" + +#define set_status(session, status) do {\ + if (session->common.callbacks && session->common.callbacks->connect_status_function) \ + session->common.callbacks->connect_status_function(session->common.callbacks->userdata, status); \ + } while (0) + +/** + * @internal + * @brief Callback to be called when the socket is connected or had a + * connection error. Changes the state of the session and updates the error + * message. + * @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){ + ssh_session session=(ssh_session)user; + + if (session->session_state != SSH_SESSION_STATE_CONNECTING && + session->session_state != SSH_SESSION_STATE_SOCKET_CONNECTED) + { + ssh_set_error(session,SSH_FATAL, "Wrong state in socket_callback_connected : %d", + session->session_state); + + return; + } + + SSH_LOG(SSH_LOG_RARE,"Socket connection callback: %d (%d)",code, errno_code); + if(code == SSH_SOCKET_CONNECTED_OK) + session->session_state=SSH_SESSION_STATE_SOCKET_CONNECTED; + else { + session->session_state=SSH_SESSION_STATE_ERROR; + ssh_set_error(session,SSH_FATAL,"%s",strerror(errno_code)); + } + session->ssh_connection_callback(session); +} + +/** + * @internal + * + * @brief Gets the banner from socket and saves it in session. + * Updates the session state + * + * @param data pointer to the beginning of header + * @param len size of the banner + * @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; + char *str = NULL; + size_t i; + int ret=0; + + if (session->session_state != SSH_SESSION_STATE_SOCKET_CONNECTED) { + ssh_set_error(session,SSH_FATAL, + "Wrong state in callback_receive_banner : %d", + session->session_state); + + return SSH_ERROR; + } + for (i = 0; i < len; ++i) { +#ifdef WITH_PCAP + if (session->pcap_ctx && buffer[i] == '\n') { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_IN, + buffer,i+1, + i+1); + } +#endif + if (buffer[i] == '\r') { + buffer[i] = '\0'; + } + if (buffer[i] == '\n') { + int cmp; + + buffer[i] = '\0'; + + /* The server MAY send other lines of data... */ + cmp = strncmp(buffer, "SSH-", 4); + if (cmp == 0) { + str = strdup(buffer); + if (str == NULL) { + return SSH_ERROR; + } + /* number of bytes read */ + ret = i + 1; + session->serverbanner = 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; + } else { + SSH_LOG(SSH_LOG_DEBUG, + "ssh_protocol_version_exchange: %s", + buffer); + ret = i + 1; + break; + } + } + /* According to RFC 4253 the max banner length is 255 */ + if (i > 255) { + /* Too big banner */ + session->session_state=SSH_SESSION_STATE_ERROR; + ssh_set_error(session, + SSH_FATAL, + "Receiving banner: too large banner"); + + return 0; + } + } + + return ret; +} + +/** @internal + * @brief Sends a SSH banner to the server. + * + * @param session The SSH session to use. + * + * @param server Send client or server banner. + * + * @return 0 on success, < 0 on error. + */ +int ssh_send_banner(ssh_session session, int server) +{ + const char *banner = CLIENT_BANNER_SSH2; + const char *terminator = "\r\n"; + /* The maximum banner length is 255 for SSH2 */ + char buffer[256] = {0}; + size_t len; + int rc = SSH_ERROR; + + if (server == 1) { + if (session->opts.custombanner == NULL){ + session->serverbanner = strdup(banner); + if (session->serverbanner == NULL) { + goto end; + } + } else { + len = strlen(session->opts.custombanner); + session->serverbanner = malloc(len + 8 + 1); + if(session->serverbanner == NULL) { + goto end; + } + snprintf(session->serverbanner, + len + 8 + 1, + "SSH-2.0-%s", + session->opts.custombanner); + } + + snprintf(buffer, + sizeof(buffer), + "%s%s", + session->serverbanner, + terminator); + } else { + session->clientbanner = strdup(banner); + if (session->clientbanner == NULL) { + goto end; + } + + snprintf(buffer, + sizeof(buffer), + "%s%s", + session->clientbanner, + terminator); + } + + rc = ssh_socket_write(session->socket, buffer, strlen(buffer)); + if (rc == SSH_ERROR) { + goto end; + } +#ifdef WITH_PCAP + if (session->pcap_ctx != NULL) { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_OUT, + buffer, + strlen(buffer), + strlen(buffer)); + } +#endif + + rc = SSH_OK; +end: + return rc; +} + +/** @internal + * @brief launches the DH handshake state machine + * @param session session handle + * @returns SSH_OK or SSH_ERROR + * @warning this function returning is no proof that DH handshake is + * completed + */ +static int dh_handshake(ssh_session session) { + + int rc = SSH_AGAIN; + + switch (session->dh_handshake_state) { + case DH_STATE_INIT: + 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: + rc = ssh_client_dh_init(session); + break; +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + rc = ssh_client_dhgex_init(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: + rc = ssh_client_ecdh_init(session); + break; +#endif +#ifdef HAVE_CURVE25519 + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + rc = ssh_client_curve25519_init(session); + break; +#endif + default: + rc = SSH_ERROR; + } + + break; + case DH_STATE_INIT_SENT: + /* wait until ssh_packet_dh_reply is called */ + break; + case DH_STATE_NEWKEYS_SENT: + /* wait until ssh_packet_newkeys is called */ + break; + case DH_STATE_FINISHED: + return SSH_OK; + default: + ssh_set_error(session, SSH_FATAL, "Invalid state in dh_handshake(): %d", + session->dh_handshake_state); + + return SSH_ERROR; + } + + 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; +} + +/** + * @internal + * + * @brief Request a service from the SSH server. + * + * Service requests are for example: ssh-userauth, ssh-connection, etc. + * + * @param session The session to use to ask for a service request. + * @param service The service request. + * + * @return SSH_OK on success + * @return SSH_ERROR on error + * @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; + + if(session->auth.service_state != SSH_AUTH_SERVICE_NONE) + goto pending; + + rc = ssh_buffer_pack(session->out_buffer, + "bs", + SSH2_MSG_SERVICE_REQUEST, + service); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + return SSH_ERROR; + } + session->auth.service_state = SSH_AUTH_SERVICE_SENT; + if (ssh_packet_send(session) == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, + "Sending SSH2_MSG_SERVICE_REQUEST failed."); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sent SSH_MSG_SERVICE_REQUEST (service %s)", service); +pending: + rc=ssh_handle_packets_termination(session,SSH_TIMEOUT_USER, + ssh_service_request_termination, session); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } + switch(session->auth.service_state) { + case SSH_AUTH_SERVICE_DENIED: + ssh_set_error(session,SSH_FATAL,"ssh_auth_service request denied"); + break; + case SSH_AUTH_SERVICE_ACCEPTED: + rc=SSH_OK; + break; + case SSH_AUTH_SERVICE_SENT: + rc=SSH_AGAIN; + break; + case SSH_AUTH_SERVICE_NONE: + rc=SSH_ERROR; + break; + } + + return rc; +} + +/** + * @addtogroup libssh_session + * + * @{ + */ + +/** + * @internal + * + * @brief A function to be called each time a step has been done in the + * connection. + */ +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); + + 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); + + /* 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, 0); + 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: + 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; + +} + +/** @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){ + case SSH_SESSION_STATE_ERROR: + case SSH_SESSION_STATE_AUTHENTICATING: + case SSH_SESSION_STATE_DISCONNECTED: + return 1; + default: + return 0; + } +} + +/** + * @brief Connect to the ssh server. + * + * @param[in] session The ssh session to connect. + * + * @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. + * + * @see ssh_new() + * @see ssh_disconnect() + */ +int ssh_connect(ssh_session session) { + int ret; + + if (session == NULL) { + return SSH_ERROR; + } + + switch(session->pending_call_state){ + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_CONNECT: + goto pending; + default: + ssh_set_error(session,SSH_FATAL,"Bad call during pending SSH call in ssh_connect"); + + return SSH_ERROR; + } + session->alive = 0; + session->client = 1; + + if (session->opts.fd == SSH_INVALID_SOCKET && + session->opts.host == NULL && + session->opts.ProxyCommand == NULL) { + ssh_set_error(session, SSH_FATAL, "Hostname required"); + return SSH_ERROR; + } + + /* If the system configuration files were not yet processed, do it now */ + if (!session->opts.config_processed) { + ret = ssh_options_parse_config(session, NULL); + if (ret != 0) { + ssh_set_error(session, SSH_FATAL, + "Failed to process system configuration files"); + return SSH_ERROR; + } + } + + ret = ssh_options_apply(session); + if (ret < 0) { + ssh_set_error(session, SSH_FATAL, "Couldn't apply options"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, + "libssh %s, using threading %s", + ssh_copyright(), + ssh_threads_get_type()); + + session->ssh_connection_callback = ssh_client_connection_callback; + session->session_state=SSH_SESSION_STATE_CONNECTING; + ssh_socket_set_callbacks(session->socket,&session->socket_callbacks); + session->socket_callbacks.connected=socket_callback_connected; + session->socket_callbacks.data=callback_receive_banner; + session->socket_callbacks.exception=ssh_socket_exception_callback; + session->socket_callbacks.userdata=session; + if (session->opts.fd != SSH_INVALID_SOCKET) { + session->session_state=SSH_SESSION_STATE_SOCKET_CONNECTED; + ssh_socket_set_fd(session->socket, session->opts.fd); + ret=SSH_OK; +#ifndef _WIN32 + } else if (session->opts.ProxyCommand != NULL){ + ret = ssh_socket_connect_proxycommand(session->socket, + session->opts.ProxyCommand); +#endif + } else { + ret=ssh_socket_connect(session->socket, + session->opts.host, + session->opts.port > 0 ? session->opts.port : 22, + session->opts.bindaddr); + } + if (ret == SSH_ERROR) { + return SSH_ERROR; + } + + set_status(session, 0.2f); + + session->alive = 1; + SSH_LOG(SSH_LOG_PROTOCOL,"Socket connecting, now waiting for the callbacks to work"); +pending: + session->pending_call_state=SSH_PENDING_CALL_CONNECT; + if(ssh_is_blocking(session)) { + int timeout = (session->opts.timeout * 1000) + + (session->opts.timeout_usec / 1000); + if (timeout == 0) { + timeout = 10 * 1000; + } + SSH_LOG(SSH_LOG_PACKET,"Actual timeout : %d", timeout); + ret = ssh_handle_packets_termination(session, timeout, ssh_connect_termination, session); + if (session->session_state != SSH_SESSION_STATE_ERROR && + (ret == SSH_ERROR || !ssh_connect_termination(session))) { + ssh_set_error(session, SSH_FATAL, + "Timeout connecting to %s", session->opts.host); + session->session_state = SSH_SESSION_STATE_ERROR; + } + } + else { + ret = ssh_handle_packets_termination(session, + SSH_TIMEOUT_NONBLOCKING, + ssh_connect_termination, + session); + if (ret == SSH_ERROR) { + session->session_state = SSH_SESSION_STATE_ERROR; + } + } + SSH_LOG(SSH_LOG_PACKET,"current state : %d",session->session_state); + if(!ssh_is_blocking(session) && !ssh_connect_termination(session)){ + return SSH_AGAIN; + } + + session->pending_call_state=SSH_PENDING_CALL_NONE; + if(session->session_state == SSH_SESSION_STATE_ERROR || session->session_state == SSH_SESSION_STATE_DISCONNECTED) + return SSH_ERROR; + return SSH_OK; +} + +/** + * @brief Get the issue banner from the server. + * + * This is the banner showing a disclaimer to users who log in, + * typically their right or the fact that they will be monitored. + * + * @param[in] session The SSH session to use. + * + * @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; + } + + return ssh_string_to_char(session->banner); +} + +/** + * @brief Get the version of the OpenSSH server, if it is not an OpenSSH server + * then 0 will be returned. + * + * You can use the SSH_VERSION_INT macro to compare version numbers. + * + * @param[in] session The SSH session to use. + * + * @return The version number if available, 0 otherwise. + * + * @code + * int openssh = ssh_get_openssh_version(); + * + * if (openssh == SSH_INT_VERSION(6, 1, 0)) { + * printf("Version match!\m"); + * } + * @endcode + */ +int ssh_get_openssh_version(ssh_session session) { + if (session == NULL) { + return 0; + } + + return session->openssh; +} + +/** + * @brief Disconnect from a session (client or server). + * The session can then be reused to open a new session. + * + * @param[in] session The SSH session to use. + */ +void ssh_disconnect(ssh_session session) { + struct ssh_iterator *it; + int rc; + + 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; + } + + 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; + + 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); + } + } + 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->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 " + "Aris Adamantiadis, Andreas Schneider " + "and libssh contributors. " + "Distributed under the LGPL, please refer to COPYING " + "file for information about your rights"; +} +/** @} */ diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..a08e41b --- /dev/null +++ b/src/config.c @@ -0,0 +1,1040 @@ +/* + * config.c - parse the ssh config file + * + * This file is part of the SSH Library + * + * Copyright (c) 2009-2013 by Andreas Schneider + * + * 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 +#include +#include +#include +#ifdef HAVE_GLOB_H +# include +#endif +#include +#include + +#include "libssh/config_parser.h" +#include "libssh/config.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/options.h" + +#define MAX_LINE_SIZE 1024 + +struct ssh_config_keyword_table_s { + const char *name; + enum ssh_config_opcode_e opcode; +}; + +static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { + { "host", SOC_HOST }, + { "match", SOC_MATCH }, + { "hostname", SOC_HOSTNAME }, + { "port", SOC_PORT }, + { "user", SOC_USERNAME }, + { "identityfile", SOC_IDENTITY }, + { "ciphers", SOC_CIPHERS }, + { "macs", SOC_MACS }, + { "compression", SOC_COMPRESSION }, + { "connecttimeout", SOC_TIMEOUT }, + { "protocol", SOC_PROTOCOL }, + { "stricthostkeychecking", SOC_STRICTHOSTKEYCHECK }, + { "userknownhostsfile", SOC_KNOWNHOSTS }, + { "proxycommand", SOC_PROXYCOMMAND }, + { "gssapiserveridentity", SOC_GSSAPISERVERIDENTITY }, + { "gssapiclientidentity", SOC_GSSAPICLIENTIDENTITY }, + { "gssapidelegatecredentials", SOC_GSSAPIDELEGATECREDENTIALS }, + { "include", SOC_INCLUDE }, + { "bindaddress", SOC_BINDADDRESS}, + { "globalknownhostsfile", SOC_GLOBALKNOWNHOSTSFILE}, + { "loglevel", SOC_LOGLEVEL}, + { "hostkeyalgorithms", SOC_HOSTKEYALGORITHMS}, + { "kexalgorithms", SOC_KEXALGORITHMS}, + { "mac", SOC_UNSUPPORTED}, /* SSHv1 */ + { "gssapiauthentication", SOC_GSSAPIAUTHENTICATION}, + { "kbdinteractiveauthentication", SOC_KBDINTERACTIVEAUTHENTICATION}, + { "passwordauthentication", SOC_PASSWORDAUTHENTICATION}, + { "pubkeyauthentication", SOC_PUBKEYAUTHENTICATION}, + { "addkeystoagent", SOC_UNSUPPORTED}, + { "addressfamily", SOC_UNSUPPORTED}, + { "batchmode", SOC_UNSUPPORTED}, + { "canonicaldomains", SOC_UNSUPPORTED}, + { "canonicalizefallbacklocal", SOC_UNSUPPORTED}, + { "canonicalizehostname", SOC_UNSUPPORTED}, + { "canonicalizemaxdots", SOC_UNSUPPORTED}, + { "canonicalizepermittedcnames", SOC_UNSUPPORTED}, + { "certificatefile", SOC_UNSUPPORTED}, + { "challengeresponseauthentication", 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}, + { "hostkeyalias", SOC_UNSUPPORTED}, + { "identitiesonly", SOC_UNSUPPORTED}, + { "identityagent", SOC_UNSUPPORTED}, + { "ipqos", SOC_UNSUPPORTED}, + { "kbdinteractivedevices", SOC_UNSUPPORTED}, + { "nohostauthenticationforlocalhost", SOC_UNSUPPORTED}, + { "numberofpasswordprompts", SOC_UNSUPPORTED}, + { "pkcs11provider", SOC_UNSUPPORTED}, + { "preferredauthentications", SOC_UNSUPPORTED}, + { "proxyjump", SOC_PROXYJUMP}, + { "proxyusefdpass", SOC_UNSUPPORTED}, + { "pubkeyacceptedtypes", SOC_PUBKEYACCEPTEDTYPES}, + { "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}, + { "streamlocalbindunlink", SOC_UNSUPPORTED}, + { "syslogfacility", SOC_UNSUPPORTED}, + { "tcpkeepalive", SOC_UNSUPPORTED}, + { "updatehostkeys", SOC_UNSUPPORTED}, + { "useprivilegedport", SOC_UNSUPPORTED}, + { "verifyhostkeydns", SOC_UNSUPPORTED}, + { "visualhostkey", SOC_UNSUPPORTED}, + { "clearallforwardings", SOC_NA}, + { "controlmaster", SOC_NA}, + { "controlpersist", SOC_NA}, + { "controlpath", SOC_NA}, + { "dynamicforward", SOC_NA}, + { "escapechar", SOC_NA}, + { "exitonforwardfailure", SOC_NA}, + { "forwardx11", SOC_NA}, + { "forwardx11timeout", SOC_NA}, + { "forwardx11trusted", SOC_NA}, + { "gatewayports", SOC_NA}, + { "ignoreunknown", SOC_NA}, + { "localcommand", SOC_NA}, + { "localforward", SOC_NA}, + { "permitlocalcommand", SOC_NA}, + { "remoteforward", SOC_NA}, + { "requesttty", SOC_NA}, + { "sendenv", SOC_NA}, + { "tunnel", SOC_NA}, + { "tunneldevice", SOC_NA}, + { "xauthlocation", SOC_NA}, + { "pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDTYPES}, + { NULL, SOC_UNKNOWN } +}; + +enum ssh_config_match_e { + MATCH_UNKNOWN = -1, + MATCH_ALL, + MATCH_FINAL, + MATCH_CANONICAL, + MATCH_EXEC, + MATCH_HOST, + MATCH_ORIGINALHOST, + MATCH_USER, + MATCH_LOCALUSER +}; + +struct ssh_config_match_keyword_table_s { + const char *name; + enum ssh_config_match_e opcode; +}; + +static struct ssh_config_match_keyword_table_s ssh_config_match_keyword_table[] = { + { "all", MATCH_ALL }, + { "canonical", MATCH_CANONICAL }, + { "final", MATCH_FINAL }, + { "exec", MATCH_EXEC }, + { "host", MATCH_HOST }, + { "originalhost", MATCH_ORIGINALHOST }, + { "user", MATCH_USER }, + { "localuser", MATCH_LOCALUSER }, + { NULL, MATCH_UNKNOWN }, +}; + +static int ssh_config_parse_line(ssh_session session, const char *line, + unsigned int count, int *parsing); + +static enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) { + int i; + + for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) { + if (strcasecmp(keyword, ssh_config_keyword_table[i].name) == 0) { + return ssh_config_keyword_table[i].opcode; + } + } + + return SOC_UNKNOWN; +} + +static void +local_parse_file(ssh_session session, + const char *filename, + int *parsing) +{ + FILE *f; + char line[MAX_LINE_SIZE] = {0}; + unsigned int count = 0; + int rv; + + f = fopen(filename, "r"); + if (f == NULL) { + SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", + filename); + return; + } + + 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); + if (rv < 0) { + fclose(f); + return; + } + } + + fclose(f); + return; +} + +#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) +static void local_parse_glob(ssh_session session, + const char *fileglob, + int *parsing) +{ + glob_t globbuf = { + .gl_flags = 0, + }; + int rt; + size_t i; + + rt = glob(fileglob, GLOB_TILDE, NULL, &globbuf); + if (rt == GLOB_NOMATCH) { + globfree(&globbuf); + return; + } else if (rt != 0) { + SSH_LOG(SSH_LOG_RARE, "Glob error: %s", + fileglob); + globfree(&globbuf); + return; + } + + for (i = 0; i < globbuf.gl_pathc; i++) { + local_parse_file(session, globbuf.gl_pathv[i], parsing); + } + + globfree(&globbuf); +} +#endif /* HAVE_GLOB HAVE_GLOB_GL_FLAGS_MEMBER */ + +static enum ssh_config_match_e +ssh_config_get_match_opcode(const char *keyword) +{ + size_t i; + + for (i = 0; ssh_config_match_keyword_table[i].name != NULL; i++) { + if (strcasecmp(keyword, ssh_config_match_keyword_table[i].name) == 0) { + return ssh_config_match_keyword_table[i].opcode; + } + } + + return MATCH_UNKNOWN; +} + +static int +ssh_config_match(char *value, const char *pattern, bool negate) +{ + int ok, result = 0; + + ok = match_pattern_list(value, pattern, strlen(pattern), 0); + if (ok <= 0 && negate == true) { + result = 1; + } else if (ok > 0 && negate == false) { + result = 1; + } + SSH_LOG(SSH_LOG_TRACE, "%s '%s' against pattern '%s'%s (ok=%d)", + result == 1 ? "Matched" : "Not matched", value, pattern, + negate == true ? " (negated)" : "", ok); + return result; +} + +/* @brief: Parse the ProxyJump configuration line and if parsing, + * stores the result in the configuration option + */ +static int +ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) +{ + char *c = NULL, *cp = NULL, *endp = NULL; + char *username = NULL; + char *hostname = NULL; + char *port = NULL; + char *next = NULL; + int cmp, rv = SSH_ERROR; + bool parse_entry = do_parsing; + + /* Special value none disables the proxy */ + cmp = strcasecmp(s, "none"); + if (cmp == 0 && do_parsing) { + ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, s); + return SSH_OK; + } + + /* This is comma-separated list of [user@]host[:port] entries */ + c = strdup(s); + if (c == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + cp = c; + do { + endp = strchr(cp, ','); + if (endp != NULL) { + /* Split out the token */ + *endp = '\0'; + } + if (parse_entry) { + /* We actually care only about the first item */ + rv = ssh_config_parse_uri(cp, &username, &hostname, &port); + /* The rest of the list needs to be passed on */ + if (endp != NULL) { + next = strdup(endp + 1); + if (next == NULL) { + ssh_set_error_oom(session); + rv = SSH_ERROR; + } + } + } else { + /* The rest is just sanity-checked to avoid failures later */ + rv = ssh_config_parse_uri(cp, NULL, NULL, NULL); + } + if (rv != SSH_OK) { + goto out; + } + parse_entry = 0; + if (endp != NULL) { + cp = endp + 1; + } else { + cp = NULL; /* end */ + } + } while (cp != NULL); + + 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", + username ? " -l " : "", + username ? username : "", + port ? " -p " : "", + port ? port : "", + next ? " -J " : "", + next ? next : "", + hostname); + if (rv < 0 || rv >= (int)sizeof(com)) { + SSH_LOG(SSH_LOG_WARN, "Too long ProxyJump configuration line"); + rv = SSH_ERROR; + goto out; + } + ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, com); + } + rv = SSH_OK; + +out: + SAFE_FREE(username); + SAFE_FREE(hostname); + SAFE_FREE(port); + SAFE_FREE(next); + SAFE_FREE(c); + return rv; +} + +static int +ssh_config_parse_line(ssh_session session, + const char *line, + unsigned int count, + int *parsing) +{ + enum ssh_config_opcode_e opcode; + const char *p = NULL, *p2 = NULL; + char *s = NULL, *x = NULL; + char *keyword = NULL; + char *lowerhost = NULL; + size_t len; + int i, rv; + uint8_t *seen = session->opts.options_seen; + long l; + int64_t ll; + + /* Ignore empty lines */ + if (line == NULL || *line == '\0') { + return 0; + } + + x = s = strdup(line); + if (s == NULL) { + ssh_set_error_oom(session); + return -1; + } + + /* Remove trailing spaces */ + for (len = strlen(s) - 1; len > 0; len--) { + if (! isspace(s[len])) { + break; + } + s[len] = '\0'; + } + + keyword = ssh_config_get_token(&s); + if (keyword == NULL || *keyword == '#' || + *keyword == '\0' || *keyword == '\n') { + SAFE_FREE(x); + return 0; + } + + opcode = ssh_config_get_opcode(keyword); + if (*parsing == 1 && + opcode != SOC_HOST && + opcode != SOC_MATCH && + opcode != SOC_INCLUDE && + opcode > SOC_UNSUPPORTED) { /* Ignore all unknown types here */ + /* Skip all the options that were already applied */ + if (seen[opcode] != 0) { + SAFE_FREE(x); + return 0; + } + seen[opcode] = 1; + } + + switch (opcode) { + case SOC_INCLUDE: /* recursive include of other files */ + + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { +#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) + local_parse_glob(session, p, parsing); +#else + local_parse_file(session, p, parsing); +#endif /* HAVE_GLOB */ + } + break; + + case SOC_MATCH: { + bool negate; + int result = 1; + size_t args = 0; + enum ssh_config_match_e opt; + char *localuser = NULL; + + *parsing = 0; + do { + p = p2 = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + break; + } + args++; + SSH_LOG(SSH_LOG_TRACE, "line %d: Processing Match keyword '%s'", + count, p); + + /* If the option is prefixed with ! the result should be negated */ + negate = false; + if (p[0] == '!') { + negate = true; + p++; + } + + opt = ssh_config_get_match_opcode(p); + switch (opt) { + case MATCH_ALL: + p = ssh_config_get_str_tok(&s, NULL); + if (args <= 2 && (p == NULL || p[0] == '\0')) { + /* The first or second, but last argument. The "all" keyword + * can be prefixed with either "final" or "canonical" + * keywords which do not have any effect here. */ + if (negate == true) { + result = 0; + } + break; + } + + ssh_set_error(session, SSH_FATAL, + "line %d: ERROR - Match all cannot be combined with " + "other Match attributes", count); + SAFE_FREE(x); + return -1; + + case MATCH_FINAL: + case MATCH_CANONICAL: + SSH_LOG(SSH_LOG_WARN, + "line %d: Unsupported Match keyword '%s', skipping", + count, + p); + /* Not set any result here -- the result is dependent on the + * following matches after this keyword */ + break; + + case MATCH_EXEC: + /* Skip to the end of line as unsupported */ + p = ssh_config_get_cmd(&s); + if (p == NULL || p[0] == '\0') { + SSH_LOG(SSH_LOG_WARN, "line %d: Match keyword " + "'%s' requires argument", count, p2); + SAFE_FREE(x); + return -1; + } + args++; + SSH_LOG(SSH_LOG_WARN, + "line %d: Unsupported Match keyword '%s', ignoring", + count, + p2); + result = 0; + break; + + case MATCH_LOCALUSER: + /* Here we match only one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + ssh_set_error(session, SSH_FATAL, + "line %d: ERROR - Match user keyword " + "requires argument", count); + SAFE_FREE(x); + return -1; + } + localuser = ssh_get_local_username(); + if (localuser == NULL) { + SSH_LOG(SSH_LOG_WARN, "line %d: Can not get local username " + "for conditional matching.", count); + SAFE_FREE(x); + return -1; + } + result &= ssh_config_match(localuser, p, negate); + SAFE_FREE(localuser); + args++; + break; + + case MATCH_ORIGINALHOST: + /* 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 " + "'%s' requires argument", count, p2); + SAFE_FREE(x); + return -1; + } + args++; + SSH_LOG(SSH_LOG_WARN, + "line %d: Unsupported Match keyword '%s', ignoring", + count, + p2); + result = 0; + break; + + case MATCH_HOST: + /* Here we match only one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + ssh_set_error(session, SSH_FATAL, + "line %d: ERROR - Match host keyword " + "requires argument", count); + SAFE_FREE(x); + return -1; + } + result &= ssh_config_match(session->opts.host, p, negate); + args++; + break; + + case MATCH_USER: + /* Here we match only one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + ssh_set_error(session, SSH_FATAL, + "line %d: ERROR - Match user keyword " + "requires argument", count); + SAFE_FREE(x); + return -1; + } + result &= ssh_config_match(session->opts.username, p, negate); + args++; + break; + + case MATCH_UNKNOWN: + default: + ssh_set_error(session, SSH_FATAL, + "ERROR - Unknown argument '%s' for Match keyword", p); + SAFE_FREE(x); + return -1; + } + } while (p != NULL && p[0] != '\0'); + if (args == 0) { + ssh_set_error(session, SSH_FATAL, + "ERROR - Match keyword requires an argument"); + SAFE_FREE(x); + return -1; + } + *parsing = result; + break; + } + case SOC_HOST: { + int ok = 0, result = -1; + + *parsing = 0; + lowerhost = (session->opts.host) ? ssh_lowercase(session->opts.host) : NULL; + for (p = ssh_config_get_str_tok(&s, NULL); + p != NULL && p[0] != '\0'; + p = ssh_config_get_str_tok(&s, NULL)) { + if (ok >= 0) { + ok = match_hostname(lowerhost, p, strlen(p)); + if (result == -1 && ok < 0) { + result = 0; + } else if (result == -1 && ok > 0) { + result = 1; + } + } + } + SAFE_FREE(lowerhost); + if (result != -1) { + *parsing = result; + } + break; + } + case SOC_HOSTNAME: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + char *z = ssh_path_expand_escape(session, p); + if (z == NULL) { + z = strdup(p); + } + ssh_options_set(session, SSH_OPTIONS_HOST, z); + free(z); + } + break; + case SOC_PORT: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_PORT_STR, p); + } + break; + case SOC_USERNAME: + if (session->opts.username == NULL) { + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_USER, p); + } + } + break; + case SOC_IDENTITY: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, p); + } + break; + case SOC_CIPHERS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, p); + ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, p); + } + break; + case SOC_MACS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, p); + ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, p); + } + break; + case SOC_COMPRESSION: + i = ssh_config_get_yesno(&s, -1); + if (i >= 0 && *parsing) { + if (i) { + ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes"); + } else { + ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no"); + } + } + 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) { + ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &l); + } + break; + case SOC_STRICTHOSTKEYCHECK: + i = ssh_config_get_yesno(&s, -1); + if (i >= 0 && *parsing) { + ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &i); + } + break; + case SOC_KNOWNHOSTS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, p); + } + break; + case SOC_PROXYCOMMAND: + p = ssh_config_get_cmd(&s); + /* We share the seen value with the ProxyJump */ + if (p && *parsing && !seen[SOC_PROXYJUMP]) { + ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, p); + } + break; + case SOC_PROXYJUMP: + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL) { + SAFE_FREE(x); + return -1; + } + /* We share the seen value with the ProxyCommand */ + rv = ssh_config_parse_proxy_jump(session, p, + (*parsing && !seen[SOC_PROXYCOMMAND])); + if (rv != SSH_OK) { + SAFE_FREE(x); + return -1; + } + break; + case SOC_GSSAPISERVERIDENTITY: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, p); + } + break; + case SOC_GSSAPICLIENTIDENTITY: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, p); + } + break; + case SOC_GSSAPIDELEGATECREDENTIALS: + i = ssh_config_get_yesno(&s, -1); + if (i >=0 && *parsing) { + ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &i); + } + break; + case SOC_BINDADDRESS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_BINDADDR, p); + } + break; + case SOC_GLOBALKNOWNHOSTSFILE: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, p); + } + break; + case SOC_LOGLEVEL: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + int value = -1; + + if (strcasecmp(p, "quiet") == 0) { + value = SSH_LOG_NONE; + } else if (strcasecmp(p, "fatal") == 0 || + strcasecmp(p, "error")== 0 || + strcasecmp(p, "info") == 0) { + value = SSH_LOG_WARN; + } else if (strcasecmp(p, "verbose") == 0) { + value = SSH_LOG_INFO; + } else if (strcasecmp(p, "DEBUG") == 0 || + strcasecmp(p, "DEBUG1") == 0) { + value = SSH_LOG_DEBUG; + } else if (strcasecmp(p, "DEBUG2") == 0 || + strcasecmp(p, "DEBUG3") == 0) { + value = SSH_LOG_TRACE; + } + if (value != -1) { + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &value); + } + } + break; + case SOC_HOSTKEYALGORITHMS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, p); + } + break; + case SOC_PUBKEYACCEPTEDTYPES: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, p); + } + break; + case SOC_KEXALGORITHMS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, p); + } + break; + case SOC_REKEYLIMIT: + /* Parse the data limit */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL) { + break; + } else if (strcmp(p, "default") == 0) { + /* Default rekey limits enforced automaticaly */ + 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"); + break; + } + switch (*endp) { + case 'G': + if (ll > LLONG_MAX / 1024) { + SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 1024; + FALL_THROUGH; + case 'M': + if (ll > LLONG_MAX / 1024) { + SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 1024; + FALL_THROUGH; + case 'K': + if (ll > LLONG_MAX / 1024) { + SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 1024; + endp++; + FALL_THROUGH; + case '\0': + /* just the number */ + break; + default: + /* Invalid suffix */ + ll = -1; + break; + } + if (*endp != ' ' && *endp != '\0') { + SSH_LOG(SSH_LOG_WARN, + "Invalid trailing characters after the rekey limit: %s", + endp); + break; + } + } + if (ll > -1 && *parsing) { + uint64_t v = (uint64_t)ll; + ssh_options_set(session, SSH_OPTIONS_REKEY_DATA, &v); + } + /* Parse the time limit */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL) { + break; + } else if (strcmp(p, "none") == 0) { + 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"); + break; + } + switch (*endp) { + case 'w': + case 'W': + if (ll > LLONG_MAX / 7) { + SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 7; + FALL_THROUGH; + case 'd': + case 'D': + if (ll > LLONG_MAX / 24) { + SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 24; + FALL_THROUGH; + case 'h': + case 'H': + if (ll > LLONG_MAX / 60) { + SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 60; + FALL_THROUGH; + case 'm': + case 'M': + if (ll > LLONG_MAX / 60) { + SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 60; + FALL_THROUGH; + case 's': + case 'S': + endp++; + FALL_THROUGH; + case '\0': + /* just the number */ + break; + default: + /* Invalid suffix */ + ll = -1; + break; + } + if (*endp != '\0') { + SSH_LOG(SSH_LOG_WARN, "Invalid trailing characters after the" + " rekey limit: %s", endp); + break; + } + } + if (ll > -1 && *parsing) { + uint32_t v = (uint32_t)ll; + ssh_options_set(session, SSH_OPTIONS_REKEY_TIME, &v); + } + break; + case SOC_GSSAPIAUTHENTICATION: + case SOC_KBDINTERACTIVEAUTHENTICATION: + case SOC_PASSWORDAUTHENTICATION: + case SOC_PUBKEYAUTHENTICATION: + i = ssh_config_get_yesno(&s, 0); + if (i>=0 && *parsing) { + switch(opcode){ + case SOC_GSSAPIAUTHENTICATION: + ssh_options_set(session, SSH_OPTIONS_GSSAPI_AUTH, &i); + break; + case SOC_KBDINTERACTIVEAUTHENTICATION: + ssh_options_set(session, SSH_OPTIONS_KBDINT_AUTH, &i); + break; + case SOC_PASSWORDAUTHENTICATION: + ssh_options_set(session, SSH_OPTIONS_PASSWORD_AUTH, &i); + break; + case SOC_PUBKEYAUTHENTICATION: + ssh_options_set(session, SSH_OPTIONS_PUBKEY_AUTH, &i); + break; + /* make gcc happy */ + default: + break; + } + } + break; + case SOC_NA: + SSH_LOG(SSH_LOG_INFO, "Unapplicable option: %s, line: %d", + keyword, count); + break; + case SOC_UNSUPPORTED: + SSH_LOG(SSH_LOG_RARE, "Unsupported option: %s, line: %d", + keyword, count); + break; + case SOC_UNKNOWN: + SSH_LOG(SSH_LOG_WARN, "Unknown option: %s, line: %d", + keyword, count); + break; + default: + ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d", + opcode); + SAFE_FREE(x); + return -1; + break; + } + + SAFE_FREE(x); + return 0; +} + +/* @brief Parse configuration file and set the options to the given session + * + * @params[in] session The ssh session + * @params[in] filename The path to the ssh configuration file + * + * @returns 0 on successful parsing the configuration file, -1 on error + */ +int ssh_config_parse_file(ssh_session session, const char *filename) +{ + char line[MAX_LINE_SIZE] = {0}; + unsigned int count = 0; + FILE *f; + int parsing, rv; + + f = fopen(filename, "r"); + if (f == NULL) { + return 0; + } + + 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); + if (rv < 0) { + fclose(f); + return -1; + } + } + + fclose(f); + return 0; +} diff --git a/src/config_parser.c b/src/config_parser.c new file mode 100644 index 0000000..ae2aa2c --- /dev/null +++ b/src/config_parser.c @@ -0,0 +1,238 @@ +/* + * config_parser.c - Common configuration file parser functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2009-2013 by Andreas Schneider + * + * 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 +#include +#include +#include + +#include "libssh/config_parser.h" +#include "libssh/priv.h" + +char *ssh_config_get_cmd(char **str) +{ + register char *c; + char *r; + + /* Ignore leading spaces */ + for (c = *str; *c; c++) { + if (! isblank(*c)) { + break; + } + } + + if (*c == '\"') { + for (r = ++c; *c; c++) { + if (*c == '\"') { + *c = '\0'; + goto out; + } + } + } + + for (r = c; *c; c++) { + if (*c == '\n') { + *c = '\0'; + goto out; + } + } + +out: + *str = c + 1; + + return r; +} + +char *ssh_config_get_token(char **str) +{ + register char *c; + char *r; + + c = ssh_config_get_cmd(str); + + for (r = c; *c; c++) { + if (isblank(*c) || *c == '=') { + *c = '\0'; + goto out; + } + } + +out: + *str = c + 1; + + return r; +} + +long ssh_config_get_long(char **str, long notfound) +{ + char *p, *endp; + long i; + + p = ssh_config_get_token(str); + if (p && *p) { + i = strtol(p, &endp, 10); + if (p == endp) { + return notfound; + } + return i; + } + + return notfound; +} + +const char *ssh_config_get_str_tok(char **str, const char *def) +{ + char *p; + + p = ssh_config_get_token(str); + if (p && *p) { + return p; + } + + return def; +} + +int ssh_config_get_yesno(char **str, int notfound) +{ + const char *p; + + p = ssh_config_get_str_tok(str, NULL); + if (p == NULL) { + return notfound; + } + + if (strncasecmp(p, "yes", 3) == 0) { + return 1; + } else if (strncasecmp(p, "no", 2) == 0) { + return 0; + } + + return notfound; +} + +int ssh_config_parse_uri(const char *tok, + char **username, + char **hostname, + char **port) +{ + char *endp = NULL; + long port_n; + + /* Sanitize inputs */ + if (username != NULL) { + *username = NULL; + } + if (hostname != NULL) { + *hostname = NULL; + } + if (port != NULL) { + *port = NULL; + } + + /* Username part (optional) */ + endp = strchr(tok, '@'); + if (endp != NULL) { + /* Zero-length username is not valid */ + if (tok == endp) { + goto error; + } + if (username != NULL) { + *username = strndup(tok, endp - tok); + if (*username == NULL) { + goto error; + } + } + tok = endp + 1; + /* If there is second @ character, this does not look like our URI */ + endp = strchr(tok, '@'); + if (endp != NULL) { + goto error; + } + } + + /* Hostname */ + if (*tok == '[') { + /* IPv6 address is enclosed with square brackets */ + tok++; + endp = strchr(tok, ']'); + if (endp == NULL) { + goto error; + } + } else { + /* Hostnames or aliases expand to the last colon or to the end */ + endp = strrchr(tok, ':'); + if (endp == NULL) { + endp = strchr(tok, '\0'); + } + } + if (tok == endp) { + /* Zero-length hostnames are not valid */ + goto error; + } + if (hostname != NULL) { + *hostname = strndup(tok, endp - tok); + if (*hostname == NULL) { + goto error; + } + } + /* Skip also the closing bracket */ + if (*endp == ']') { + endp++; + } + + /* Port (optional) */ + if (*endp != '\0') { + char *port_end = NULL; + + /* 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." + " The value '%ld' is invalid or there are some" + " trailing characters: '%s'", port_n, port_end); + goto error; + } + if (port != NULL) { + *port = strdup(endp + 1); + if (*port == NULL) { + goto error; + } + } + } + + return SSH_OK; + +error: + if (username != NULL) { + SAFE_FREE(*username); + } + if (hostname != NULL) { + SAFE_FREE(*hostname); + } + if (port != NULL) { + SAFE_FREE(*port); + } + return SSH_ERROR; +} diff --git a/src/connect.c b/src/connect.c new file mode 100644 index 0000000..252e2c6 --- /dev/null +++ b/src/connect.c @@ -0,0 +1,397 @@ +/* + * connect.c - handles connections to ssh servers + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 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 +#include +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "libssh/misc.h" + +#ifdef _WIN32 +/* + * Only use Windows API functions available on Windows 2000 SP4 or later. + * The available constants are in . + * http://msdn.microsoft.com/en-us/library/aa383745.aspx + * http://blogs.msdn.com/oldnewthing/archive/2007/04/11/2079137.aspx + */ +#undef _WIN32_WINNT +#ifdef HAVE_WSPIAPI_H +#define _WIN32_WINNT 0x0500 /* _WIN32_WINNT_WIN2K */ +#undef NTDDI_VERSION +#define NTDDI_VERSION 0x05000400 /* NTDDI_WIN2KSP4 */ +#else +#define _WIN32_WINNT 0x0501 /* _WIN32_WINNT_WINXP */ +#undef NTDDI_VERSION +#define NTDDI_VERSION 0x05010000 /* NTDDI_WINXP */ +#endif + +#if _MSC_VER >= 1400 +#include +#undef close +#define close _close +#endif /* _MSC_VER */ +#include +#include + +/* is necessary for getaddrinfo before Windows XP, but it isn't + * available on some platforms like MinGW. */ +#ifdef HAVE_WSPIAPI_H +#include +#endif + +#ifndef EINPROGRESS +#define EINPROGRESS WSAEINPROGRESS +#endif + +#else /* _WIN32 */ + +#include +#include +#include +#include +#include + +#endif /* _WIN32 */ + +#include "libssh/priv.h" +#include "libssh/socket.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/poll.h" + +#ifndef HAVE_GETADDRINFO +#error "Your system must have getaddrinfo()" +#endif + +#ifdef _WIN32 +#ifndef gai_strerror +char WSAAPI *gai_strerrorA(int code) +{ + static char buf[256]; + + snprintf(buf, sizeof(buf), "Undetermined error code (%d)", code); + + return buf; +} +#endif /* gai_strerror */ +#endif /* _WIN32 */ + +static int ssh_connect_socket_close(socket_t s) +{ +#ifdef _WIN32 + return closesocket(s); +#else + return close(s); +#endif +} + +static int getai(const char *host, int port, struct addrinfo **ai) +{ + const char *service = NULL; + struct addrinfo hints; + char s_port[10]; + + ZERO_STRUCT(hints); + + hints.ai_protocol = IPPROTO_TCP; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if (port == 0) { + hints.ai_flags = AI_PASSIVE; + } else { + snprintf(s_port, sizeof(s_port), "%hu", (unsigned short)port); + service = s_port; +#ifdef AI_NUMERICSERV + hints.ai_flags = AI_NUMERICSERV; +#endif + } + + if (ssh_is_ipaddr(host)) { + /* this is an IP address */ + SSH_LOG(SSH_LOG_PACKET, "host %s matches an IP address", host); + hints.ai_flags |= AI_NUMERICHOST; + } + + return getaddrinfo(host, service, &hints, ai); +} + +static int set_tcp_nodelay(socket_t socket) +{ + int opt = 1; + + return setsockopt(socket, + IPPROTO_TCP, + TCP_NODELAY, + (void *)&opt, + sizeof(opt)); +} + +/** + * @internal + * + * @brief Launches a nonblocking connect to an IPv4 or IPv6 host + * specified by its IP address or hostname. + * + * @returns A file descriptor, < 0 on error. + * @warning very ugly !!! + */ +socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, + const char *bind_addr, int port) +{ + socket_t s = -1; + int rc; + struct addrinfo *ai = NULL; + struct addrinfo *itr = NULL; + + rc = getai(host, port, &ai); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, + "Failed to resolve hostname %s (%s)", + host, gai_strerror(rc)); + + return -1; + } + + for (itr = ai; itr != NULL; itr = itr->ai_next) { + /* 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)); + continue; + } + + if (bind_addr) { + struct addrinfo *bind_ai; + struct addrinfo *bind_itr; + + SSH_LOG(SSH_LOG_PACKET, "Resolving %s", bind_addr); + + rc = getai(bind_addr, 0, &bind_ai); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, + "Failed to resolve bind address %s (%s)", + bind_addr, + gai_strerror(rc)); + ssh_connect_socket_close(s); + s = -1; + break; + } + + for (bind_itr = bind_ai; + bind_itr != NULL; + bind_itr = bind_itr->ai_next) + { + if (bind(s, bind_itr->ai_addr, bind_itr->ai_addrlen) < 0) { + ssh_set_error(session, SSH_FATAL, + "Binding local address: %s", strerror(errno)); + continue; + } else { + break; + } + } + freeaddrinfo(bind_ai); + + /* Cannot bind to any local addresses */ + if (bind_itr == NULL) { + ssh_connect_socket_close(s); + s = -1; + continue; + } + } + + rc = ssh_socket_set_nonblocking(s); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Failed to set socket non-blocking for %s:%d", + host, port); + ssh_connect_socket_close(s); + s = -1; + continue; + } + + if (session->opts.nodelay) { + /* For winsock, socket options are only effective before connect */ + rc = set_tcp_nodelay(s); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Failed to set TCP_NODELAY on socket: %s", + strerror(errno)); + ssh_connect_socket_close(s); + s = -1; + continue; + } + } + + 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; + continue; + } + + break; + } + + freeaddrinfo(ai); + + return s; +} + +/** + * @addtogroup libssh_session + * + * @{ + */ + +static int ssh_select_cb (socket_t fd, int revents, void *userdata) +{ + fd_set *set = (fd_set *)userdata; + if (revents & POLLIN) { + FD_SET(fd, set); + } + return 0; +} + +/** + * @brief A wrapper for the select syscall + * + * This functions 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[in] maxfd Maximum +1 file descriptor from readfds. + * + * @param[in] readfds A fd_set of file descriptors to be select'ed for + * reading. + * + * @param[in] timeout The timeout in milliseconds. + * + * @return SSH_OK on success, + * SSH_ERROR on error, + * SSH_EINTR if it was interrupted. In that case, + * just restart it. + * + * @warning libssh is not reentrant here. That means that if a signal is caught + * during the processing of this function, you cannot call libssh + * functions on sessions that are busy with ssh_select(). + * + * @see select(2) + */ +int ssh_select(ssh_channel *channels, ssh_channel *outchannels, socket_t maxfd, + fd_set *readfds, struct timeval *timeout) +{ + fd_set origfds; + socket_t fd; + size_t i, j; + int rc; + int base_tm, tm; + struct ssh_timestamp ts; + ssh_event event = ssh_event_new(); + int firstround = 1; + + base_tm = tm = (timeout->tv_sec * 1000) + (timeout->tv_usec / 1000); + for (i = 0 ; channels[i] != NULL; ++i) { + ssh_event_add_session(event, channels[i]->session); + } + + ZERO_STRUCT(origfds); + FD_ZERO(&origfds); + for (fd = 0; fd < maxfd ; fd++) { + if (FD_ISSET(fd, readfds)) { + ssh_event_add_fd(event, fd, POLLIN, ssh_select_cb, readfds); + FD_SET(fd, &origfds); + } + } + outchannels[0] = NULL; + FD_ZERO(readfds); + ssh_timestamp_init(&ts); + do { + /* Poll every channel */ + j = 0; + for (i = 0; channels[i]; i++) { + rc = ssh_channel_poll(channels[i], 0); + if (rc != 0) { + outchannels[j] = channels[i]; + j++; + } else { + rc = ssh_channel_poll(channels[i], 1); + if (rc != 0) { + outchannels[j] = channels[i]; + j++; + } + } + } + + outchannels[j] = NULL; + if (j != 0) { + break; + } + + /* watch if a user socket was triggered */ + for (fd = 0; fd < maxfd; fd++) { + if (FD_ISSET(fd, readfds)) { + goto out; + } + } + + /* If the timeout is elapsed, we should go out */ + if (!firstround && ssh_timeout_elapsed(&ts, base_tm)) { + goto out; + } + + /* since there's nothing, let's fire the polling */ + rc = ssh_event_dopoll(event,tm); + if (rc == SSH_ERROR) { + goto out; + } + + tm = ssh_timeout_update(&ts, base_tm); + firstround = 0; + } while (1); +out: + for (fd = 0; fd < maxfd; fd++) { + if (FD_ISSET(fd, &origfds)) { + ssh_event_remove_fd(event, fd); + } + } + ssh_event_free(event); + return SSH_OK; +} + +/** @} */ diff --git a/src/connector.c b/src/connector.c new file mode 100644 index 0000000..ac84133 --- /dev/null +++ b/src/connector.c @@ -0,0 +1,752 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2015 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/priv.h" +#include "libssh/poll.h" +#include "libssh/callbacks.h" +#include "libssh/session.h" +#include +#include +#include +#include + +#define CHUNKSIZE 4096 + +#ifdef _WIN32 +# ifdef HAVE_IO_H +# include +# 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 +# include +# include +#endif + +struct ssh_connector_struct { + ssh_session session; + + ssh_channel in_channel; + ssh_channel out_channel; + + socket_t in_fd; + socket_t out_fd; + + bool fd_is_socket; + + ssh_poll_handle in_poll; + ssh_poll_handle out_poll; + + ssh_event event; + + int in_available; + int out_wontblock; + + struct ssh_channel_callbacks_struct in_channel_cb; + struct ssh_channel_callbacks_struct out_channel_cb; + + enum ssh_connector_flags_e in_flags; + enum ssh_connector_flags_e out_flags; +}; + +static int ssh_connector_channel_data_cb(ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata); +static int ssh_connector_channel_write_wontblock_cb(ssh_session session, + ssh_channel channel, + size_t bytes, + void *userdata); +static ssize_t ssh_connector_fd_read(ssh_connector connector, + void *buffer, + uint32_t len); +static ssize_t ssh_connector_fd_write(ssh_connector connector, + const void *buffer, + uint32_t len); +static bool ssh_connector_fd_is_socket(socket_t socket); + +ssh_connector ssh_connector_new(ssh_session session) +{ + ssh_connector connector; + + connector = calloc(1, sizeof(struct ssh_connector_struct)); + if (connector == NULL){ + ssh_set_error_oom(session); + return NULL; + } + + connector->session = session; + connector->in_fd = SSH_INVALID_SOCKET; + connector->out_fd = SSH_INVALID_SOCKET; + + connector->fd_is_socket = false; + + ssh_callbacks_init(&connector->in_channel_cb); + ssh_callbacks_init(&connector->out_channel_cb); + + connector->in_channel_cb.userdata = connector; + connector->in_channel_cb.channel_data_function = ssh_connector_channel_data_cb; + + connector->out_channel_cb.userdata = connector; + connector->out_channel_cb.channel_write_wontblock_function = + ssh_connector_channel_write_wontblock_cb; + + return connector; +} + +void ssh_connector_free (ssh_connector connector) +{ + if (connector->in_channel != NULL) { + ssh_remove_channel_callbacks(connector->in_channel, + &connector->in_channel_cb); + } + if (connector->out_channel != NULL) { + ssh_remove_channel_callbacks(connector->out_channel, + &connector->out_channel_cb); + } + + if (connector->event != NULL){ + ssh_connector_remove_event(connector); + } + + if (connector->in_poll != NULL) { + ssh_poll_free(connector->in_poll); + connector->in_poll = NULL; + } + + if (connector->out_poll != NULL) { + ssh_poll_free(connector->out_poll); + connector->out_poll = NULL; + } + + free(connector); +} + +int ssh_connector_set_in_channel(ssh_connector connector, + ssh_channel channel, + enum ssh_connector_flags_e flags) +{ + connector->in_channel = channel; + connector->in_fd = SSH_INVALID_SOCKET; + connector->in_flags = flags; + + /* Fallback to default value for invalid flags */ + if (!(flags & SSH_CONNECTOR_STDOUT) && !(flags & SSH_CONNECTOR_STDERR)) { + connector->in_flags = SSH_CONNECTOR_STDOUT; + } + + return ssh_add_channel_callbacks(channel, &connector->in_channel_cb); +} + +int ssh_connector_set_out_channel(ssh_connector connector, + ssh_channel channel, + enum ssh_connector_flags_e flags) +{ + connector->out_channel = channel; + connector->out_fd = SSH_INVALID_SOCKET; + connector->out_flags = flags; + + /* Fallback to default value for invalid flags */ + if (!(flags & SSH_CONNECTOR_STDOUT) && !(flags & SSH_CONNECTOR_STDERR)) { + connector->in_flags = SSH_CONNECTOR_STDOUT; + } + + return ssh_add_channel_callbacks(channel, &connector->out_channel_cb); +} + +void ssh_connector_set_in_fd(ssh_connector connector, socket_t fd) +{ + connector->in_fd = fd; + connector->fd_is_socket = ssh_connector_fd_is_socket(fd); + connector->in_channel = NULL; +} + +void ssh_connector_set_out_fd(ssh_connector connector, socket_t fd) +{ + connector->out_fd = fd; + connector->fd_is_socket = ssh_connector_fd_is_socket(fd); + connector->out_channel = NULL; +} + +/* TODO */ +static void ssh_connector_except(ssh_connector connector, socket_t fd) +{ + (void) connector; + (void) fd; +} + +/* TODO */ +static void ssh_connector_except_channel(ssh_connector connector, + ssh_channel channel) +{ + (void) connector; + (void) channel; +} + +/** + * @internal + * + * @brief Reset the poll events to be followed for each file descriptors. + */ +static void ssh_connector_reset_pollevents(ssh_connector connector) +{ + if (connector->in_fd != SSH_INVALID_SOCKET) { + if (connector->in_available) { + ssh_poll_remove_events(connector->in_poll, POLLIN); + } else { + ssh_poll_add_events(connector->in_poll, POLLIN); + } + } + + if (connector->out_fd != SSH_INVALID_SOCKET) { + if (connector->out_wontblock) { + ssh_poll_remove_events(connector->out_poll, POLLOUT); + } else { + ssh_poll_add_events(connector->out_poll, POLLOUT); + } + } +} + +/** + * @internal + * + * @brief Callback called when a poll event is received on an input fd. + */ +static void ssh_connector_fd_in_cb(ssh_connector connector) +{ + unsigned char buffer[CHUNKSIZE]; + uint32_t toread = CHUNKSIZE; + ssize_t r; + ssize_t w; + int 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); + + /* Don't attempt reading more than the window */ + toread = MIN(size, CHUNKSIZE); + } + + r = ssh_connector_fd_read(connector, buffer, toread); + if (r < 0) { + ssh_connector_except(connector, connector->in_fd); + return; + } + + if (connector->out_channel != NULL) { + if (r == 0) { + SSH_LOG(SSH_LOG_TRACE, "input fd %d is EOF", connector->in_fd); + if (connector->out_channel->local_eof == 0) { + rc = ssh_channel_send_eof(connector->out_channel); + (void)rc; /* TODO Handle rc? */ + } + connector->in_available = 1; /* Don't poll on it */ + return; + } else if (r> 0) { + /* loop around ssh_channel_write in case our window reduced due to a race */ + while (total != r){ + if (connector->out_flags & SSH_CONNECTOR_STDOUT) { + w = ssh_channel_write(connector->out_channel, + buffer + total, + r - total); + } else { + w = ssh_channel_write_stderr(connector->out_channel, + buffer + total, + r - total); + } + if (w == SSH_ERROR) { + return; + } + total += w; + } + } + } else if (connector->out_fd != SSH_INVALID_SOCKET) { + if (r == 0){ + close(connector->out_fd); + connector->out_fd = SSH_INVALID_SOCKET; + } else { + /* + * 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){ + ssh_connector_except(connector, connector->out_fd); + return; + } + total += w; + } + } + } else { + ssh_set_error(connector->session, SSH_FATAL, "output socket or channel closed"); + return; + } + connector->out_wontblock = 0; + connector->in_available = 0; + } else { + connector->in_available = 1; + } +} + +/** @internal + * @brief Callback called when a poll event is received on an output fd + */ +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); + + 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)){ + close(connector->out_fd); + connector->out_fd = SSH_INVALID_SOCKET; + return; + } 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){ + ssh_connector_except(connector, connector->out_fd); + return; + } + total += w; + } + } + } else if (connector->in_fd != SSH_INVALID_SOCKET){ + /* fallback on the socket input callback */ + connector->out_wontblock = 1; + ssh_connector_fd_in_cb(connector); + } else { + ssh_set_error(connector->session, + SSH_FATAL, + "Output socket or channel closed"); + return; + } + connector->in_available = 0; + connector->out_wontblock = 0; + } else { + connector->out_wontblock = 1; + } +} + +/** + * @internal + * + * @brief Callback called when a poll event is received on a file descriptor. + * + * This is for (input or output. + * + * @param[in] fd file descriptor receiving the event + * + * @param[in] revents received Poll(2) events + * + * @param[in] userdata connector + * + * @returns 0 + */ +static int ssh_connector_fd_cb(ssh_poll_handle p, + socket_t fd, + int revents, + void *userdata) +{ + ssh_connector connector = userdata; + + (void)p; + + if (revents & POLLERR) { + ssh_connector_except(connector, fd); + } else if((revents & (POLLIN|POLLHUP)) && fd == connector->in_fd) { + ssh_connector_fd_in_cb(connector); + } else if(((revents & POLLOUT) || (revents & POLLHUP)) && + fd == connector->out_fd) { + ssh_connector_fd_out_cb(connector); + } + ssh_connector_reset_pollevents(connector); + + return 0; +} + +/** + * @internal + * + * @brief Callback called when data is received on channel. + * + * @param[in] data Pointer to the data + * + * @param[in] len Length of data + * + * @param[in] is_stderr Set to 1 if the data are out of band + * + * @param[in] userdata The ssh connector + * + * @returns Amount of data bytes consumed + */ +static int ssh_connector_channel_data_cb(ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata) +{ + ssh_connector connector = userdata; + int w; + size_t window; + + (void) session; + (void) channel; + (void) is_stderr; + + SSH_LOG(SSH_LOG_TRACE,"connector data on channel"); + + if (is_stderr && !(connector->in_flags & SSH_CONNECTOR_STDERR)) { + /* ignore stderr */ + return 0; + } else if (!is_stderr && !(connector->in_flags & SSH_CONNECTOR_STDOUT)) { + /* ignore stdout */ + return 0; + } + + if (connector->out_wontblock) { + if (connector->out_channel != NULL) { + int window_len; + + window = ssh_channel_window_size(connector->out_channel); + window_len = MIN(window, len); + + /* Route the data to the right exception channel */ + if (is_stderr && (connector->out_flags & SSH_CONNECTOR_STDERR)) { + w = ssh_channel_write_stderr(connector->out_channel, + data, + window_len); + } else if (!is_stderr && + (connector->out_flags & SSH_CONNECTOR_STDOUT)) { + w = ssh_channel_write(connector->out_channel, + data, + window_len); + } else if (connector->out_flags & SSH_CONNECTOR_STDOUT) { + w = ssh_channel_write(connector->out_channel, + data, + window_len); + } else { + w = ssh_channel_write_stderr(connector->out_channel, + data, + window_len); + } + if (w == SSH_ERROR) { + ssh_connector_except_channel(connector, connector->out_channel); + } + } else if (connector->out_fd != SSH_INVALID_SOCKET) { + w = ssh_connector_fd_write(connector, data, len); + if (w < 0) + ssh_connector_except(connector, connector->out_fd); + } else { + ssh_set_error(session, SSH_FATAL, "output socket or channel closed"); + return SSH_ERROR; + } + + connector->out_wontblock = 0; + connector->in_available = 0; + if ((unsigned int)w < len) { + connector->in_available = 1; + } + ssh_connector_reset_pollevents(connector); + + return w; + } else { + connector->in_available = 1; + + return 0; + } +} + +/** + * @internal + * + * @brief Callback called when the channel is free to write. + * + * @param[in] bytes Amount of bytes that can be written without blocking + * + * @param[in] userdata The ssh connector + * + * @returns Amount of data bytes consumed + */ +static int ssh_connector_channel_write_wontblock_cb(ssh_session session, + ssh_channel channel, + size_t bytes, + void *userdata) +{ + ssh_connector connector = userdata; + uint8_t buffer[CHUNKSIZE]; + int r, w; + + (void) channel; + + 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); + + r = ssh_channel_read_nonblocking(connector->in_channel, + buffer, + len, + 0); + if (r == SSH_ERROR) { + ssh_connector_except_channel(connector, connector->in_channel); + } else if(r == 0 && ssh_channel_is_eof(connector->in_channel)){ + ssh_channel_send_eof(connector->out_channel); + } else if (r > 0) { + w = ssh_channel_write(connector->out_channel, buffer, r); + if (w == SSH_ERROR) { + ssh_connector_except_channel(connector, + connector->out_channel); + } + } + } else if (connector->in_fd != SSH_INVALID_SOCKET) { + /* fallback on on the socket input callback */ + connector->out_wontblock = 1; + ssh_connector_fd_in_cb(connector); + ssh_connector_reset_pollevents(connector); + } else { + ssh_set_error(session, + SSH_FATAL, + "Output socket or channel closed"); + + return 0; + } + connector->in_available = 0; + connector->out_wontblock = 0; + } else { + connector->out_wontblock = 1; + } + + return 0; +} + +int ssh_connector_set_event(ssh_connector connector, ssh_event event) +{ + int rc = SSH_OK; + + if ((connector->in_fd == SSH_INVALID_SOCKET && + connector->in_channel == NULL) + || (connector->out_fd == SSH_INVALID_SOCKET && + connector->out_channel == NULL)) { + rc = SSH_ERROR; + ssh_set_error(connector->session,SSH_FATAL,"Connector not complete"); + goto error; + } + + connector->event = event; + if (connector->in_fd != SSH_INVALID_SOCKET) { + if (connector->in_poll == NULL) { + connector->in_poll = ssh_poll_new(connector->in_fd, + POLLIN|POLLERR, + ssh_connector_fd_cb, + connector); + } + rc = ssh_event_add_poll(event, connector->in_poll); + if (rc != SSH_OK) { + goto error; + } + } + + if (connector->out_fd != SSH_INVALID_SOCKET) { + if (connector->out_poll == NULL) { + connector->out_poll = ssh_poll_new(connector->out_fd, + POLLOUT|POLLERR, + ssh_connector_fd_cb, + connector); + } + + rc = ssh_event_add_poll(event, connector->out_poll); + if (rc != SSH_OK) { + goto error; + } + } + if (connector->in_channel != NULL) { + rc = ssh_event_add_session(event, + ssh_channel_get_session(connector->in_channel)); + if (rc != SSH_OK) + goto error; + if (ssh_channel_poll_timeout(connector->in_channel, 0, 0) > 0){ + connector->in_available = 1; + } + } + if(connector->out_channel != NULL) { + ssh_session session = ssh_channel_get_session(connector->out_channel); + + rc = ssh_event_add_session(event, session); + if (rc != SSH_OK) { + goto error; + } + if (ssh_channel_window_size(connector->out_channel) > 0) { + connector->out_wontblock = 1; + } + } + +error: + return rc; +} + +int ssh_connector_remove_event(ssh_connector connector) { + ssh_session session; + + if (connector->in_poll != NULL) { + ssh_event_remove_poll(connector->event, connector->in_poll); + ssh_poll_free(connector->in_poll); + connector->in_poll = NULL; + } + + if (connector->out_poll != NULL) { + ssh_event_remove_poll(connector->event, connector->out_poll); + ssh_poll_free(connector->out_poll); + connector->out_poll = NULL; + } + + if (connector->in_channel != NULL) { + session = ssh_channel_get_session(connector->in_channel); + + ssh_event_remove_session(connector->event, session); + } + + if (connector->out_channel != NULL) { + session = ssh_channel_get_session(connector->out_channel); + + ssh_event_remove_session(connector->event, session); + } + connector->event = NULL; + + return SSH_OK; +} + +/** + * @internal + * + * @brief Check the file descriptor to check if it is a Windows socket handle. + * + */ +static bool ssh_connector_fd_is_socket(socket_t s) +{ +#ifdef _WIN32 + struct sockaddr_storage ss; + int len = sizeof(struct sockaddr_storage); + int rc; + + rc = getsockname(s, (struct sockaddr *)&ss, &len); + if (rc == 0) { + return true; + } + + SSH_LOG(SSH_LOG_TRACE, + "Error %i in getsockname() for fd %d", + WSAGetLastError(), + s); + + return false; +#else + struct stat sb; + int rc; + + rc = fstat(s, &sb); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "error %i in fstat() for fd %d", + errno, + s); + return false; + } + + /* The descriptor is a socket */ + if (S_ISSOCK(sb.st_mode)) { + return true; + } + + return false; +#endif /* _WIN32 */ +} + +/** + * @internal + * + * @brief read len bytes from socket into buffer + * + */ +static ssize_t ssh_connector_fd_read(ssh_connector connector, + void *buffer, + uint32_t len) +{ + ssize_t nread = -1; + + if (connector->fd_is_socket) { + nread = recv(connector->in_fd,buffer, len, 0); + } else { + nread = read(connector->in_fd,buffer, len); + } + + return nread; +} + +/** + * @internal + * + * @brief brief writes len bytes from buffer to socket + * + */ +static ssize_t ssh_connector_fd_write(ssh_connector connector, + const void *buffer, + uint32_t len) +{ + ssize_t bwritten = -1; + int flags = 0; + +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + + if (connector->fd_is_socket) { + bwritten = send(connector->out_fd,buffer, len, flags); + } else { + bwritten = write(connector->out_fd, buffer, len); + } + + return bwritten; +} diff --git a/src/curve25519.c b/src/curve25519.c new file mode 100644 index 0000000..c13b360 --- /dev/null +++ b/src/curve25519.c @@ -0,0 +1,518 @@ +/* + * curve25519.c - Curve25519 ECDH functions for key exchange + * curve25519-sha256@libssh.org and curve25519-sha256 + * + * This file is part of the SSH Library + * + * Copyright (c) 2013 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, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/curve25519.h" +#ifdef HAVE_CURVE25519 + +#ifdef WITH_NACL +#include "nacl/crypto_scalarmult_curve25519.h" +#endif + +#include "libssh/ssh2.h" +#include "libssh/buffer.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/dh.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" + +#ifdef HAVE_OPENSSL_X25519 +#include +#endif + +static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply); + +static ssh_packet_callback dh_client_callbacks[] = { + ssh_packet_client_curve25519_reply +}; + +static struct ssh_packet_callbacks_struct ssh_curve25519_client_callbacks = { + .start = SSH2_MSG_KEX_ECDH_REPLY, + .n_callbacks = 1, + .callbacks = dh_client_callbacks, + .user = NULL +}; + +static int ssh_curve25519_init(ssh_session session) +{ + int rc; +#ifdef HAVE_OPENSSL_X25519 + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY *pkey = NULL; + size_t pubkey_len = CURVE25519_PUBKEY_SIZE; + size_t pkey_len = CURVE25519_PRIVKEY_SIZE; + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL); + if (pctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize X25519 context: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + rc = EVP_PKEY_keygen_init(pctx); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize X25519 keygen: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(pctx); + return SSH_ERROR; + } + + rc = EVP_PKEY_keygen(pctx, &pkey); + EVP_PKEY_CTX_free(pctx); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate X25519 keys: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + if (session->server) { + rc = EVP_PKEY_get_raw_public_key(pkey, + session->next_crypto->curve25519_server_pubkey, + &pubkey_len); + } else { + rc = EVP_PKEY_get_raw_public_key(pkey, + session->next_crypto->curve25519_client_pubkey, + &pubkey_len); + } + + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get X25519 raw public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_free(pkey); + return SSH_ERROR; + } + + rc = EVP_PKEY_get_raw_private_key(pkey, + session->next_crypto->curve25519_privkey, + &pkey_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get X25519 raw private key: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_free(pkey); + return SSH_ERROR; + } + + EVP_PKEY_free(pkey); +#else + rc = ssh_get_random(session->next_crypto->curve25519_privkey, + CURVE25519_PRIVKEY_SIZE, 1); + if (rc != 1) { + ssh_set_error(session, SSH_FATAL, "PRNG error"); + return SSH_ERROR; + } + + if (session->server) { + crypto_scalarmult_base(session->next_crypto->curve25519_server_pubkey, + session->next_crypto->curve25519_privkey); + } else { + crypto_scalarmult_base(session->next_crypto->curve25519_client_pubkey, + session->next_crypto->curve25519_privkey); + } +#endif /* HAVE_OPENSSL_X25519 */ + + return SSH_OK; +} + +/** @internal + * @brief Starts curve25519-sha256@libssh.org / curve25519-sha256 key exchange + */ +int ssh_client_curve25519_init(ssh_session session) +{ + int rc; + + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + return rc; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_KEX_ECDH_INIT, + CURVE25519_PUBKEY_SIZE, + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_client_pubkey); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_curve25519_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + rc = ssh_packet_send(session); + + return rc; +} + +static int ssh_curve25519_build_k(ssh_session session) +{ + ssh_curve25519_pubkey k; + +#ifdef HAVE_OPENSSL_X25519 + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY *pkey = NULL, *pubkey = NULL; + size_t shared_key_len = sizeof(k); + int rc, ret = SSH_ERROR; + + pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, + session->next_crypto->curve25519_privkey, + CURVE25519_PRIVKEY_SIZE); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create X25519 EVP_PKEY: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + pctx = EVP_PKEY_CTX_new(pkey, NULL); + if (pctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize X25519 context: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + rc = EVP_PKEY_derive_init(pctx); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize X25519 key derivation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + if (session->server) { + pubkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL, + session->next_crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE); + } else { + pubkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL, + session->next_crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); + } + if (pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create X25519 public key EVP_PKEY: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + rc = EVP_PKEY_derive_set_peer(pctx, pubkey); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to set peer X25519 public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + rc = EVP_PKEY_derive(pctx, k, &shared_key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to derive X25519 shared secret: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + ret = SSH_OK; +out: + EVP_PKEY_free(pkey); + EVP_PKEY_free(pubkey); + EVP_PKEY_CTX_free(pctx); + if (ret == SSH_ERROR) { + return ret; + } +#else + if (session->server) { + crypto_scalarmult(k, session->next_crypto->curve25519_privkey, + session->next_crypto->curve25519_client_pubkey); + } else { + crypto_scalarmult(k, session->next_crypto->curve25519_privkey, + session->next_crypto->curve25519_server_pubkey); + } +#endif /* HAVE_OPENSSL_X25519 */ + + bignum_bin2bn(k, CURVE25519_PUBKEY_SIZE, &session->next_crypto->shared_secret); + if (session->next_crypto->shared_secret == NULL) { + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Session server cookie", + session->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 + + return 0; +} + +/** @internal + * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back + * a SSH_MSG_NEWKEYS + */ +static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply){ + ssh_string q_s_string = NULL; + ssh_string pubkey_blob = NULL; + ssh_string signature = NULL; + int rc; + (void)type; + (void)user; + + ssh_packet_remove_callbacks(session, &ssh_curve25519_client_callbacks); + + pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (pubkey_blob == NULL) { + ssh_set_error(session,SSH_FATAL, "No public key in packet"); + goto error; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + ssh_set_error(session, + SSH_FATAL, + "Failed to import next public key"); + goto error; + } + + q_s_string = ssh_buffer_get_ssh_string(packet); + if (q_s_string == NULL) { + ssh_set_error(session,SSH_FATAL, "No Q_S ECC point in packet"); + goto error; + } + if (ssh_string_len(q_s_string) != CURVE25519_PUBKEY_SIZE){ + ssh_set_error(session, SSH_FATAL, "Incorrect size for server Curve25519 public key: %d", + (int)ssh_string_len(q_s_string)); + SSH_STRING_FREE(q_s_string); + goto error; + } + memcpy(session->next_crypto->curve25519_server_pubkey, ssh_string_data(q_s_string), CURVE25519_PUBKEY_SIZE); + SSH_STRING_FREE(q_s_string); + + signature = ssh_buffer_get_ssh_string(packet); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "No signature in packet"); + goto error; + } + session->next_crypto->dh_server_signature = signature; + signature=NULL; /* ownership changed */ + /* TODO: verify signature now instead of waiting for NEWKEYS */ + if (ssh_curve25519_build_k(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { + goto error; + } + + rc=ssh_packet_send(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: + session->session_state=SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init); + +static ssh_packet_callback dh_server_callbacks[]= { + ssh_packet_server_curve25519_init +}; + +static struct ssh_packet_callbacks_struct ssh_curve25519_server_callbacks = { + .start = SSH2_MSG_KEX_ECDH_INIT, + .n_callbacks = 1, + .callbacks = dh_server_callbacks, + .user = NULL +}; + +/** @internal + * @brief sets up the curve25519-sha256@libssh.org kex callbacks + */ +void ssh_server_curve25519_init(ssh_session session){ + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_curve25519_server_callbacks); +} + +/** @brief Parse a SSH_MSG_KEXDH_INIT packet (server) and send a + * SSH_MSG_KEXDH_REPLY + */ +static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init){ + /* ECDH keys */ + ssh_string q_c_string; + ssh_string q_s_string; + ssh_string server_pubkey_blob = NULL; + + /* SSH host keys (rsa,dsa,ecdsa) */ + ssh_key privkey; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string sig_blob = NULL; + int rc; + (void)type; + (void)user; + + ssh_packet_remove_callbacks(session, &ssh_curve25519_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"); + goto error; + } + if (ssh_string_len(q_c_string) != CURVE25519_PUBKEY_SIZE){ + ssh_set_error(session, + 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 */ + + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate curve25519 keys"); + goto error; + } + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_REPLY); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + /* build k and session_id */ + rc = ssh_curve25519_build_k(session); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* privkey is not allocated */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto error; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + goto error; + } + + /* add host's public key */ + rc = ssh_buffer_add_ssh_string(session->out_buffer, + server_pubkey_blob); + SSH_STRING_FREE(server_pubkey_blob); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + /* add ecdh public key */ + q_s_string = ssh_string_new(CURVE25519_PUBKEY_SIZE); + if (q_s_string == NULL) { + goto error; + } + + ssh_string_fill(q_s_string, + session->next_crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); + + rc = ssh_buffer_add_ssh_string(session->out_buffer, q_s_string); + SSH_STRING_FREE(q_s_string); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + /* add signature blob */ + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + goto error; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, sig_blob); + SSH_STRING_FREE(sig_blob); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "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); + if (rc == SSH_ERROR) { + goto error; + } + SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); + + return SSH_PACKET_USED; +error: + ssh_buffer_reinit(session->out_buffer); + session->session_state=SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ + +#endif /* HAVE_CURVE25519 */ diff --git a/src/dh-gex.c b/src/dh-gex.c new file mode 100644 index 0000000..9bf0546 --- /dev/null +++ b/src/dh-gex.c @@ -0,0 +1,689 @@ +/* + * dh-gex.c - diffie-hellman group exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2016 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 +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/dh-gex.h" +#include "libssh/libssh.h" +#include "libssh/ssh2.h" +#include "libssh/callbacks.h" +#include "libssh/dh.h" +#include "libssh/buffer.h" +#include "libssh/session.h" + +/* Minimum, recommanded and maximum size of DH group */ +#define DH_PMIN 2048 +#define DH_PREQ 2048 +#define DH_PMAX 8192 + +static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group); +static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply); + +static ssh_packet_callback dhgex_client_callbacks[] = { + ssh_packet_client_dhgex_group, /* SSH_MSG_KEX_DH_GEX_GROUP */ + NULL, /* SSH_MSG_KEX_DH_GEX_INIT */ + ssh_packet_client_dhgex_reply /* SSH_MSG_KEX_DH_GEX_REPLY */ +}; + +static struct ssh_packet_callbacks_struct ssh_dhgex_client_callbacks = { + .start = SSH2_MSG_KEX_DH_GEX_GROUP, + .n_callbacks = 3, + .callbacks = dhgex_client_callbacks, + .user = NULL +}; + +/** @internal + * @brief initiates a diffie-hellman-group-exchange kex + */ +int ssh_client_dhgex_init(ssh_session session) +{ + int rc; + + rc = ssh_dh_init_common(session->next_crypto); + if (rc != SSH_OK){ + goto error; + } + + session->next_crypto->dh_pmin = DH_PMIN; + session->next_crypto->dh_pn = DH_PREQ; + session->next_crypto->dh_pmax = DH_PMAX; + /* Minimum group size, preferred group size, maximum group size */ + rc = ssh_buffer_pack(session->out_buffer, + "bddd", + SSH2_MSG_KEX_DH_GEX_REQUEST, + session->next_crypto->dh_pmin, + session->next_crypto->dh_pn, + session->next_crypto->dh_pmax); + if (rc != SSH_OK) { + goto error; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_dhgex_client_callbacks); + session->dh_handshake_state = DH_STATE_REQUEST_SENT; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + return rc; +error: + ssh_dh_cleanup(session->next_crypto); + return SSH_ERROR; +} + +/** @internal + * @brief handle a DH_GEX_GROUP packet, client side. This packet contains + * the group parameters. + */ +SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group) +{ + int rc; + int blen; + bignum pmin1 = NULL, one = NULL; + bignum_CTX ctx = bignum_ctx_new(); + bignum modulus = NULL, generator = NULL; + const_bignum pubkey; + (void) type; + (void) user; + + SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEX_DH_GEX_GROUP received"); + + if (bignum_ctx_invalid(ctx)) { + goto error; + } + + if (session->dh_handshake_state != DH_STATE_REQUEST_SENT) { + ssh_set_error(session, + SSH_FATAL, + "Received DH_GEX_GROUP in invalid state"); + goto error; + } + one = bignum_new(); + pmin1 = bignum_new(); + if (one == NULL || pmin1 == NULL) { + ssh_set_error_oom(session); + goto error; + } + rc = ssh_buffer_unpack(packet, + "BB", + &modulus, + &generator); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Invalid DH_GEX_GROUP packet"); + goto error; + } + /* basic checks */ + if (ssh_fips_mode() && + !ssh_dh_is_known_group(modulus, generator)) { + ssh_set_error(session, + SSH_FATAL, + "The received DH group is not FIPS approved"); + goto error; + } + rc = bignum_set_word(one, 1); + if (rc != 1) { + goto error; + } + blen = bignum_num_bits(modulus); + if (blen < DH_PMIN || blen > DH_PMAX) { + ssh_set_error(session, + SSH_FATAL, + "Invalid dh group parameter p: %d not in [%d:%d]", + blen, + DH_PMIN, + DH_PMAX); + goto error; + } + if (bignum_cmp(modulus, one) <= 0) { + /* p must be positive and preferably bigger than one */ + ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter p"); + } + if (!bignum_is_bit_set(modulus, 0)) { + /* p must be a prime and therefore not divisible by 2 */ + ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter p"); + goto error; + } + bignum_sub(pmin1, modulus, one); + if (bignum_cmp(generator, one) <= 0 || + bignum_cmp(generator, pmin1) > 0) { + /* generator must be at least 2 and smaller than p-1*/ + ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter g"); + goto error; + } + bignum_ctx_free(ctx); + ctx = NULL; + + /* all checks passed, set parameters (the BNs are copied in openssl backend) */ + rc = ssh_dh_set_parameters(session->next_crypto->dh_ctx, + modulus, generator); + if (rc != SSH_OK) { + goto error; + } +#ifdef HAVE_LIBCRYPTO + bignum_safe_free(modulus); + bignum_safe_free(generator); +#endif + modulus = NULL; + generator = NULL; + + /* compute and send DH public parameter */ + rc = ssh_dh_keypair_gen_keys(session->next_crypto->dh_ctx, + DH_CLIENT_KEYPAIR); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, + DH_CLIENT_KEYPAIR, NULL, &pubkey); + if (rc != SSH_OK) { + goto error; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bB", + SSH2_MSG_KEX_DH_GEX_INIT, + pubkey); + if (rc != SSH_OK) { + goto error; + } + + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + + bignum_safe_free(one); + bignum_safe_free(pmin1); + return SSH_PACKET_USED; + +error: + bignum_safe_free(modulus); + bignum_safe_free(generator); + bignum_safe_free(one); + bignum_safe_free(pmin1); + if(!bignum_ctx_invalid(ctx)) { + bignum_ctx_free(ctx); + } + ssh_dh_cleanup(session->next_crypto); + session->session_state = SSH_SESSION_STATE_ERROR; + + return SSH_PACKET_USED; +} + +static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply) +{ + struct ssh_crypto_struct *crypto=session->next_crypto; + int rc; + ssh_string pubkey_blob = NULL; + bignum server_pubkey = NULL; + (void)type; + (void)user; + SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEX_DH_GEX_REPLY received"); + + ssh_packet_remove_callbacks(session, &ssh_dhgex_client_callbacks); + rc = ssh_buffer_unpack(packet, + "SBS", + &pubkey_blob, &server_pubkey, + &crypto->dh_server_signature); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "Invalid DH_GEX_REPLY packet"); + goto error; + } + rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, DH_SERVER_KEYPAIR, + NULL, server_pubkey); + if (rc != SSH_OK) { + bignum_safe_free(server_pubkey); + goto error; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + 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); + ssh_dh_debug_crypto(session->next_crypto); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "Could not generate shared secret"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { + goto error; + } + + rc = ssh_packet_send(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_dh_cleanup(session->next_crypto); + session->session_state = SSH_SESSION_STATE_ERROR; + + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +#define MODULI_FILE "/etc/ssh/moduli" +/* 2 "Safe" prime; (p-1)/2 is also prime. */ +#define SAFE_PRIME 2 +/* 0x04 Probabilistic Miller-Rabin primality tests. */ +#define PRIM_TEST_REQUIRED 0x04 + +/** + * @internal + * + * @brief Determines if the proposed modulus size is more appropriate than the + * current one. + * + * @returns 1 if it's more appropriate. Returns 0 if same or less appropriate + */ +static bool dhgroup_better_size(uint32_t pmin, + uint32_t pn, + uint32_t pmax, + size_t current_size, + size_t proposed_size) +{ + if (current_size == proposed_size) { + return false; + } + + if (current_size == pn) { + /* can't do better */ + return false; + } + + if (current_size == 0 && proposed_size >= pmin && proposed_size <= pmax) { + return true; + } + + if (proposed_size < pmin || proposed_size > pmax) { + /* out of bounds */ + return false; + } + + if (current_size == 0) { + /* not in the allowed window */ + return false; + } + + if (proposed_size >= pn && proposed_size < current_size) { + return true; + } + + if (proposed_size <= pn && proposed_size > current_size) { + return true; + } + + if (proposed_size >= pn && current_size < pn) { + return true; + } + + /* We're in the allowed window but a better match already exists. */ + return false; +} + +/** @internal + * @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) +{ + uint32_t nounce = 0; + int ok; + + ok = ssh_get_random(&nounce, sizeof(nounce), 0); + if (!ok) { + return false; + } + return (nounce % n) == 0; +} + +/** @internal + * @brief retrieves a DH group from an open moduli file. + */ +static int ssh_retrieve_dhgroup_file(FILE *moduli, + uint32_t pmin, + uint32_t pn, + uint32_t pmax, + size_t *best_size, + char **best_generator, + char **best_modulus) +{ + char timestamp[32] = {0}; + char generator[32] = {0}; + char modulus[4096] = {0}; + size_t type, tests, tries, size, proposed_size; + int firstbyte; + int rc; + size_t line = 0; + size_t best_nlines = 0; + + for(;;) { + line++; + firstbyte = getc(moduli); + if (firstbyte == '#'){ + do { + firstbyte = getc(moduli); + } while(firstbyte != '\n' && firstbyte != EOF); + continue; + } + if (firstbyte == EOF) { + break; + } + ungetc(firstbyte, moduli); + rc = fscanf(moduli, + "%31s %zu %zu %zu %zu %31s %4095s\n", + timestamp, + &type, + &tests, + &tries, + &size, + generator, + modulus); + if (rc != 7){ + if (rc == EOF) { + break; + } + SSH_LOG(SSH_LOG_INFO, "Invalid moduli entry line %zu", line); + do { + firstbyte = getc(moduli); + } while(firstbyte != '\n' && firstbyte != EOF); + continue; + } + + /* we only want safe primes that were tested */ + if (type != SAFE_PRIME || !(tests & PRIM_TEST_REQUIRED)) { + continue; + } + + proposed_size = size + 1; + if (proposed_size != *best_size && + dhgroup_better_size(pmin, pn, pmax, *best_size, proposed_size)) { + best_nlines = 0; + *best_size = proposed_size; + } + if (proposed_size == *best_size) { + best_nlines++; + } + + /* Use reservoir sampling algorithm */ + if (proposed_size == *best_size && invn_chance(best_nlines)) { + SAFE_FREE(*best_generator); + SAFE_FREE(*best_modulus); + *best_generator = strdup(generator); + if (*best_generator == NULL) { + return SSH_ERROR; + } + *best_modulus = strdup(modulus); + if (*best_modulus == NULL) { + SAFE_FREE(*best_generator); + return SSH_ERROR; + } + } + } + if (*best_size != 0) { + SSH_LOG(SSH_LOG_INFO, + "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]", + pmin, + pn, + pmax); + } + + return SSH_OK; +} + +/** @internal + * @brief retrieves a DH group from the moduli file based on bits len parameters + * @param[in] pmin minimum group size in bits + * @param[in] pn preferred group size + * @param[in] pmax maximum group size + * @param[out] size size of the chosen modulus + * @param[out] p modulus + * @param[out] g generator + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +static int ssh_retrieve_dhgroup(uint32_t pmin, + uint32_t pn, + uint32_t pmax, + size_t *size, + bignum *p, + bignum *g) +{ + FILE *moduli = NULL; + char *generator = NULL; + char *modulus = NULL; + int rc; + + /* In FIPS mode, we can not negotiate arbitrary primes, + * but just the approved ones */ + if (ssh_fips_mode()) { + SSH_LOG(SSH_LOG_TRACE, "In FIPS mode, using built-in primes"); + return ssh_fallback_group(pmax, p, g); + } + + moduli = fopen(MODULI_FILE, "r"); + if (moduli == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "Unable to open moduli file: %s", + strerror(errno)); + return ssh_fallback_group(pmax, p, g); + } + + *size = 0; + *p = NULL; + *g = NULL; + + rc = ssh_retrieve_dhgroup_file(moduli, + pmin, + pn, + pmax, + size, + &generator, + &modulus); + fclose(moduli); + if (rc == SSH_ERROR || *size == 0) { + goto error; + } + rc = bignum_hex2bn(generator, g); + if (rc == 0) { + goto error; + } + rc = bignum_hex2bn(modulus, p); + if (rc == 0) { + goto error; + } + SAFE_FREE(generator); + SAFE_FREE(modulus); + + return SSH_OK; + +error: + bignum_safe_free(*g); + bignum_safe_free(*p); + SAFE_FREE(generator); + SAFE_FREE(modulus); + + return SSH_ERROR; +} + +static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request); +static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_init); + +static ssh_packet_callback dhgex_server_callbacks[]= { + NULL, /* SSH_MSG_KEX_DH_GEX_REQUEST_OLD */ + NULL, /* SSH_MSG_KEX_DH_GEX_GROUP */ + ssh_packet_server_dhgex_init, /* SSH_MSG_KEX_DH_GEX_INIT */ + NULL, /* SSH_MSG_KEX_DH_GEX_REPLY */ + ssh_packet_server_dhgex_request /* SSH_MSG_GEX_DH_GEX_REQUEST */ + +}; + +static struct ssh_packet_callbacks_struct ssh_dhgex_server_callbacks = { + .start = SSH2_MSG_KEX_DH_GEX_REQUEST_OLD, + .n_callbacks = 5, + .callbacks = dhgex_server_callbacks, + .user = NULL +}; + +/** @internal + * @brief sets up the diffie-hellman-groupx kex callbacks + */ +void ssh_server_dhgex_init(ssh_session session){ + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_dhgex_server_callbacks); + ssh_dh_init_common(session->next_crypto); + session->dh_handshake_state = DH_STATE_INIT; +} + +static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request) +{ + bignum modulus = NULL, generator = NULL; + uint32_t pmin, pn, pmax; + size_t size = 0; + int rc; + + (void) type; + (void) user; + + if (session->dh_handshake_state != DH_STATE_INIT) { + ssh_set_error(session, + SSH_FATAL, + "Received DH_GEX_REQUEST in invalid state"); + goto error; + } + + /* Minimum group size, preferred group size, maximum group size */ + rc = ssh_buffer_unpack(packet, "ddd", &pmin, &pn, &pmax); + if (rc != SSH_OK){ + ssh_set_error_invalid(session); + goto error; + } + SSH_LOG(SSH_LOG_INFO, "dh-gex: DHGEX_REQUEST[%u:%u:%u]", 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]", + pmin, + pn, + pmax); + goto error; + } + session->next_crypto->dh_pmin = pmin; + session->next_crypto->dh_pn = pn; + session->next_crypto->dh_pmax = pmax; + + /* ensure safe parameters */ + if (pmin < DH_PMIN) { + pmin = DH_PMIN; + if (pn < pmin) { + pn = pmin; + } + } + rc = ssh_retrieve_dhgroup(pmin, + pn, + pmax, + &size, + &modulus, + &generator); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Couldn't find DH group for [%u:%u:%u]", + pmin, + pn, + pmax); + goto error; + } + rc = ssh_dh_set_parameters(session->next_crypto->dh_ctx, + modulus, generator); + if (rc != SSH_OK) { + bignum_safe_free(generator); + bignum_safe_free(modulus); + goto error; + } + rc = ssh_buffer_pack(session->out_buffer, + "bBB", + SSH2_MSG_KEX_DH_GEX_GROUP, + modulus, + generator); + +#ifdef HAVE_LIBCRYPTO + bignum_safe_free(generator); + bignum_safe_free(modulus); +#endif + + if (rc != SSH_OK) { + ssh_set_error_invalid(session); + goto error; + } + + session->dh_handshake_state = DH_STATE_GROUP_SENT; + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + +error: + return SSH_PACKET_USED; +} + +/** @internal + * @brief parse an incoming SSH_MSG_KEX_DH_GEX_INIT packet and complete + * Diffie-Hellman key exchange + **/ +static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_init){ + (void) type; + (void) user; + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_KEX_DHGEX_INIT"); + ssh_packet_remove_callbacks(session, &ssh_dhgex_server_callbacks); + ssh_server_dh_process_init(session, packet); + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ diff --git a/src/dh.c b/src/dh.c new file mode 100644 index 0000000..05903da --- /dev/null +++ b/src/dh.c @@ -0,0 +1,807 @@ +/* + * dh.c - Diffie-Helman algorithm code against SSH 2 + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2018 by Aris Adamantiadis + * Copyright (c) 2009-2013 by Andreas Schneider + * Copyright (c) 2012 by Dmitriy Kuznetsov + * + * 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/priv.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/dh.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" + +static unsigned char p_group1_value[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +#define P_GROUP1_LEN 128 /* Size in bytes of the p number */ + +static unsigned char p_group14_value[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, + 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, + 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, + 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, + 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C, + 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF}; + +#define P_GROUP14_LEN 256 /* Size in bytes of the p number for group 14 */ + +static unsigned char p_group16_value[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, + 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, + 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, + 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, + 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C, + 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAA, 0xC4, 0x2D, 0xAD, 0x33, 0x17, 0x0D, + 0x04, 0x50, 0x7A, 0x33, 0xA8, 0x55, 0x21, 0xAB, 0xDF, 0x1C, 0xBA, 0x64, + 0xEC, 0xFB, 0x85, 0x04, 0x58, 0xDB, 0xEF, 0x0A, 0x8A, 0xEA, 0x71, 0x57, + 0x5D, 0x06, 0x0C, 0x7D, 0xB3, 0x97, 0x0F, 0x85, 0xA6, 0xE1, 0xE4, 0xC7, + 0xAB, 0xF5, 0xAE, 0x8C, 0xDB, 0x09, 0x33, 0xD7, 0x1E, 0x8C, 0x94, 0xE0, + 0x4A, 0x25, 0x61, 0x9D, 0xCE, 0xE3, 0xD2, 0x26, 0x1A, 0xD2, 0xEE, 0x6B, + 0xF1, 0x2F, 0xFA, 0x06, 0xD9, 0x8A, 0x08, 0x64, 0xD8, 0x76, 0x02, 0x73, + 0x3E, 0xC8, 0x6A, 0x64, 0x52, 0x1F, 0x2B, 0x18, 0x17, 0x7B, 0x20, 0x0C, + 0xBB, 0xE1, 0x17, 0x57, 0x7A, 0x61, 0x5D, 0x6C, 0x77, 0x09, 0x88, 0xC0, + 0xBA, 0xD9, 0x46, 0xE2, 0x08, 0xE2, 0x4F, 0xA0, 0x74, 0xE5, 0xAB, 0x31, + 0x43, 0xDB, 0x5B, 0xFC, 0xE0, 0xFD, 0x10, 0x8E, 0x4B, 0x82, 0xD1, 0x20, + 0xA9, 0x21, 0x08, 0x01, 0x1A, 0x72, 0x3C, 0x12, 0xA7, 0x87, 0xE6, 0xD7, + 0x88, 0x71, 0x9A, 0x10, 0xBD, 0xBA, 0x5B, 0x26, 0x99, 0xC3, 0x27, 0x18, + 0x6A, 0xF4, 0xE2, 0x3C, 0x1A, 0x94, 0x68, 0x34, 0xB6, 0x15, 0x0B, 0xDA, + 0x25, 0x83, 0xE9, 0xCA, 0x2A, 0xD4, 0x4C, 0xE8, 0xDB, 0xBB, 0xC2, 0xDB, + 0x04, 0xDE, 0x8E, 0xF9, 0x2E, 0x8E, 0xFC, 0x14, 0x1F, 0xBE, 0xCA, 0xA6, + 0x28, 0x7C, 0x59, 0x47, 0x4E, 0x6B, 0xC0, 0x5D, 0x99, 0xB2, 0x96, 0x4F, + 0xA0, 0x90, 0xC3, 0xA2, 0x23, 0x3B, 0xA1, 0x86, 0x51, 0x5B, 0xE7, 0xED, + 0x1F, 0x61, 0x29, 0x70, 0xCE, 0xE2, 0xD7, 0xAF, 0xB8, 0x1B, 0xDD, 0x76, + 0x21, 0x70, 0x48, 0x1C, 0xD0, 0x06, 0x91, 0x27, 0xD5, 0xB0, 0x5A, 0xA9, + 0x93, 0xB4, 0xEA, 0x98, 0x8D, 0x8F, 0xDD, 0xC1, 0x86, 0xFF, 0xB7, 0xDC, + 0x90, 0xA6, 0xC0, 0x8F, 0x4D, 0xF4, 0x35, 0xC9, 0x34, 0x06, 0x31, 0x99, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +#define P_GROUP16_LEN 512 /* Size in bytes of the p number for group 16 */ + +static unsigned char p_group18_value[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, + 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, + 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, + 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, + 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C, + 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAA, 0xC4, 0x2D, 0xAD, 0x33, 0x17, 0x0D, + 0x04, 0x50, 0x7A, 0x33, 0xA8, 0x55, 0x21, 0xAB, 0xDF, 0x1C, 0xBA, 0x64, + 0xEC, 0xFB, 0x85, 0x04, 0x58, 0xDB, 0xEF, 0x0A, 0x8A, 0xEA, 0x71, 0x57, + 0x5D, 0x06, 0x0C, 0x7D, 0xB3, 0x97, 0x0F, 0x85, 0xA6, 0xE1, 0xE4, 0xC7, + 0xAB, 0xF5, 0xAE, 0x8C, 0xDB, 0x09, 0x33, 0xD7, 0x1E, 0x8C, 0x94, 0xE0, + 0x4A, 0x25, 0x61, 0x9D, 0xCE, 0xE3, 0xD2, 0x26, 0x1A, 0xD2, 0xEE, 0x6B, + 0xF1, 0x2F, 0xFA, 0x06, 0xD9, 0x8A, 0x08, 0x64, 0xD8, 0x76, 0x02, 0x73, + 0x3E, 0xC8, 0x6A, 0x64, 0x52, 0x1F, 0x2B, 0x18, 0x17, 0x7B, 0x20, 0x0C, + 0xBB, 0xE1, 0x17, 0x57, 0x7A, 0x61, 0x5D, 0x6C, 0x77, 0x09, 0x88, 0xC0, + 0xBA, 0xD9, 0x46, 0xE2, 0x08, 0xE2, 0x4F, 0xA0, 0x74, 0xE5, 0xAB, 0x31, + 0x43, 0xDB, 0x5B, 0xFC, 0xE0, 0xFD, 0x10, 0x8E, 0x4B, 0x82, 0xD1, 0x20, + 0xA9, 0x21, 0x08, 0x01, 0x1A, 0x72, 0x3C, 0x12, 0xA7, 0x87, 0xE6, 0xD7, + 0x88, 0x71, 0x9A, 0x10, 0xBD, 0xBA, 0x5B, 0x26, 0x99, 0xC3, 0x27, 0x18, + 0x6A, 0xF4, 0xE2, 0x3C, 0x1A, 0x94, 0x68, 0x34, 0xB6, 0x15, 0x0B, 0xDA, + 0x25, 0x83, 0xE9, 0xCA, 0x2A, 0xD4, 0x4C, 0xE8, 0xDB, 0xBB, 0xC2, 0xDB, + 0x04, 0xDE, 0x8E, 0xF9, 0x2E, 0x8E, 0xFC, 0x14, 0x1F, 0xBE, 0xCA, 0xA6, + 0x28, 0x7C, 0x59, 0x47, 0x4E, 0x6B, 0xC0, 0x5D, 0x99, 0xB2, 0x96, 0x4F, + 0xA0, 0x90, 0xC3, 0xA2, 0x23, 0x3B, 0xA1, 0x86, 0x51, 0x5B, 0xE7, 0xED, + 0x1F, 0x61, 0x29, 0x70, 0xCE, 0xE2, 0xD7, 0xAF, 0xB8, 0x1B, 0xDD, 0x76, + 0x21, 0x70, 0x48, 0x1C, 0xD0, 0x06, 0x91, 0x27, 0xD5, 0xB0, 0x5A, 0xA9, + 0x93, 0xB4, 0xEA, 0x98, 0x8D, 0x8F, 0xDD, 0xC1, 0x86, 0xFF, 0xB7, 0xDC, + 0x90, 0xA6, 0xC0, 0x8F, 0x4D, 0xF4, 0x35, 0xC9, 0x34, 0x02, 0x84, 0x92, + 0x36, 0xC3, 0xFA, 0xB4, 0xD2, 0x7C, 0x70, 0x26, 0xC1, 0xD4, 0xDC, 0xB2, + 0x60, 0x26, 0x46, 0xDE, 0xC9, 0x75, 0x1E, 0x76, 0x3D, 0xBA, 0x37, 0xBD, + 0xF8, 0xFF, 0x94, 0x06, 0xAD, 0x9E, 0x53, 0x0E, 0xE5, 0xDB, 0x38, 0x2F, + 0x41, 0x30, 0x01, 0xAE, 0xB0, 0x6A, 0x53, 0xED, 0x90, 0x27, 0xD8, 0x31, + 0x17, 0x97, 0x27, 0xB0, 0x86, 0x5A, 0x89, 0x18, 0xDA, 0x3E, 0xDB, 0xEB, + 0xCF, 0x9B, 0x14, 0xED, 0x44, 0xCE, 0x6C, 0xBA, 0xCE, 0xD4, 0xBB, 0x1B, + 0xDB, 0x7F, 0x14, 0x47, 0xE6, 0xCC, 0x25, 0x4B, 0x33, 0x20, 0x51, 0x51, + 0x2B, 0xD7, 0xAF, 0x42, 0x6F, 0xB8, 0xF4, 0x01, 0x37, 0x8C, 0xD2, 0xBF, + 0x59, 0x83, 0xCA, 0x01, 0xC6, 0x4B, 0x92, 0xEC, 0xF0, 0x32, 0xEA, 0x15, + 0xD1, 0x72, 0x1D, 0x03, 0xF4, 0x82, 0xD7, 0xCE, 0x6E, 0x74, 0xFE, 0xF6, + 0xD5, 0x5E, 0x70, 0x2F, 0x46, 0x98, 0x0C, 0x82, 0xB5, 0xA8, 0x40, 0x31, + 0x90, 0x0B, 0x1C, 0x9E, 0x59, 0xE7, 0xC9, 0x7F, 0xBE, 0xC7, 0xE8, 0xF3, + 0x23, 0xA9, 0x7A, 0x7E, 0x36, 0xCC, 0x88, 0xBE, 0x0F, 0x1D, 0x45, 0xB7, + 0xFF, 0x58, 0x5A, 0xC5, 0x4B, 0xD4, 0x07, 0xB2, 0x2B, 0x41, 0x54, 0xAA, + 0xCC, 0x8F, 0x6D, 0x7E, 0xBF, 0x48, 0xE1, 0xD8, 0x14, 0xCC, 0x5E, 0xD2, + 0x0F, 0x80, 0x37, 0xE0, 0xA7, 0x97, 0x15, 0xEE, 0xF2, 0x9B, 0xE3, 0x28, + 0x06, 0xA1, 0xD5, 0x8B, 0xB7, 0xC5, 0xDA, 0x76, 0xF5, 0x50, 0xAA, 0x3D, + 0x8A, 0x1F, 0xBF, 0xF0, 0xEB, 0x19, 0xCC, 0xB1, 0xA3, 0x13, 0xD5, 0x5C, + 0xDA, 0x56, 0xC9, 0xEC, 0x2E, 0xF2, 0x96, 0x32, 0x38, 0x7F, 0xE8, 0xD7, + 0x6E, 0x3C, 0x04, 0x68, 0x04, 0x3E, 0x8F, 0x66, 0x3F, 0x48, 0x60, 0xEE, + 0x12, 0xBF, 0x2D, 0x5B, 0x0B, 0x74, 0x74, 0xD6, 0xE6, 0x94, 0xF9, 0x1E, + 0x6D, 0xBE, 0x11, 0x59, 0x74, 0xA3, 0x92, 0x6F, 0x12, 0xFE, 0xE5, 0xE4, + 0x38, 0x77, 0x7C, 0xB6, 0xA9, 0x32, 0xDF, 0x8C, 0xD8, 0xBE, 0xC4, 0xD0, + 0x73, 0xB9, 0x31, 0xBA, 0x3B, 0xC8, 0x32, 0xB6, 0x8D, 0x9D, 0xD3, 0x00, + 0x74, 0x1F, 0xA7, 0xBF, 0x8A, 0xFC, 0x47, 0xED, 0x25, 0x76, 0xF6, 0x93, + 0x6B, 0xA4, 0x24, 0x66, 0x3A, 0xAB, 0x63, 0x9C, 0x5A, 0xE4, 0xF5, 0x68, + 0x34, 0x23, 0xB4, 0x74, 0x2B, 0xF1, 0xC9, 0x78, 0x23, 0x8F, 0x16, 0xCB, + 0xE3, 0x9D, 0x65, 0x2D, 0xE3, 0xFD, 0xB8, 0xBE, 0xFC, 0x84, 0x8A, 0xD9, + 0x22, 0x22, 0x2E, 0x04, 0xA4, 0x03, 0x7C, 0x07, 0x13, 0xEB, 0x57, 0xA8, + 0x1A, 0x23, 0xF0, 0xC7, 0x34, 0x73, 0xFC, 0x64, 0x6C, 0xEA, 0x30, 0x6B, + 0x4B, 0xCB, 0xC8, 0x86, 0x2F, 0x83, 0x85, 0xDD, 0xFA, 0x9D, 0x4B, 0x7F, + 0xA2, 0xC0, 0x87, 0xE8, 0x79, 0x68, 0x33, 0x03, 0xED, 0x5B, 0xDD, 0x3A, + 0x06, 0x2B, 0x3C, 0xF5, 0xB3, 0xA2, 0x78, 0xA6, 0x6D, 0x2A, 0x13, 0xF8, + 0x3F, 0x44, 0xF8, 0x2D, 0xDF, 0x31, 0x0E, 0xE0, 0x74, 0xAB, 0x6A, 0x36, + 0x45, 0x97, 0xE8, 0x99, 0xA0, 0x25, 0x5D, 0xC1, 0x64, 0xF3, 0x1C, 0xC5, + 0x08, 0x46, 0x85, 0x1D, 0xF9, 0xAB, 0x48, 0x19, 0x5D, 0xED, 0x7E, 0xA1, + 0xB1, 0xD5, 0x10, 0xBD, 0x7E, 0xE7, 0x4D, 0x73, 0xFA, 0xF3, 0x6B, 0xC3, + 0x1E, 0xCF, 0xA2, 0x68, 0x35, 0x90, 0x46, 0xF4, 0xEB, 0x87, 0x9F, 0x92, + 0x40, 0x09, 0x43, 0x8B, 0x48, 0x1C, 0x6C, 0xD7, 0x88, 0x9A, 0x00, 0x2E, + 0xD5, 0xEE, 0x38, 0x2B, 0xC9, 0x19, 0x0D, 0xA6, 0xFC, 0x02, 0x6E, 0x47, + 0x95, 0x58, 0xE4, 0x47, 0x56, 0x77, 0xE9, 0xAA, 0x9E, 0x30, 0x50, 0xE2, + 0x76, 0x56, 0x94, 0xDF, 0xC8, 0x1F, 0x56, 0xE8, 0x80, 0xB9, 0x6E, 0x71, + 0x60, 0xC9, 0x80, 0xDD, 0x98, 0xED, 0xD3, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF}; + +#define P_GROUP18_LEN 1024 /* Size in bytes of the p number for group 18 */ + +bignum ssh_dh_generator; +bignum ssh_dh_group1; +bignum ssh_dh_group14; +bignum ssh_dh_group16; +bignum ssh_dh_group18; +static int dh_crypto_initialized; + +/** + * @internal + * @brief Initialize global constants used in DH key agreement + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_dh_init(void) +{ + unsigned long g_int = 2 ; /* G is defined as 2 by the ssh2 standards */ + int rc; + if (dh_crypto_initialized) { + return SSH_OK; + } + dh_crypto_initialized = 1; + + ssh_dh_generator = bignum_new(); + if (ssh_dh_generator == NULL) { + goto error; + } + rc = bignum_set_word(ssh_dh_generator, g_int); + if (rc != 1) { + goto error; + } + + bignum_bin2bn(p_group1_value, P_GROUP1_LEN, &ssh_dh_group1); + if (ssh_dh_group1 == NULL) { + goto error; + } + bignum_bin2bn(p_group14_value, P_GROUP14_LEN, &ssh_dh_group14); + if (ssh_dh_group14 == NULL) { + goto error; + } + bignum_bin2bn(p_group16_value, P_GROUP16_LEN, &ssh_dh_group16); + if (ssh_dh_group16 == NULL) { + goto error; + } + bignum_bin2bn(p_group18_value, P_GROUP18_LEN, &ssh_dh_group18); + if (ssh_dh_group18 == NULL) { + goto error; + } + + return 0; +error: + ssh_dh_finalize(); + return SSH_ERROR; +} + +/** + * @internal + * @brief Finalize and free global constants used in DH key agreement + */ +void ssh_dh_finalize(void) +{ + if (!dh_crypto_initialized) { + return; + } + + bignum_safe_free(ssh_dh_generator); + bignum_safe_free(ssh_dh_group1); + bignum_safe_free(ssh_dh_group14); + bignum_safe_free(ssh_dh_group16); + bignum_safe_free(ssh_dh_group18); + + dh_crypto_initialized = 0; +} + +int ssh_dh_import_next_pubkey_blob(ssh_session session, ssh_string pubkey_blob) +{ + return ssh_pki_import_pubkey_blob(pubkey_blob, + &session->next_crypto->server_pubkey); + +} + +static SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply); + +static ssh_packet_callback dh_client_callbacks[]= { + ssh_packet_client_dh_reply +}; + +static struct ssh_packet_callbacks_struct ssh_dh_client_callbacks = { + .start = SSH2_MSG_KEXDH_REPLY, + .n_callbacks = 1, + .callbacks = dh_client_callbacks, + .user = NULL +}; + +/** @internal + * @brief Starts diffie-hellman-group1 key exchange + */ +int ssh_client_dh_init(ssh_session session){ + struct ssh_crypto_struct *crypto = session->next_crypto; + const_bignum pubkey; + int rc; + + rc = ssh_dh_init_common(crypto); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_dh_keypair_gen_keys(crypto->dh_ctx, DH_CLIENT_KEYPAIR); + if (rc == SSH_ERROR){ + goto error; + } + rc = ssh_dh_keypair_get_keys(crypto->dh_ctx, DH_CLIENT_KEYPAIR, + NULL, &pubkey); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_buffer_pack(session->out_buffer, "bB", SSH2_MSG_KEXDH_INIT, pubkey); + if (rc != SSH_OK) { + goto error; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_dh_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + return rc; +error: + ssh_dh_cleanup(crypto); + return SSH_ERROR; +} + +SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ + struct ssh_crypto_struct *crypto=session->next_crypto; + ssh_string pubkey_blob = NULL; + bignum server_pubkey; + int rc; + + (void)type; + (void)user; + + ssh_packet_remove_callbacks(session, &ssh_dh_client_callbacks); + + rc = ssh_buffer_unpack(packet, "SBS", &pubkey_blob, &server_pubkey, + &crypto->dh_server_signature); + if (rc == SSH_ERROR) { + goto error; + } + rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, DH_SERVER_KEYPAIR, + NULL, server_pubkey); + if (rc != SSH_OK) { + bignum_safe_free(server_pubkey); + goto error; + } + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + 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); + ssh_dh_debug_crypto(session->next_crypto); + if (rc == SSH_ERROR){ + ssh_set_error(session, SSH_FATAL, "Could not generate shared secret"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { + goto error; + } + + rc=ssh_packet_send(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_dh_cleanup(session->next_crypto); + session->session_state=SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +static SSH_PACKET_CALLBACK(ssh_packet_server_dh_init); + +static ssh_packet_callback dh_server_callbacks[] = { + ssh_packet_server_dh_init, +}; + +static struct ssh_packet_callbacks_struct ssh_dh_server_callbacks = { + .start = SSH2_MSG_KEXDH_INIT, + .n_callbacks = 1, + .callbacks = dh_server_callbacks, + .user = NULL +}; + +/** @internal + * @brief sets up the diffie-hellman-groupx kex callbacks + */ +void ssh_server_dh_init(ssh_session session){ + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_dh_server_callbacks); + + ssh_dh_init_common(session->next_crypto); +} + +/** @internal + * @brief processes a SSH_MSG_KEXDH_INIT or SSH_MSG_KEX_DH_GEX_INIT packet and sends + * the appropriate SSH_MSG_KEXDH_REPLY or SSH_MSG_KEX_DH_GEX_REPLY + */ +int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string sig_blob = NULL; + ssh_string pubkey_blob = NULL; + bignum client_pubkey; + const_bignum server_pubkey; + int packet_type; + int rc; + + rc = ssh_buffer_unpack(packet, "B", &client_pubkey); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "No e number in client request"); + goto error; + } + + rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, DH_CLIENT_KEYPAIR, + NULL, client_pubkey); + if (rc != SSH_OK) { + bignum_safe_free(client_pubkey); + goto error; + } + + rc = ssh_dh_keypair_gen_keys(crypto->dh_ctx, DH_SERVER_KEYPAIR); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_dh_compute_shared_secret(crypto->dh_ctx, + DH_SERVER_KEYPAIR, DH_CLIENT_KEYPAIR, + &crypto->shared_secret); + ssh_dh_debug_crypto(crypto); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "Could not generate shared secret"); + goto error; + } + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto error; + } + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + goto error; + } + switch (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: + packet_type = SSH2_MSG_KEXDH_REPLY; + break; +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + packet_type = SSH2_MSG_KEX_DH_GEX_REPLY; + break; +#endif /* WITH_GEX */ + default: + ssh_set_error(session, SSH_FATAL, "Invalid kex type"); + goto error; + } + rc = ssh_dh_keypair_get_keys(crypto->dh_ctx, DH_SERVER_KEYPAIR, + NULL, &server_pubkey); + if (rc != SSH_OK){ + goto error; + } + rc = ssh_dh_get_next_server_publickey_blob(session, &pubkey_blob); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + goto error; + } + rc = ssh_buffer_pack(session->out_buffer, + "bSBS", + packet_type, + pubkey_blob, + server_pubkey, + sig_blob); + SSH_STRING_FREE(sig_blob); + SSH_STRING_FREE(pubkey_blob); + if(rc != SSH_OK) { + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + goto error; + } + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + 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) { + 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); + + session->session_state = SSH_SESSION_STATE_ERROR; + ssh_dh_cleanup(session->next_crypto); + return SSH_ERROR; +} + +/** @internal + * @brief parse an incoming SSH_MSG_KEXDH_INIT packet and complete + * Diffie-Hellman key exchange + **/ +static SSH_PACKET_CALLBACK(ssh_packet_server_dh_init){ + (void)type; + (void)user; + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_KEXDH_INIT"); + ssh_packet_remove_callbacks(session, &ssh_dh_server_callbacks); + ssh_server_dh_process_init(session, packet); + return SSH_PACKET_USED; +} + +/** @internal + * @brief Choose a fallback group for the DH Group exchange if the + * moduli file is not readable + * @param[in] pmax maximum requestsd group size + * @param[out] modulus + * @param[out] generator + * @returns SSH_OK on success, SSH_ERROR otherwise + */ +int ssh_fallback_group(uint32_t pmax, + bignum *modulus, + bignum *generator) +{ + *modulus = NULL; + *generator = NULL; + + if (pmax < 3072) { + bignum_dup(ssh_dh_group14, modulus); + } else if (pmax < 6144) { + bignum_dup(ssh_dh_group16, modulus); + } else { + bignum_dup(ssh_dh_group18, modulus); + } + if (*modulus == NULL) { + return SSH_ERROR; + } + + bignum_dup(ssh_dh_generator, generator); + if (*generator == NULL) { + bignum_safe_free((*modulus)); + return SSH_ERROR; + } + + return SSH_OK; +} + +#endif /* WITH_SERVER */ + +/** + * @addtogroup libssh_session + * + * @{ + */ + +bool ssh_dh_is_known_group(bignum modulus, bignum generator) +{ + int cmp, bits; + bignum m = NULL; + + bits = bignum_num_bits(modulus); + if (bits < 3072) { + m = ssh_dh_group14; + } else if (bits < 6144) { + m = ssh_dh_group16; + } else { + m = ssh_dh_group18; + } + + cmp = bignum_cmp(m, modulus); + if (cmp != 0) { + return false; + } + + cmp = bignum_cmp(ssh_dh_generator, generator); + if (cmp != 0) { + return false; + } + + SSH_LOG(SSH_LOG_TRACE, "The received primes in FIPS are known"); + return true; +} + +ssh_key ssh_dh_get_current_server_publickey(ssh_session session) +{ + if (session->current_crypto == NULL) { + return NULL; + } + + return session->current_crypto->server_pubkey; +} + +/* Caller need to free the blob */ +int ssh_dh_get_current_server_publickey_blob(ssh_session session, + ssh_string *pubkey_blob) +{ + const ssh_key pubkey = ssh_dh_get_current_server_publickey(session); + + return ssh_pki_export_pubkey_blob(pubkey, pubkey_blob); +} + +ssh_key ssh_dh_get_next_server_publickey(ssh_session session) +{ + return session->next_crypto->server_pubkey; +} + +/* Caller need to free the blob */ +int ssh_dh_get_next_server_publickey_blob(ssh_session session, + ssh_string *pubkey_blob) +{ + const ssh_key pubkey = ssh_dh_get_next_server_publickey(session); + + return ssh_pki_export_pubkey_blob(pubkey, pubkey_blob); +} + +/** + * @internal + * + * @brief Convert a buffer into an unpadded base64 string. + * The caller has to free the memory. + * + * @param hash What should be converted to a base64 string. + * + * @param len Length of the buffer to convert. + * + * @return The base64 string or NULL on error. + * + * @see ssh_string_free_char() + */ +static char *ssh_get_b64_unpadded(const unsigned char *hash, size_t len) +{ + char *b64_padded = NULL; + char *b64_unpadded = NULL; + size_t k; + + b64_padded = (char *)bin_to_base64(hash, (int)len); + if (b64_padded == NULL) { + return NULL; + } + for (k = strlen(b64_padded); k != 0 && b64_padded[k-1] == '='; k--); + + b64_unpadded = strndup(b64_padded, k); + SAFE_FREE(b64_padded); + + return b64_unpadded; +} + +/** + * @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 + * string. Either way, the output is prepended by the hash-type. + * + * @warning Do NOT use MD5 or SHA1! Those hash functions are being deprecated. + * + * @param type Which sort of hash is given, use + * SSH_PUBLICKEY_HASH_SHA256 or better. + * + * @param hash The hash to be converted to fingerprint. + * + * @param len Length of the buffer to convert. + * + * @return Returns the allocated fingerprint hash or NULL on error. + * + * @see ssh_string_free_char() + */ +char *ssh_get_fingerprint_hash(enum ssh_publickey_hash_type type, + unsigned char *hash, + size_t len) +{ + const char *prefix = "UNKNOWN"; + char *fingerprint = NULL; + char *str = NULL; + size_t str_len; + int rc; + + switch (type) { + case SSH_PUBLICKEY_HASH_SHA1: + case SSH_PUBLICKEY_HASH_SHA256: + fingerprint = ssh_get_b64_unpadded(hash, len); + break; + case SSH_PUBLICKEY_HASH_MD5: + fingerprint = ssh_get_hexa(hash, len); + break; + } + if (fingerprint == NULL) { + return NULL; + } + + switch (type) { + case SSH_PUBLICKEY_HASH_MD5: + prefix = "MD5"; + break; + case SSH_PUBLICKEY_HASH_SHA1: + prefix = "SHA1"; + break; + case SSH_PUBLICKEY_HASH_SHA256: + prefix = "SHA256"; + break; + } + + str_len = strlen(prefix); + if (str_len + 1 + strlen(fingerprint) + 1 < str_len) { + SAFE_FREE(fingerprint); + return NULL; + } + str_len += 1 + strlen(fingerprint) + 1; + + str = malloc(str_len); + if (str == NULL) { + SAFE_FREE(fingerprint); + return NULL; + } + rc = snprintf(str, str_len, "%s:%s", prefix, fingerprint); + SAFE_FREE(fingerprint); + if (rc < 0 || rc < (int)(str_len - 1)) { + SAFE_FREE(str); + } + + return str; +} + +/** + * @brief Print a hash as a human-readable hex- or base64-string. + * + * This prints an unpadded base64 strings for SHA sums and hex strings for MD5 + * sum. Either way, the output is prepended by the hash-type. + * + * @param type Which sort of hash is given. Use + * SSH_PUBLICKEY_HASH_SHA256 or better. + * + * @param hash The hash to be converted to fingerprint. + * + * @param len Length of the buffer to convert. + * + * @see ssh_get_publickey_hash() + * @see ssh_get_fingerprint_hash() + */ +void ssh_print_hash(enum ssh_publickey_hash_type type, + unsigned char *hash, + size_t len) +{ + char *fingerprint = NULL; + + fingerprint = ssh_get_fingerprint_hash(type, + hash, + len); + if (fingerprint == NULL) { + return; + } + + fprintf(stderr, "%s\n", fingerprint); + + SAFE_FREE(fingerprint); +} + +/** @} */ diff --git a/src/dh_crypto.c b/src/dh_crypto.c new file mode 100644 index 0000000..3b3495c --- /dev/null +++ b/src/dh_crypto.c @@ -0,0 +1,287 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Simo Sorce - Red Hat, Inc. + * + * 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/session.h" +#include "libssh/dh.h" +#include "libssh/buffer.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" + +#include "openssl/crypto.h" +#include "openssl/dh.h" +#include "libcrypto-compat.h" + +extern bignum ssh_dh_generator; +extern bignum ssh_dh_group1; +extern bignum ssh_dh_group14; +extern bignum ssh_dh_group16; +extern bignum ssh_dh_group18; + +struct dh_ctx { + DH *keypair[2]; +}; + +void ssh_dh_debug_crypto(struct ssh_crypto_struct *c) +{ +#ifdef DEBUG_CRYPTO + const_bignum x = NULL, y = NULL, e = NULL, f = NULL; + + 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); + ssh_print_bignum("x", x); + ssh_print_bignum("y", y); + ssh_print_bignum("e", e); + ssh_print_bignum("f", f); + + ssh_log_hexdump("Session server cookie", c->server_kex.cookie, 16); + ssh_log_hexdump("Session client cookie", c->client_kex.cookie, 16); + ssh_print_bignum("k", c->shared_secret); + +#else + (void)c; /* UNUSED_PARAM */ +#endif +} + +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + const_bignum *priv, const_bignum *pub) +{ + if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || + ((priv == NULL) && (pub == NULL)) || (ctx == NULL) || + (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; +} + +int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, + const bignum priv, const bignum pub) +{ + bignum priv_key = NULL; + bignum pub_key = NULL; + + if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || + ((priv == NULL) && (pub == NULL)) || (ctx == NULL) || + (ctx->keypair[peer] == NULL)) { + return SSH_ERROR; + } + + if (priv) { + priv_key = priv; + } + if (pub) { + pub_key = pub; + } + (void)DH_set0_key(ctx->keypair[peer], pub_key, priv_key); + + return SSH_OK; +} + +int ssh_dh_get_parameters(struct dh_ctx *ctx, + const_bignum *modulus, const_bignum *generator) +{ + if (ctx == NULL || ctx->keypair[0] == NULL) { + return SSH_ERROR; + } + DH_get0_pqg(ctx->keypair[0], modulus, NULL, generator); + return SSH_OK; +} + +int ssh_dh_set_parameters(struct dh_ctx *ctx, + const bignum modulus, const bignum generator) +{ + size_t i; + int rc; + + if ((ctx == NULL) || (modulus == NULL) || (generator == NULL)) { + return SSH_ERROR; + } + + for (i = 0; i < 2; i++) { + bignum p = NULL; + bignum g = NULL; + + /* when setting modulus or generator, + * make sure to invalidate existing keys */ + DH_free(ctx->keypair[i]); + ctx->keypair[i] = DH_new(); + if (ctx->keypair[i] == NULL) { + rc = SSH_ERROR; + goto done; + } + + p = BN_dup(modulus); + g = BN_dup(generator); + rc = DH_set0_pqg(ctx->keypair[i], p, NULL, g); + if (rc != 1) { + BN_free(p); + BN_free(g); + rc = SSH_ERROR; + goto done; + } + } + + rc = SSH_OK; +done: + if (rc != SSH_OK) { + DH_free(ctx->keypair[0]); + DH_free(ctx->keypair[1]); + ctx->keypair[0] = NULL; + ctx->keypair[1] = NULL; + } + return rc; +} + +/** + * @internal + * @brief allocate and initialize ephemeral values used in dh kex + */ +int ssh_dh_init_common(struct ssh_crypto_struct *crypto) +{ + struct dh_ctx *ctx; + int rc; + + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return SSH_ERROR; + } + crypto->dh_ctx = ctx; + + switch (crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group1, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group14, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP16_SHA512: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group16, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP18_SHA512: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group18, ssh_dh_generator); + break; + default: + rc = SSH_OK; + break; + } + + if (rc != SSH_OK) { + ssh_dh_cleanup(crypto); + } + return rc; +} + +void ssh_dh_cleanup(struct ssh_crypto_struct *crypto) +{ + if (crypto->dh_ctx != NULL) { + DH_free(crypto->dh_ctx->keypair[0]); + DH_free(crypto->dh_ctx->keypair[1]); + free(crypto->dh_ctx); + crypto->dh_ctx = NULL; + } +} + +/** @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 peer Select either client or server key storage. Valid values are: + * DH_CLIENT_KEYPAIR or DH_SERVER_KEYPAIR + * + * @return SSH_OK on success, SSH_ERROR on error + */ +int ssh_dh_keypair_gen_keys(struct dh_ctx *dh_ctx, int peer) +{ + int rc; + + if ((dh_ctx == NULL) || (dh_ctx->keypair[peer] == NULL)) { + return SSH_ERROR; + } + rc = DH_generate_key(dh_ctx->keypair[peer]); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +/** @internal + * @brief generates a shared secret between the local peer and the remote + * peer. The local peer must have been initialized using either the + * ssh_dh_keypair_gen_keys() function or by seetting manually both + * the private and public keys. The remote peer only needs to have + * the remote's peer public key set. + * @param[in] local peer identifier (DH_CLIENT_KEYPAIR or DH_SERVER_KEYPAIR) + * @param[in] remote peer identifier (DH_CLIENT_KEYPAIR or DH_SERVER_KEYPAIR) + * @param[out] dest a new bignum with the shared secret value is returned. + * @return SSH_OK on success, SSH_ERROR on error + */ +int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, + bignum *dest) +{ + unsigned char *kstring = NULL; + const_bignum pub_key = NULL; + int klen, rc; + + if ((dh_ctx == NULL) || + (dh_ctx->keypair[local] == NULL) || + (dh_ctx->keypair[remote] == NULL)) { + return SSH_ERROR; + } + + kstring = malloc(DH_size(dh_ctx->keypair[local])); + if (kstring == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = ssh_dh_keypair_get_keys(dh_ctx, remote, NULL, &pub_key); + if (rc != SSH_OK) { + rc = SSH_ERROR; + goto done; + } + + klen = DH_compute_key(kstring, pub_key, dh_ctx->keypair[local]); + if (klen == -1) { + rc = SSH_ERROR; + goto done; + } + + *dest = BN_bin2bn(kstring, klen, NULL); + if (*dest == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = SSH_OK; +done: + free(kstring); + return rc; +} diff --git a/src/dh_key.c b/src/dh_key.c new file mode 100644 index 0000000..bda54b1 --- /dev/null +++ b/src/dh_key.c @@ -0,0 +1,376 @@ +/* + * dh-int.c - Diffie-Helman algorithm code against SSH 2 + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2018 by Aris Adamantiadis + * Copyright (c) 2009-2013 by Andreas Schneider + * Copyright (c) 2012 by Dmitriy Kuznetsov + * Copyright (c) 2019 by Simo Sorce + * + * 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/priv.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/dh.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" + +extern bignum ssh_dh_generator; +extern bignum ssh_dh_group1; +extern bignum ssh_dh_group14; +extern bignum ssh_dh_group16; +extern bignum ssh_dh_group18; + +/* + * How many bits of security we want for fast DH. DH private key size must be + * twice that size. + */ +#define DH_SECURITY_BITS 512 + +struct dh_keypair { + bignum priv_key; + bignum pub_key; +}; + +struct dh_ctx { + /* 0 is client, 1 is server */ + struct dh_keypair keypair[2]; + bignum generator; + bignum modulus; +}; + +void ssh_dh_debug_crypto(struct ssh_crypto_struct *c) +{ +#ifdef DEBUG_CRYPTO + const_bignum x = NULL, y = NULL, e = NULL, f = NULL; + + 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); + ssh_print_bignum("p", c->dh_ctx->modulus); + ssh_print_bignum("g", c->dh_ctx->generator); + ssh_print_bignum("x", x); + ssh_print_bignum("y", y); + ssh_print_bignum("e", e); + ssh_print_bignum("f", f); + + ssh_log_hexdump("Session server cookie", c->server_kex.cookie, 16); + ssh_log_hexdump("Session client cookie", c->client_kex.cookie, 16); + ssh_print_bignum("k", c->shared_secret); +#else + (void)c; /* UNUSED_PARAM */ +#endif +} + +static void ssh_dh_free_modulus(struct dh_ctx *ctx) +{ + if ((ctx->modulus != ssh_dh_group1) && + (ctx->modulus != ssh_dh_group14) && + (ctx->modulus != ssh_dh_group16) && + (ctx->modulus != ssh_dh_group18)) { + bignum_safe_free(ctx->modulus); + } + ctx->modulus = NULL; +} + +static void ssh_dh_free_generator(struct dh_ctx *ctx) +{ + if (ctx->generator != ssh_dh_generator) { + bignum_safe_free(ctx->generator); + } +} + +static void ssh_dh_free_dh_keypair(struct dh_keypair *keypair) +{ + bignum_safe_free(keypair->priv_key); + bignum_safe_free(keypair->pub_key); +} + +static int ssh_dh_init_dh_keypair(struct dh_keypair *keypair) +{ + int rc; + + keypair->priv_key = bignum_new(); + if (keypair->priv_key == NULL) { + rc = SSH_ERROR; + goto done; + } + keypair->pub_key = bignum_new(); + if (keypair->pub_key == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = SSH_OK; +done: + if (rc != SSH_OK) { + ssh_dh_free_dh_keypair(keypair); + } + return rc; +} + +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + const_bignum *priv, const_bignum *pub) +{ + if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || + ((priv == NULL) && (pub == NULL)) || (ctx == NULL)) { + return SSH_ERROR; + } + + if (priv) { + /* check that we have something in it */ + if (bignum_num_bits(ctx->keypair[peer].priv_key)) { + *priv = ctx->keypair[peer].priv_key; + } else { + return SSH_ERROR; + } + } + + if (pub) { + /* check that we have something in it */ + if (bignum_num_bits(ctx->keypair[peer].pub_key)) { + *pub = ctx->keypair[peer].pub_key; + } else { + return SSH_ERROR; + } + } + + return SSH_OK; +} + +int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, + bignum priv, bignum pub) +{ + if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || + ((priv == NULL) && (pub == NULL)) || (ctx == NULL)) { + return SSH_ERROR; + } + + if (priv) { + bignum_safe_free(ctx->keypair[peer].priv_key); + ctx->keypair[peer].priv_key = priv; + } + if (pub) { + bignum_safe_free(ctx->keypair[peer].pub_key); + ctx->keypair[peer].pub_key = pub; + } + return SSH_OK; +} + +int ssh_dh_get_parameters(struct dh_ctx *ctx, + const_bignum *modulus, const_bignum *generator) +{ + if (ctx == NULL) { + return SSH_ERROR; + } + if (modulus) { + *modulus = ctx->modulus; + } + if (generator) { + *generator = ctx->generator; + } + + return SSH_OK; +} + +int ssh_dh_set_parameters(struct dh_ctx *ctx, + bignum modulus, bignum generator) +{ + int rc; + + if ((ctx == NULL) || ((modulus == NULL) && (generator == NULL))) { + return SSH_ERROR; + } + /* when setting modulus or generator, + * make sure to invalidate existing keys */ + ssh_dh_free_dh_keypair(&ctx->keypair[DH_CLIENT_KEYPAIR]); + ssh_dh_free_dh_keypair(&ctx->keypair[DH_SERVER_KEYPAIR]); + + rc = ssh_dh_init_dh_keypair(&ctx->keypair[DH_CLIENT_KEYPAIR]); + if (rc != SSH_OK) { + goto done; + } + rc = ssh_dh_init_dh_keypair(&ctx->keypair[DH_SERVER_KEYPAIR]); + if (rc != SSH_OK) { + goto done; + } + + if (modulus) { + ssh_dh_free_modulus(ctx); + ctx->modulus = modulus; + } + if (generator) { + ssh_dh_free_generator(ctx); + ctx->generator = generator; + } + +done: + return rc; +} + +/** + * @internal + * @brief allocate and initialize ephemeral values used in dh kex + */ +int ssh_dh_init_common(struct ssh_crypto_struct *crypto) +{ + struct dh_ctx *ctx = NULL; + int rc; + + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return SSH_ERROR; + } + + switch (crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group1, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group14, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP16_SHA512: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group16, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP18_SHA512: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group18, ssh_dh_generator); + break; + default: + rc = SSH_OK; + break; + } + + crypto->dh_ctx = ctx; + + if (rc != SSH_OK) { + ssh_dh_cleanup(crypto); + } + return rc; +} + +void ssh_dh_cleanup(struct ssh_crypto_struct *crypto) +{ + struct dh_ctx *ctx = crypto->dh_ctx; + + if (ctx == NULL) { + return; + } + + ssh_dh_free_dh_keypair(&ctx->keypair[DH_CLIENT_KEYPAIR]); + ssh_dh_free_dh_keypair(&ctx->keypair[DH_SERVER_KEYPAIR]); + + ssh_dh_free_modulus(ctx); + ssh_dh_free_generator(ctx); + free(ctx); + crypto->dh_ctx = NULL; +} + +/** @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 + * 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) +{ + bignum tmp = NULL; + bignum_CTX ctx = NULL; + int rc = 0; + int bits = 0; + int p_bits = 0; + + ctx = bignum_ctx_new(); + if (bignum_ctx_invalid(ctx)){ + goto error; + } + tmp = bignum_new(); + if (tmp == NULL) { + goto error; + } + p_bits = bignum_num_bits(dh_ctx->modulus); + /* we need at most DH_SECURITY_BITS */ + bits = MIN(DH_SECURITY_BITS * 2, p_bits); + /* ensure we're not too close of p so rnd()%p stays uniform */ + if (bits <= p_bits && bits + 64 > p_bits) { + bits += 64; + } + rc = bignum_rand(tmp, bits); + if (rc != 1) { + goto error; + } + rc = bignum_mod(dh_ctx->keypair[peer].priv_key, tmp, dh_ctx->modulus, ctx); + if (rc != 1) { + goto error; + } + /* Now compute the corresponding public key */ + rc = bignum_mod_exp(dh_ctx->keypair[peer].pub_key, dh_ctx->generator, + dh_ctx->keypair[peer].priv_key, dh_ctx->modulus, ctx); + if (rc != 1) { + goto error; + } + bignum_safe_free(tmp); + bignum_ctx_free(ctx); + return SSH_OK; +error: + bignum_safe_free(tmp); + bignum_ctx_free(ctx); + return SSH_ERROR; +} + +/** @internal + * @brief generates a shared secret between the local peer and the remote peer + * @param[in] local peer identifier + * @param[in] remote peer identifier + * @param[out] dest a preallocated bignum where to store parameter + * @return SSH_OK on success, SSH_ERROR on error + */ +int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, + bignum *dest) +{ + int rc; + bignum_CTX ctx = bignum_ctx_new(); + if (bignum_ctx_invalid(ctx)) { + return -1; + } + + if (*dest == NULL) { + *dest = bignum_new(); + if (*dest == NULL) { + rc = 0; + goto done; + } + } + + rc = bignum_mod_exp(*dest, dh_ctx->keypair[remote].pub_key, + dh_ctx->keypair[local].priv_key, + dh_ctx->modulus, ctx); + +done: + bignum_ctx_free(ctx); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} diff --git a/src/ecdh.c b/src/ecdh.c new file mode 100644 index 0000000..a4c07cc --- /dev/null +++ b/src/ecdh.c @@ -0,0 +1,131 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011-2013 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/session.h" +#include "libssh/ecdh.h" +#include "libssh/dh.h" +#include "libssh/buffer.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" + +#ifdef HAVE_ECDH + +static SSH_PACKET_CALLBACK(ssh_packet_client_ecdh_reply); + +static ssh_packet_callback ecdh_client_callbacks[]= { + ssh_packet_client_ecdh_reply +}; + +struct ssh_packet_callbacks_struct ssh_ecdh_client_callbacks = { + .start = SSH2_MSG_KEX_ECDH_REPLY, + .n_callbacks = 1, + .callbacks = ecdh_client_callbacks, + .user = NULL +}; + +/** @internal + * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back + * a SSH_MSG_NEWKEYS + */ +SSH_PACKET_CALLBACK(ssh_packet_client_ecdh_reply){ + ssh_string q_s_string = NULL; + ssh_string pubkey_blob = NULL; + ssh_string signature = NULL; + int rc; + (void)type; + (void)user; + + ssh_packet_remove_callbacks(session, &ssh_ecdh_client_callbacks); + pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (pubkey_blob == NULL) { + ssh_set_error(session,SSH_FATAL, "No public key in packet"); + goto error; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + goto error; + } + + q_s_string = ssh_buffer_get_ssh_string(packet); + if (q_s_string == NULL) { + ssh_set_error(session,SSH_FATAL, "No Q_S ECC point in packet"); + goto error; + } + session->next_crypto->ecdh_server_pubkey = q_s_string; + signature = ssh_buffer_get_ssh_string(packet); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "No signature in packet"); + goto error; + } + session->next_crypto->dh_server_signature = signature; + signature=NULL; /* ownership changed */ + /* TODO: verify signature now instead of waiting for NEWKEYS */ + if (ecdh_build_k(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { + goto error; + } + + rc=ssh_packet_send(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: + session->session_state=SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +static ssh_packet_callback ecdh_server_callbacks[] = { + ssh_packet_server_ecdh_init +}; + +struct ssh_packet_callbacks_struct ssh_ecdh_server_callbacks = { + .start = SSH2_MSG_KEX_ECDH_INIT, + .n_callbacks = 1, + .callbacks = ecdh_server_callbacks, + .user = NULL +}; + +/** @internal + * @brief sets up the ecdh kex callbacks + */ +void ssh_server_ecdh_init(ssh_session session){ + ssh_packet_set_callbacks(session, &ssh_ecdh_server_callbacks); +} + +#endif /* WITH_SERVER */ +#endif /* HAVE_ECDH */ diff --git a/src/ecdh_crypto.c b/src/ecdh_crypto.c new file mode 100644 index 0000000..a1de27f --- /dev/null +++ b/src/ecdh_crypto.c @@ -0,0 +1,348 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011-2013 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/session.h" +#include "libssh/ecdh.h" +#include "libssh/dh.h" +#include "libssh/buffer.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" + +#ifdef HAVE_ECDH +#include + +#define NISTP256 NID_X9_62_prime256v1 +#define NISTP384 NID_secp384r1 +#define NISTP521 NID_secp521r1 + +/** @internal + * @brief Map the given key exchange enum value to its curve name. + */ +static int ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { + if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256) { + return NISTP256; + } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP384) { + return NISTP384; + } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP521) { + return NISTP521; + } + return SSH_ERROR; +} + +/** @internal + * @brief Starts ecdh-sha2-nistp256 key exchange + */ +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(); + + 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 (curve == SSH_ERROR) { + BN_CTX_free(ctx); + return SSH_ERROR; + } + + key = EC_KEY_new_by_curve_name(curve); + if (key == NULL) { + BN_CTX_free(ctx); + return SSH_ERROR; + } + group = EC_KEY_get0_group(key); + + EC_KEY_generate_key(key); + + pubkey=EC_KEY_get0_public_key(key); + len = EC_POINT_point2oct(group,pubkey,POINT_CONVERSION_UNCOMPRESSED, + NULL,0,ctx); + + client_pubkey = ssh_string_new(len); + if (client_pubkey == NULL) { + BN_CTX_free(ctx); + EC_KEY_free(key); + return SSH_ERROR; + } + + EC_POINT_point2oct(group,pubkey,POINT_CONVERSION_UNCOMPRESSED, + ssh_string_data(client_pubkey),len,ctx); + BN_CTX_free(ctx); + + 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; + } + + session->next_crypto->ecdh_privkey = key; + 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 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); + return -1; + } + + 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), + 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), + ctx); + } + bignum_ctx_free(ctx); + if (rc <= 0) { + EC_POINT_clear_free(pubkey); + return -1; + } + + buffer = malloc(len); + if (buffer == NULL) { + EC_POINT_clear_free(pubkey); + return -1; + } + + rc = ECDH_compute_key(buffer, + len, + pubkey, + session->next_crypto->ecdh_privkey, + NULL); + EC_POINT_clear_free(pubkey); + if (rc <= 0) { + free(buffer); + return -1; + } + + bignum_bin2bn(buffer, len, &session->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; + return -1; + } + EC_KEY_free(session->next_crypto->ecdh_privkey); + session->next_crypto->ecdh_privkey = NULL; + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Session server cookie", + session->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 + + return 0; +} + +#ifdef WITH_SERVER + +/** @brief Handle a SSH_MSG_KEXDH_INIT packet (server) and send a + * SSH_MSG_KEXDH_REPLY + */ +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_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_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"); + 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); + 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 */ + rc = ecdh_build_k(session); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* privkey is not allocated */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto error; + } + + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + goto error; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + SSH_STRING_FREE(sig_blob); + return SSH_ERROR; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bSSS", + SSH2_MSG_KEXDH_REPLY, + pubkey_blob, /* host's pubkey */ + q_s_string, /* ecdh public key */ + sig_blob); /* signature blob */ + + SSH_STRING_FREE(sig_blob); + SSH_STRING_FREE(pubkey_blob); + + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "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){ + goto error; + } + SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); + + return SSH_PACKET_USED; +error: + ssh_buffer_reinit(session->out_buffer); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ + +#endif /* HAVE_ECDH */ diff --git a/src/ecdh_gcrypt.c b/src/ecdh_gcrypt.c new file mode 100644 index 0000000..d9c41bf --- /dev/null +++ b/src/ecdh_gcrypt.c @@ -0,0 +1,398 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011-2013 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/session.h" +#include "libssh/ecdh.h" +#include "libssh/dh.h" +#include "libssh/buffer.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" +#include "libssh/libgcrypt.h" + +#ifdef HAVE_ECDH +#include + +/** @internal + * @brief Map the given key exchange enum value to its curve name. + */ +static const char *ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { + if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256) { + return "NIST P-256"; + } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP384) { + return "NIST P-384"; + } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP521) { + return "NIST P-521"; + } + return NULL; +} + +/** @internal + * @brief Starts ecdh-sha2-nistp{256,384,521} key exchange. + */ +int ssh_client_ecdh_init(ssh_session session) +{ + int rc; + gpg_error_t err; + ssh_string client_pubkey = NULL; + gcry_sexp_t param = NULL; + gcry_sexp_t key = NULL; + const char *curve = NULL; + + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); + if (curve == NULL) { + rc = SSH_ERROR; + goto out; + } + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT); + if (rc < 0) { + rc = SSH_ERROR; + goto out; + } + + err = gcry_sexp_build(¶m, + NULL, + "(genkey(ecdh(curve %s)))", + curve); + if (err) { + rc = SSH_ERROR; + goto out; + } + + err = gcry_pk_genkey(&key, param); + if (err) { + rc = SSH_ERROR; + goto out; + } + + client_pubkey = ssh_sexp_extract_mpi(key, + "q", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (client_pubkey == NULL) { + rc = SSH_ERROR; + goto out; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, client_pubkey); + if (rc < 0) { + rc = SSH_ERROR; + goto out; + } + + session->next_crypto->ecdh_privkey = key; + key = NULL; + session->next_crypto->ecdh_client_pubkey = client_pubkey; + client_pubkey = NULL; + + /* 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); + + out: + gcry_sexp_release(param); + gcry_sexp_release(key); + SSH_STRING_FREE(client_pubkey); + return rc; +} + +int ecdh_build_k(ssh_session session) +{ + gpg_error_t err; + gcry_sexp_t data = NULL; + gcry_sexp_t result = NULL; + /* We need to get the x coordinate. Libgcrypt 1.7 and above + offers a suitable API for that. */ +#if (GCRYPT_VERSION_NUMBER >= 0x010700) + gcry_mpi_t s = NULL; + gcry_mpi_point_t point; +#else + size_t k_len = 0; + enum ssh_key_exchange_e kex_type = session->next_crypto->kex_type; + ssh_string s; +#endif + ssh_string pubkey_raw; + gcry_sexp_t pubkey = NULL; + ssh_string privkey = NULL; + int rc = SSH_ERROR; + const char *curve = NULL; + + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); + if (curve == NULL) { + goto out; + } + + pubkey_raw = session->server + ? session->next_crypto->ecdh_client_pubkey + : session->next_crypto->ecdh_server_pubkey; + + err = gcry_sexp_build(&pubkey, + NULL, + "(key-data(public-key(ecdh(curve %s)(q %b))))", + curve, + ssh_string_len(pubkey_raw), + ssh_string_data(pubkey_raw)); + if (err) { + goto out; + } + + privkey = ssh_sexp_extract_mpi(session->next_crypto->ecdh_privkey, + "d", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (privkey == NULL) { + goto out; + } + + err = gcry_sexp_build(&data, NULL, + "(data(flags raw)(value %b))", + ssh_string_len(privkey), + ssh_string_data(privkey)); + if (err) { + goto out; + } + + err = gcry_pk_encrypt(&result, data, pubkey); + if (err) { + goto out; + } + +#if (GCRYPT_VERSION_NUMBER >= 0x010700) + err = gcry_sexp_extract_param(result, "", "s", &s, NULL); + if (err) { + goto out; + } + + point = gcry_mpi_point_new(0); + if (point == NULL) { + gcry_mpi_release(s); + goto out; + } + + err = gcry_mpi_ec_decode_point(point, s, NULL); + gcry_mpi_release(s); + if (err) { + goto out; + } + + session->next_crypto->shared_secret = gcry_mpi_new(0); + gcry_mpi_point_snatch_get(session->next_crypto->shared_secret, + NULL, NULL, point); +#else + s = ssh_sexp_extract_mpi(result, "s", GCRYMPI_FMT_USG, GCRYMPI_FMT_USG); + if (s == NULL) { + goto out; + } + + if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256) { + k_len = 65; + } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP384) { + k_len = 97; + } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP521) { + k_len = 133; + } else { + ssh_string_burn(s); + SSH_STRING_FREE(s); + goto out; + } + + if (ssh_string_len(s) != k_len) { + ssh_string_burn(s); + SSH_STRING_FREE(s); + goto out; + } + + err = gcry_mpi_scan(&session->next_crypto->shared_secret, + GCRYMPI_FMT_USG, + (const char *)ssh_string_data(s) + 1, + k_len / 2, + NULL); + ssh_string_burn(s); + SSH_STRING_FREE(s); + if (err) { + goto out; + } +#endif + + rc = SSH_OK; + gcry_sexp_release(session->next_crypto->ecdh_privkey); + session->next_crypto->ecdh_privkey = NULL; + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Session server cookie", + session->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 + + out: + gcry_sexp_release(pubkey); + gcry_sexp_release(data); + gcry_sexp_release(result); + ssh_string_burn(privkey); + SSH_STRING_FREE(privkey); + return rc; +} + +#ifdef WITH_SERVER + + +/** @brief Handle a SSH_MSG_KEXDH_INIT packet (server) and send a + * SSH_MSG_KEXDH_REPLY + */ +SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ + gpg_error_t err; + /* ECDH keys */ + ssh_string q_c_string; + ssh_string q_s_string; + gcry_sexp_t param = NULL; + gcry_sexp_t key = NULL; + /* SSH host keys (rsa,dsa,ecdsa) */ + ssh_key privkey; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string sig_blob = NULL; + ssh_string pubkey_blob = NULL; + int rc = SSH_ERROR; + const char *curve = NULL; + (void)type; + (void)user; + + ssh_packet_remove_callbacks(session, &ssh_ecdh_server_callbacks); + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); + if (curve == NULL) { + goto out; + } + + /* 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"); + goto out; + } + session->next_crypto->ecdh_client_pubkey = q_c_string; + + /* Build server's keypair */ + err = gcry_sexp_build(¶m, NULL, "(genkey(ecdh(curve %s) (flags transient-key)))", + curve); + if (err) { + goto out; + } + + err = gcry_pk_genkey(&key, param); + if (err) + goto out; + + q_s_string = ssh_sexp_extract_mpi(key, + "q", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (q_s_string == NULL) { + goto out; + } + + session->next_crypto->ecdh_privkey = key; + key = NULL; + session->next_crypto->ecdh_server_pubkey = q_s_string; + + /* build k and session_id */ + rc = ecdh_build_k(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto out; + } + + /* privkey is not allocated */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc != SSH_OK) { + goto out; + } + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto out; + } + + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + rc = SSH_ERROR; + goto out; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + SSH_STRING_FREE(sig_blob); + goto out; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bSSS", + SSH2_MSG_KEXDH_REPLY, + pubkey_blob, /* host's pubkey */ + q_s_string, /* ecdh public key */ + sig_blob); /* signature blob */ + + SSH_STRING_FREE(sig_blob); + SSH_STRING_FREE(pubkey_blob); + + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto out; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEXDH_REPLY sent"); + rc = ssh_packet_send(session); + if (rc != SSH_OK) { + goto out; + } + + + /* Send the MSG_NEWKEYS */ + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); + if (rc != SSH_OK) { + 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); + if (rc == SSH_ERROR) { + ssh_buffer_reinit(session->out_buffer); + session->session_state = SSH_SESSION_STATE_ERROR; + } + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ + +#endif /* HAVE_ECDH */ diff --git a/src/ecdh_mbedcrypto.c b/src/ecdh_mbedcrypto.c new file mode 100644 index 0000000..718f152 --- /dev/null +++ b/src/ecdh_mbedcrypto.c @@ -0,0 +1,323 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * 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/session.h" +#include "libssh/ecdh.h" +#include "libssh/buffer.h" +#include "libssh/ssh2.h" +#include "libssh/dh.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" +#include "libssh/libmbedcrypto.h" + +#include +#include + +#ifdef HAVE_ECDH + +static mbedtls_ecp_group_id ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { + if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256) { + return MBEDTLS_ECP_DP_SECP256R1; + } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP384) { + return MBEDTLS_ECP_DP_SECP384R1; + } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP521) { + return MBEDTLS_ECP_DP_SECP521R1; + } + + return MBEDTLS_ECP_DP_NONE; +} +int ssh_client_ecdh_init(ssh_session session) +{ + ssh_string client_pubkey = NULL; + mbedtls_ecp_group grp; + int rc; + mbedtls_ecp_group_id curve; + + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); + if (curve == MBEDTLS_ECP_DP_NONE) { + return SSH_ERROR; + } + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT); + if (rc < 0) { + return SSH_ERROR; + } + + session->next_crypto->ecdh_privkey = malloc(sizeof(mbedtls_ecp_keypair)); + if (session->next_crypto->ecdh_privkey == NULL) { + return SSH_ERROR; + } + + mbedtls_ecp_keypair_init(session->next_crypto->ecdh_privkey); + mbedtls_ecp_group_init(&grp); + + rc = mbedtls_ecp_group_load(&grp, curve); + if (rc != 0) { + rc = SSH_ERROR; + goto out; + } + + 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()); + + if (rc != 0) { + rc = SSH_ERROR; + goto out; + } + + client_pubkey = make_ecpoint_string(&grp, + &session->next_crypto->ecdh_privkey->Q); + if (client_pubkey == NULL) { + rc = SSH_ERROR; + goto out; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, client_pubkey); + if (rc < 0) { + rc = SSH_ERROR; + goto out; + } + + session->next_crypto->ecdh_client_pubkey = client_pubkey; + client_pubkey = NULL; + + /* 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); + +out: + mbedtls_ecp_group_free(&grp); + SSH_STRING_FREE(client_pubkey); + + return rc; +} + +int ecdh_build_k(ssh_session session) +{ + mbedtls_ecp_group grp; + mbedtls_ecp_point pubkey; + int rc; + mbedtls_ecp_group_id curve; + + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); + if (curve == MBEDTLS_ECP_DP_NONE) { + return SSH_ERROR; + } + + mbedtls_ecp_group_init(&grp); + mbedtls_ecp_point_init(&pubkey); + + rc = mbedtls_ecp_group_load(&grp, curve); + if (rc != 0) { + rc = SSH_ERROR; + goto out; + } + + if (session->server) { + rc = mbedtls_ecp_point_read_binary(&grp, &pubkey, + ssh_string_data(session->next_crypto->ecdh_client_pubkey), + ssh_string_len(session->next_crypto->ecdh_client_pubkey)); + } else { + rc = mbedtls_ecp_point_read_binary(&grp, &pubkey, + ssh_string_data(session->next_crypto->ecdh_server_pubkey), + ssh_string_len(session->next_crypto->ecdh_server_pubkey)); + } + + if (rc != 0) { + rc = SSH_ERROR; + goto out; + } + + session->next_crypto->shared_secret = malloc(sizeof(mbedtls_mpi)); + if (session->next_crypto->shared_secret == NULL) { + rc = SSH_ERROR; + goto out; + } + + mbedtls_mpi_init(session->next_crypto->shared_secret); + + 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()); + if (rc != 0) { + rc = SSH_ERROR; + goto out; + } + +out: + mbedtls_ecp_keypair_free(session->next_crypto->ecdh_privkey); + SAFE_FREE(session->next_crypto->ecdh_privkey); + mbedtls_ecp_group_free(&grp); + mbedtls_ecp_point_free(&pubkey); + return rc; +} + +#ifdef WITH_SERVER + +SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ + ssh_string q_c_string = NULL; + ssh_string q_s_string = NULL; + mbedtls_ecp_group grp; + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string sig_blob = NULL; + ssh_string pubkey_blob = NULL; + int rc; + mbedtls_ecp_group_id curve; + (void)type; + (void)user; + + ssh_packet_remove_callbacks(session, &ssh_ecdh_server_callbacks); + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); + if (curve == MBEDTLS_ECP_DP_NONE) { + return SSH_ERROR; + } + + 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"); + return SSH_ERROR; + } + + session->next_crypto->ecdh_privkey = malloc(sizeof(mbedtls_ecp_keypair)); + if (session->next_crypto->ecdh_privkey == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + session->next_crypto->ecdh_client_pubkey = q_c_string; + + mbedtls_ecp_group_init(&grp); + mbedtls_ecp_keypair_init(session->next_crypto->ecdh_privkey); + + rc = mbedtls_ecp_group_load(&grp, curve); + if (rc != 0) { + rc = SSH_ERROR; + goto out; + } + + 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()); + if (rc != 0) { + rc = SSH_ERROR; + goto out; + } + + q_s_string = make_ecpoint_string(&grp, &session->next_crypto->ecdh_privkey->Q); + if (q_s_string == NULL) { + rc = SSH_ERROR; + goto out; + } + + session->next_crypto->ecdh_server_pubkey = q_s_string; + + /* build k and session_id */ + rc = ecdh_build_k(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto out; + } + + /* privkey is not allocated */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc == SSH_ERROR) { + rc = SSH_ERROR; + goto out; + } + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + rc = SSH_ERROR; + goto out; + } + + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + rc = SSH_ERROR; + goto out; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + SSH_STRING_FREE(sig_blob); + goto out; + } + + rc = ssh_buffer_pack(session->out_buffer, "bSSS", + SSH2_MSG_KEXDH_REPLY, + pubkey_blob, /* host's pubkey */ + q_s_string, /* ecdh public key */ + sig_blob); /* signature blob */ + + SSH_STRING_FREE(sig_blob); + SSH_STRING_FREE(pubkey_blob); + + if (rc != SSH_OK) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto out; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "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; + 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) { + ssh_buffer_reinit(session->out_buffer); + session->session_state = SSH_SESSION_STATE_ERROR; + } + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ +#endif diff --git a/src/error.c b/src/error.c new file mode 100644 index 0000000..2218040 --- /dev/null +++ b/src/error.c @@ -0,0 +1,154 @@ +/* + * error.c - functions for ssh error handling + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 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 +#include +#include "libssh/priv.h" +#include "libssh/session.h" + +/** + * @defgroup libssh_error The SSH error functions. + * @ingroup libssh + * + * Functions for error handling. + * + * @{ + */ + +/** + * @internal + * + * @brief Registers an error with a description. + * + * @param error The place to store the error. + * + * @param code The class of error. + * + * @param descr The description, which can be a format string. + * + * @param ... The arguments for the format string. + */ +void _ssh_set_error(void *error, + int code, + const char *function, + const char *descr, ...) +{ + struct ssh_common_struct *err = error; + va_list va; + + va_start(va, descr); + vsnprintf(err->error.error_buffer, ERROR_BUFFERLEN, descr, va); + va_end(va); + + err->error.error_code = code; + if (ssh_get_log_level() >= SSH_LOG_WARN) { + ssh_log_function(SSH_LOG_WARN, + function, + err->error.error_buffer); + } +} + +/** + * @internal + * + * @brief Registers an out of memory error + * + * @param error The place to store the error. + * + */ +void _ssh_set_error_oom(void *error, const char *function) +{ + struct error_struct *err = error; + + snprintf(err->error_buffer, sizeof(err->error_buffer), + "%s: Out of memory", function); + err->error_code = SSH_FATAL; +} + +/** + * @internal + * + * @brief Registers an invalid argument error + * + * @param error The place to store the error. + * + * @param function The function the error happened in. + * + */ +void _ssh_set_error_invalid(void *error, const char *function) +{ + _ssh_set_error(error, SSH_FATAL, function, + "Invalid argument in %s", function); +} + +/** + * @internal + * + * @brief Reset the error code and message + * + * @param error The place to reset the error. + */ +void ssh_reset_error(void *error) +{ + struct ssh_common_struct *err = error; + + ZERO_STRUCT(err->error.error_buffer); + err->error.error_code = 0; +} + +/** + * @brief Retrieve the error text message from the last error. + * + * @param error An ssh_session or ssh_bind. + * + * @return A static string describing the error. + */ +const char *ssh_get_error(void *error) { + struct error_struct *err = error; + + return err->error_buffer; +} + +/** + * @brief Retrieve the error code from the last error. + * + * @param error An ssh_session or ssh_bind. + * + * \return SSH_NO_ERROR No error occurred\n + * SSH_REQUEST_DENIED The last request was denied but situation is + * recoverable\n + * SSH_FATAL A fatal error occurred. This could be an unexpected + * disconnection\n + * + * Other error codes are internal but can be considered same than + * SSH_FATAL. + */ +int ssh_get_error_code(void *error) { + struct error_struct *err = error; + + return err->error_code; +} + +/** @} */ diff --git a/src/external/bcrypt_pbkdf.c b/src/external/bcrypt_pbkdf.c new file mode 100644 index 0000000..6fb35ff --- /dev/null +++ b/src/external/bcrypt_pbkdf.c @@ -0,0 +1,183 @@ +/* $OpenBSD: bcrypt_pbkdf.c,v 1.4 2013/07/29 00:55:53 tedu Exp $ */ +/* + * Copyright (c) 2013 Ted Unangst + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +//#include "includes.h" + +#ifndef HAVE_BCRYPT_PBKDF + +#include "config.h" + +#include "libssh/priv.h" +#include "libssh/wrapper.h" +#include +#include +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#include "libssh/blf.h" +#include "libssh/pki_priv.h" +#ifndef SHA512_DIGEST_LENGTH +#define SHA512_DIGEST_LENGTH SHA512_DIGEST_LEN +#endif + +/* + * pkcs #5 pbkdf2 implementation using the "bcrypt" hash + * + * The bcrypt hash function is derived from the bcrypt password hashing + * 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 + * to "OxychromaticBlowfishSwatDynamite" + * 4. The hash function is defined to perform 64 rounds of initial state + * expansion. (More rounds are performed by iterating the hash.) + * + * Note that this implementation pulls the SHA512 operations into the caller + * as a performance optimization. + * + * One modification from official pbkdf2. Instead of outputting key material + * linearly, we mix it. pbkdf2 has a known weakness where if one uses it to + * generate (i.e.) 512 bits of key material for use as two 256 bit keys, an + * attacker can merely run once through the outer loop below, but the user + * always runs it twice. Shuffling output bytes requires computing the + * entirety of the key material to assemble any subkey. This is something a + * wise caller could do; we just do it for you. + */ + +#define BCRYPT_BLOCKS 8 +#define BCRYPT_HASHSIZE (BCRYPT_BLOCKS * 4) + +static void +bcrypt_hash(uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *out) +{ + ssh_blf_ctx state; + uint8_t ciphertext[BCRYPT_HASHSIZE] = + "OxychromaticBlowfishSwatDynamite"; + uint32_t cdata[BCRYPT_BLOCKS]; + int i; + uint16_t j; + uint16_t shalen = SHA512_DIGEST_LENGTH; + + /* key expansion */ + 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); + } + + /* encryption */ + j = 0; + for (i = 0; i < BCRYPT_BLOCKS; i++) + cdata[i] = Blowfish_stream2word(ciphertext, sizeof(ciphertext), + &j); + for (i = 0; i < 64; i++) + ssh_blf_enc(&state, cdata, sizeof(cdata) / sizeof(uint64_t)); + + /* copy out */ + for (i = 0; i < BCRYPT_BLOCKS; i++) { + out[4 * i + 3] = (cdata[i] >> 24) & 0xff; + out[4 * i + 2] = (cdata[i] >> 16) & 0xff; + out[4 * i + 1] = (cdata[i] >> 8) & 0xff; + out[4 * i + 0] = cdata[i] & 0xff; + } + + /* zap */ + explicit_bzero(ciphertext, sizeof(ciphertext)); + explicit_bzero(cdata, sizeof(cdata)); + ZERO_STRUCT(state); +} + +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) +{ + uint8_t sha2pass[SHA512_DIGEST_LENGTH]; + uint8_t sha2salt[SHA512_DIGEST_LENGTH]; + uint8_t out[BCRYPT_HASHSIZE]; + uint8_t tmpout[BCRYPT_HASHSIZE]; + uint8_t *countsalt; + size_t i, j, amt, stride; + uint32_t count; + size_t origkeylen = keylen; + SHA512CTX ctx; + + /* nothing crazy */ + if (rounds < 1) + return -1; + if (passlen == 0 || saltlen == 0 || keylen == 0 || + keylen > sizeof(out) * sizeof(out) || saltlen > 1<<20) + return -1; + if ((countsalt = calloc(1, saltlen + 4)) == NULL) + return -1; + stride = (keylen + sizeof(out) - 1) / sizeof(out); + amt = (keylen + stride - 1) / stride; + + memcpy(countsalt, salt, saltlen); + + /* collapse password */ + ctx = sha512_init(); + sha512_update(ctx, pass, passlen); + sha512_final(sha2pass, ctx); + + /* generate key, sizeof(out) at a time */ + for (count = 1; keylen > 0; count++) { + countsalt[saltlen + 0] = (count >> 24) & 0xff; + countsalt[saltlen + 1] = (count >> 16) & 0xff; + countsalt[saltlen + 2] = (count >> 8) & 0xff; + countsalt[saltlen + 3] = count & 0xff; + + /* first round, salt is salt */ + ctx = sha512_init(); + sha512_update(ctx, countsalt, saltlen + 4); + sha512_final(sha2salt, ctx); + + bcrypt_hash(sha2pass, sha2salt, tmpout); + memcpy(out, tmpout, sizeof(out)); + + for (i = 1; i < rounds; i++) { + /* subsequent rounds, salt is previous output */ + ctx = sha512_init(); + sha512_update(ctx, tmpout, sizeof(tmpout)); + sha512_final(sha2salt, ctx); + bcrypt_hash(sha2pass, sha2salt, tmpout); + for (j = 0; j < sizeof(out); j++) + out[j] ^= tmpout[j]; + } + + /* + * pbkdf2 deviation: ouput the key material non-linearly. + */ + amt = MIN(amt, keylen); + for (i = 0; i < amt; i++) { + size_t dest = i * stride + (count - 1); + if (dest >= origkeylen) { + break; + } + key[dest] = out[i]; + } + keylen -= i; + } + + /* zap */ + explicit_bzero(out, sizeof(out)); + free(countsalt); + + return 0; +} +#endif /* HAVE_BCRYPT_PBKDF */ diff --git a/src/external/blowfish.c b/src/external/blowfish.c new file mode 100644 index 0000000..4008a9c --- /dev/null +++ b/src/external/blowfish.c @@ -0,0 +1,694 @@ +/* $OpenBSD: blowfish.c,v 1.18 2004/11/02 17:23:26 hshoexer Exp $ */ +/* + * Blowfish block cipher for OpenBSD + * Copyright 1997 Niels Provos + * All rights reserved. + * + * Implementation advice by David Mazieres . + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Niels Provos. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + */ + +/* + * This code is derived from section 14.3 and the given source + * in section V of Applied Cryptography, second edition. + * Blowfish is an unpatented fast block cipher designed by + * Bruce Schneier. + */ + + +#if !defined(HAVE_BCRYPT_PBKDF) && (!defined(HAVE_BLOWFISH_INITSTATE) || \ + !defined(HAVE_BLOWFISH_EXPAND0STATE) || !defined(HAVE_BLF_ENC)) + +#if 0 +#include /* used for debugging */ +#include +#endif + +#include +#include + +#include "libssh/blf.h" + +#undef inline +#ifdef __GNUC__ +#define inline __inline +#else /* !__GNUC__ */ +#define inline +#endif /* !__GNUC__ */ + +/* Function for Feistel Networks */ + +#define F(s, x) ((((s)[ (((x)>>24)&0xFF)] \ + + (s)[0x100 + (((x)>>16)&0xFF)]) \ + ^ (s)[0x200 + (((x)>> 8)&0xFF)]) \ + + (s)[0x300 + ( (x) &0xFF)]) + +#define BLFRND(s,p,i,j,n) (i ^= F(s,j) ^ (p)[n]) + +void +Blowfish_encipher(ssh_blf_ctx *c, uint32_t *xl, uint32_t *xr) +{ + uint32_t Xl; + uint32_t Xr; + uint32_t *s = c->S[0]; + uint32_t *p = c->P; + + Xl = *xl; + Xr = *xr; + + Xl ^= p[0]; + BLFRND(s, p, Xr, Xl, 1); BLFRND(s, p, Xl, Xr, 2); + BLFRND(s, p, Xr, Xl, 3); BLFRND(s, p, Xl, Xr, 4); + BLFRND(s, p, Xr, Xl, 5); BLFRND(s, p, Xl, Xr, 6); + BLFRND(s, p, Xr, Xl, 7); BLFRND(s, p, Xl, Xr, 8); + BLFRND(s, p, Xr, Xl, 9); BLFRND(s, p, Xl, Xr, 10); + BLFRND(s, p, Xr, Xl, 11); BLFRND(s, p, Xl, Xr, 12); + BLFRND(s, p, Xr, Xl, 13); BLFRND(s, p, Xl, Xr, 14); + BLFRND(s, p, Xr, Xl, 15); BLFRND(s, p, Xl, Xr, 16); + + *xl = Xr ^ p[17]; + *xr = Xl; +} + +void +Blowfish_decipher(ssh_blf_ctx *c, uint32_t *xl, uint32_t *xr) +{ + uint32_t Xl; + uint32_t Xr; + uint32_t *s = c->S[0]; + uint32_t *p = c->P; + + Xl = *xl; + Xr = *xr; + + Xl ^= p[17]; + BLFRND(s, p, Xr, Xl, 16); BLFRND(s, p, Xl, Xr, 15); + BLFRND(s, p, Xr, Xl, 14); BLFRND(s, p, Xl, Xr, 13); + BLFRND(s, p, Xr, Xl, 12); BLFRND(s, p, Xl, Xr, 11); + BLFRND(s, p, Xr, Xl, 10); BLFRND(s, p, Xl, Xr, 9); + BLFRND(s, p, Xr, Xl, 8); BLFRND(s, p, Xl, Xr, 7); + BLFRND(s, p, Xr, Xl, 6); BLFRND(s, p, Xl, Xr, 5); + BLFRND(s, p, Xr, Xl, 4); BLFRND(s, p, Xl, Xr, 3); + BLFRND(s, p, Xr, Xl, 2); BLFRND(s, p, Xl, Xr, 1); + + *xl = Xr ^ p[0]; + *xr = Xl; +} + +void +Blowfish_initstate(ssh_blf_ctx *c) +{ + /* P-box and S-box tables initialized with digits of Pi */ + + static const ssh_blf_ctx initstate = + { { + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a}, + { + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7}, + { + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0}, + { + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6} + }, + { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + } }; + + *c = initstate; +} + +uint32_t +Blowfish_stream2word(const uint8_t *data, uint16_t databytes, + uint16_t *current) +{ + uint8_t i; + uint16_t j; + uint32_t temp; + + temp = 0x00000000; + j = *current; + + for (i = 0; i < 4; i++, j++) { + if (j >= databytes) + j = 0; + temp = (temp << 8) | data[j]; + } + + *current = j; + return temp; +} + +void +Blowfish_expand0state(ssh_blf_ctx *c, const uint8_t *key, uint16_t keybytes) +{ + uint16_t i; + uint16_t j; + uint16_t k; + uint32_t temp; + uint32_t datal; + uint32_t datar; + + j = 0; + for (i = 0; i < BLF_N + 2; i++) { + /* Extract 4 int8 to 1 int32 from keystream */ + temp = Blowfish_stream2word(key, keybytes, &j); + c->P[i] = c->P[i] ^ temp; + } + + j = 0; + datal = 0x00000000; + datar = 0x00000000; + for (i = 0; i < BLF_N + 2; i += 2) { + Blowfish_encipher(c, &datal, &datar); + + c->P[i] = datal; + c->P[i + 1] = datar; + } + + for (i = 0; i < 4; i++) { + for (k = 0; k < 256; k += 2) { + Blowfish_encipher(c, &datal, &datar); + + c->S[i][k] = datal; + c->S[i][k + 1] = datar; + } + } +} + + +void +Blowfish_expandstate(ssh_blf_ctx *c, const uint8_t *data, uint16_t databytes, + const uint8_t *key, uint16_t keybytes) +{ + uint16_t i; + uint16_t j; + uint16_t k; + uint32_t temp; + uint32_t datal; + uint32_t datar; + + j = 0; + for (i = 0; i < BLF_N + 2; i++) { + /* Extract 4 int8 to 1 int32 from keystream */ + temp = Blowfish_stream2word(key, keybytes, &j); + c->P[i] = c->P[i] ^ temp; + } + + j = 0; + datal = 0x00000000; + datar = 0x00000000; + for (i = 0; i < BLF_N + 2; i += 2) { + datal ^= Blowfish_stream2word(data, databytes, &j); + datar ^= Blowfish_stream2word(data, databytes, &j); + Blowfish_encipher(c, &datal, &datar); + + c->P[i] = datal; + c->P[i + 1] = datar; + } + + for (i = 0; i < 4; i++) { + for (k = 0; k < 256; k += 2) { + datal ^= Blowfish_stream2word(data, databytes, &j); + datar ^= Blowfish_stream2word(data, databytes, &j); + Blowfish_encipher(c, &datal, &datar); + + c->S[i][k] = datal; + c->S[i][k + 1] = datar; + } + } + +} + +void +ssh_blf_key(ssh_blf_ctx *c, const uint8_t *k, uint16_t len) +{ + /* Initialize S-boxes and subkeys with Pi */ + Blowfish_initstate(c); + + /* Transform S-boxes and subkeys with key */ + Blowfish_expand0state(c, k, len); +} + +void +ssh_blf_enc(ssh_blf_ctx *c, uint32_t *data, uint16_t blocks) +{ + uint32_t *d; + uint16_t i; + + d = data; + for (i = 0; i < blocks; i++) { + Blowfish_encipher(c, d, d + 1); + d += 2; + } +} + +void +ssh_blf_dec(ssh_blf_ctx *c, uint32_t *data, uint16_t blocks) +{ + uint32_t *d; + uint16_t i; + + d = data; + for (i = 0; i < blocks; i++) { + Blowfish_decipher(c, d, d + 1); + d += 2; + } +} + +void +ssh_blf_ecb_encrypt(ssh_blf_ctx *c, uint8_t *data, uint32_t len) +{ + uint32_t l, r; + uint32_t i; + + for (i = 0; i < len; i += 8) { + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_encipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + data += 8; + } +} + +void +ssh_blf_ecb_decrypt(ssh_blf_ctx *c, uint8_t *data, uint32_t len) +{ + uint32_t l, r; + uint32_t i; + + for (i = 0; i < len; i += 8) { + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_decipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + data += 8; + } +} + +void +ssh_blf_cbc_encrypt(ssh_blf_ctx *c, uint8_t *iv, uint8_t *data, uint32_t len) +{ + uint32_t l, r; + uint32_t i, j; + + for (i = 0; i < len; i += 8) { + for (j = 0; j < 8; j++) + data[j] ^= iv[j]; + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_encipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + iv = data; + data += 8; + } +} + +void +ssh_blf_cbc_decrypt(ssh_blf_ctx *c, uint8_t *iva, uint8_t *data, uint32_t len) +{ + uint32_t l, r; + uint8_t *iv; + uint32_t i, j; + + iv = data + len - 16; + data = data + len - 8; + for (i = len - 8; i >= 8; i -= 8) { + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_decipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + for (j = 0; j < 8; j++) + data[j] ^= iv[j]; + iv -= 8; + data -= 8; + } + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_decipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + for (j = 0; j < 8; j++) + data[j] ^= iva[j]; +} + +#if 0 +void +report(uint32_t data[], uint16_t len) +{ + uint16_t i; + for (i = 0; i < len; i += 2) + printf("Block %0hd: %08lx %08lx.\n", + i / 2, data[i], data[i + 1]); +} +void +main(void) +{ + + ssh_blf_ctx c; + char key[] = "AAAAA"; + char key2[] = "abcdefghijklmnopqrstuvwxyz"; + + uint32_t data[10]; + uint32_t data2[] = + {0x424c4f57l, 0x46495348l}; + + uint16_t i; + + /* First test */ + for (i = 0; i < 10; i++) + data[i] = i; + + ssh_blf_key(&c, (uint8_t *) key, 5); + ssh_blf_enc(&c, data, 5); + ssh_blf_dec(&c, data, 1); + ssh_blf_dec(&c, data + 2, 4); + printf("Should read as 0 - 9.\n"); + report(data, 10); + + /* Second test */ + ssh_blf_key(&c, (uint8_t *) key2, strlen(key2)); + ssh_blf_enc(&c, data2, 1); + printf("\nShould read as: 0x324ed0fe 0xf413a203.\n"); + report(data2, 2); + ssh_blf_dec(&c, data2, 1); + report(data2, 2); +} +#endif + +#endif /* !defined(HAVE_BCRYPT_PBKDF) && (!defined(HAVE_BLOWFISH_INITSTATE) || \ + !defined(HAVE_BLOWFISH_EXPAND0STATE) || !defined(HAVE_BLF_ENC)) */ diff --git a/src/external/chacha.c b/src/external/chacha.c new file mode 100644 index 0000000..8d1ccca --- /dev/null +++ b/src/external/chacha.c @@ -0,0 +1,216 @@ +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. + */ + +#include +#include +#include + +#include "libssh/chacha.h" + +typedef struct chacha_ctx chacha_ctx; + +#define U8C(v) (v##U) +#define U32C(v) (v##U) + +#define U8V(v) ((uint8_t)(v) & U8C(0xFF)) +#define U32V(v) ((uint32_t)(v) & U32C(0xFFFFFFFF)) + +#define ROTL32(v, n) \ + (U32V((v) << (n)) | ((v) >> (32 - (n)))) + +#define U8TO32_LITTLE(p) \ + (((uint32_t)((p)[0]) ) | \ + ((uint32_t)((p)[1]) << 8) | \ + ((uint32_t)((p)[2]) << 16) | \ + ((uint32_t)((p)[3]) << 24)) + +#define U32TO8_LITTLE(p, v) \ + do { \ + (p)[0] = U8V((v) ); \ + (p)[1] = U8V((v) >> 8); \ + (p)[2] = U8V((v) >> 16); \ + (p)[3] = U8V((v) >> 24); \ + } while (0) + +#define ROTATE(v,c) (ROTL32(v,c)) +#define XOR(v,w) ((v) ^ (w)) +#define PLUS(v,w) (U32V((v) + (w))) +#define PLUSONE(v) (PLUS((v),1)) + +#define QUARTERROUND(a,b,c,d) \ + a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \ + a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c), 7); + +static const char sigma[16] = "expand 32-byte k"; +static const char tau[16] = "expand 16-byte k"; + +void +chacha_keysetup(chacha_ctx *x,const uint8_t *k,uint32_t kbits) +{ + const char *constants; + + x->input[4] = U8TO32_LITTLE(k + 0); + x->input[5] = U8TO32_LITTLE(k + 4); + x->input[6] = U8TO32_LITTLE(k + 8); + x->input[7] = U8TO32_LITTLE(k + 12); + if (kbits == 256) { /* recommended */ + k += 16; + constants = sigma; + } else { /* kbits == 128 */ + constants = tau; + } + x->input[8] = U8TO32_LITTLE(k + 0); + x->input[9] = U8TO32_LITTLE(k + 4); + x->input[10] = U8TO32_LITTLE(k + 8); + x->input[11] = U8TO32_LITTLE(k + 12); + x->input[0] = U8TO32_LITTLE(constants + 0); + x->input[1] = U8TO32_LITTLE(constants + 4); + x->input[2] = U8TO32_LITTLE(constants + 8); + x->input[3] = U8TO32_LITTLE(constants + 12); +} + +void +chacha_ivsetup(chacha_ctx *x, const uint8_t *iv, const uint8_t *counter) +{ + x->input[12] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 0); + x->input[13] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 4); + x->input[14] = U8TO32_LITTLE(iv + 0); + x->input[15] = U8TO32_LITTLE(iv + 4); +} + +void +chacha_encrypt_bytes(chacha_ctx *x,const uint8_t *m,uint8_t *c,uint32_t bytes) +{ + uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; + uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; + uint8_t *ctarget = NULL; + uint8_t tmp[64]; + uint32_t i; + + if (!bytes) return; + + j0 = x->input[0]; + j1 = x->input[1]; + j2 = x->input[2]; + j3 = x->input[3]; + j4 = x->input[4]; + j5 = x->input[5]; + j6 = x->input[6]; + j7 = x->input[7]; + j8 = x->input[8]; + j9 = x->input[9]; + j10 = x->input[10]; + j11 = x->input[11]; + j12 = x->input[12]; + j13 = x->input[13]; + j14 = x->input[14]; + j15 = x->input[15]; + + for (;;) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) tmp[i] = m[i]; + m = tmp; + ctarget = c; + c = tmp; + } + x0 = j0; + x1 = j1; + x2 = j2; + x3 = j3; + x4 = j4; + x5 = j5; + x6 = j6; + x7 = j7; + x8 = j8; + x9 = j9; + x10 = j10; + x11 = j11; + x12 = j12; + x13 = j13; + x14 = j14; + x15 = j15; + for (i = 20;i > 0;i -= 2) { + QUARTERROUND( x0, x4, x8,x12) + QUARTERROUND( x1, x5, x9,x13) + QUARTERROUND( x2, x6,x10,x14) + QUARTERROUND( x3, x7,x11,x15) + QUARTERROUND( x0, x5,x10,x15) + QUARTERROUND( x1, x6,x11,x12) + QUARTERROUND( x2, x7, x8,x13) + QUARTERROUND( x3, x4, x9,x14) + } + x0 = PLUS(x0,j0); + x1 = PLUS(x1,j1); + x2 = PLUS(x2,j2); + x3 = PLUS(x3,j3); + x4 = PLUS(x4,j4); + x5 = PLUS(x5,j5); + x6 = PLUS(x6,j6); + x7 = PLUS(x7,j7); + x8 = PLUS(x8,j8); + x9 = PLUS(x9,j9); + x10 = PLUS(x10,j10); + x11 = PLUS(x11,j11); + x12 = PLUS(x12,j12); + x13 = PLUS(x13,j13); + x14 = PLUS(x14,j14); + x15 = PLUS(x15,j15); + + x0 = XOR(x0,U8TO32_LITTLE(m + 0)); + x1 = XOR(x1,U8TO32_LITTLE(m + 4)); + x2 = XOR(x2,U8TO32_LITTLE(m + 8)); + x3 = XOR(x3,U8TO32_LITTLE(m + 12)); + x4 = XOR(x4,U8TO32_LITTLE(m + 16)); + x5 = XOR(x5,U8TO32_LITTLE(m + 20)); + x6 = XOR(x6,U8TO32_LITTLE(m + 24)); + x7 = XOR(x7,U8TO32_LITTLE(m + 28)); + x8 = XOR(x8,U8TO32_LITTLE(m + 32)); + x9 = XOR(x9,U8TO32_LITTLE(m + 36)); + x10 = XOR(x10,U8TO32_LITTLE(m + 40)); + x11 = XOR(x11,U8TO32_LITTLE(m + 44)); + x12 = XOR(x12,U8TO32_LITTLE(m + 48)); + x13 = XOR(x13,U8TO32_LITTLE(m + 52)); + x14 = XOR(x14,U8TO32_LITTLE(m + 56)); + x15 = XOR(x15,U8TO32_LITTLE(m + 60)); + + j12 = PLUSONE(j12); + if (!j12) { + j13 = PLUSONE(j13); + /* stopping at 2^70 bytes per nonce is user's responsibility */ + } + + U32TO8_LITTLE(c + 0,x0); + U32TO8_LITTLE(c + 4,x1); + U32TO8_LITTLE(c + 8,x2); + U32TO8_LITTLE(c + 12,x3); + U32TO8_LITTLE(c + 16,x4); + U32TO8_LITTLE(c + 20,x5); + U32TO8_LITTLE(c + 24,x6); + U32TO8_LITTLE(c + 28,x7); + U32TO8_LITTLE(c + 32,x8); + U32TO8_LITTLE(c + 36,x9); + U32TO8_LITTLE(c + 40,x10); + U32TO8_LITTLE(c + 44,x11); + U32TO8_LITTLE(c + 48,x12); + U32TO8_LITTLE(c + 52,x13); + U32TO8_LITTLE(c + 56,x14); + U32TO8_LITTLE(c + 60,x15); + + if (bytes <= 64) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) ctarget[i] = c[i]; + } + x->input[12] = j12; + x->input[13] = j13; + return; + } + bytes -= 64; + c += 64; + m += 64; + } +} diff --git a/src/external/curve25519_ref.c b/src/external/curve25519_ref.c new file mode 100644 index 0000000..6ffb14a --- /dev/null +++ b/src/external/curve25519_ref.c @@ -0,0 +1,271 @@ +/* +version 20081011 +Matthew Dempsky +Public domain. +Derived from public domain code by D. J. Bernstein. +*/ + +#include "libssh/curve25519.h" +static const unsigned char base[32] = {9}; + +int crypto_scalarmult_base(unsigned char *q, + const unsigned char *n) +{ + return crypto_scalarmult(q,n,base); +} + +static void add(unsigned int out[32],const unsigned int a[32],const unsigned int b[32]) +{ + unsigned int j; + unsigned int u; + u = 0; + for (j = 0;j < 31;++j) { u += a[j] + b[j]; out[j] = u & 255; u >>= 8; } + u += a[31] + b[31]; out[31] = u; +} + +static void sub(unsigned int out[32],const unsigned int a[32],const unsigned int b[32]) +{ + unsigned int j; + unsigned int u; + u = 218; + for (j = 0;j < 31;++j) { + u += a[j] + 65280 - b[j]; + out[j] = u & 255; + u >>= 8; + } + u += a[31] - b[31]; + out[31] = u; +} + +static void squeeze(unsigned int a[32]) +{ + unsigned int j; + unsigned int u; + u = 0; + for (j = 0;j < 31;++j) { u += a[j]; a[j] = u & 255; u >>= 8; } + u += a[31]; a[31] = u & 127; + u = 19 * (u >> 7); + for (j = 0;j < 31;++j) { u += a[j]; a[j] = u & 255; u >>= 8; } + u += a[31]; a[31] = u; +} + +static const unsigned int minusp[32] = { + 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128 +} ; + +static void freeze(unsigned int a[32]) +{ + unsigned int aorig[32]; + unsigned int j; + unsigned int negative; + + for (j = 0;j < 32;++j) aorig[j] = a[j]; + add(a,a,minusp); + negative = -((a[31] >> 7) & 1); + for (j = 0;j < 32;++j) a[j] ^= negative & (aorig[j] ^ a[j]); +} + +static void mult(unsigned int out[32],const unsigned int a[32],const unsigned int b[32]) +{ + unsigned int i; + unsigned int j; + unsigned int u; + + for (i = 0;i < 32;++i) { + u = 0; + for (j = 0;j <= i;++j) u += a[j] * b[i - j]; + for (j = i + 1;j < 32;++j) u += 38 * a[j] * b[i + 32 - j]; + out[i] = u; + } + squeeze(out); +} + +static void mult121665(unsigned int out[32],const unsigned int a[32]) +{ + unsigned int j; + unsigned int u; + + u = 0; + for (j = 0;j < 31;++j) { u += 121665 * a[j]; out[j] = u & 255; u >>= 8; } + u += 121665 * a[31]; out[31] = u & 127; + u = 19 * (u >> 7); + for (j = 0;j < 31;++j) { u += out[j]; out[j] = u & 255; u >>= 8; } + u += out[j]; out[j] = u; +} + +static void square(unsigned int out[32],const unsigned int a[32]) +{ + unsigned int i; + unsigned int j; + unsigned int u; + + for (i = 0;i < 32;++i) { + u = 0; + for (j = 0;j < i - j;++j) u += a[j] * a[i - j]; + for (j = i + 1;j < i + 32 - j;++j) u += 38 * a[j] * a[i + 32 - j]; + u *= 2; + if ((i & 1) == 0) { + u += a[i / 2] * a[i / 2]; + u += 38 * a[i / 2 + 16] * a[i / 2 + 16]; + } + out[i] = u; + } + squeeze(out); +} + +static void c_select(unsigned int p[64],unsigned int q[64],const unsigned int r[64],const unsigned int s[64],unsigned int b) +{ + unsigned int j; + unsigned int t; + unsigned int bminus1; + + bminus1 = b - 1; + for (j = 0;j < 64;++j) { + t = bminus1 & (r[j] ^ s[j]); + p[j] = s[j] ^ t; + q[j] = r[j] ^ t; + } +} + +static void mainloop(unsigned int work[64],const unsigned char e[32]) +{ + unsigned int xzm1[64]; + unsigned int xzm[64]; + unsigned int xzmb[64]; + unsigned int xzm1b[64]; + unsigned int xznb[64]; + unsigned int xzn1b[64]; + unsigned int a0[64]; + unsigned int a1[64]; + unsigned int b0[64]; + unsigned int b1[64]; + unsigned int c1[64]; + unsigned int r[32]; + unsigned int s[32]; + unsigned int t[32]; + unsigned int u[32]; + unsigned int j; + unsigned int b; + int pos; + + for (j = 0;j < 32;++j) xzm1[j] = work[j]; + xzm1[32] = 1; + for (j = 33;j < 64;++j) xzm1[j] = 0; + + xzm[0] = 1; + for (j = 1;j < 64;++j) xzm[j] = 0; + + for (pos = 254;pos >= 0;--pos) { + b = e[pos / 8] >> (pos & 7); + b &= 1; + c_select(xzmb,xzm1b,xzm,xzm1,b); + add(a0,xzmb,xzmb + 32); + sub(a0 + 32,xzmb,xzmb + 32); + add(a1,xzm1b,xzm1b + 32); + sub(a1 + 32,xzm1b,xzm1b + 32); + square(b0,a0); + square(b0 + 32,a0 + 32); + mult(b1,a1,a0 + 32); + mult(b1 + 32,a1 + 32,a0); + add(c1,b1,b1 + 32); + sub(c1 + 32,b1,b1 + 32); + square(r,c1 + 32); + sub(s,b0,b0 + 32); + mult121665(t,s); + add(u,t,b0); + mult(xznb,b0,b0 + 32); + mult(xznb + 32,s,u); + square(xzn1b,c1); + mult(xzn1b + 32,r,work); + c_select(xzm,xzm1,xznb,xzn1b,b); + } + + for (j = 0;j < 64;++j) work[j] = xzm[j]; +} + +static void recip(unsigned int out[32],const unsigned int z[32]) +{ + unsigned int z2[32]; + unsigned int z9[32]; + unsigned int z11[32]; + unsigned int z2_5_0[32]; + unsigned int z2_10_0[32]; + unsigned int z2_20_0[32]; + unsigned int z2_50_0[32]; + unsigned int z2_100_0[32]; + unsigned int t0[32]; + unsigned int t1[32]; + int i; + + /* 2 */ square(z2,z); + /* 4 */ square(t1,z2); + /* 8 */ square(t0,t1); + /* 9 */ mult(z9,t0,z); + /* 11 */ mult(z11,z9,z2); + /* 22 */ square(t0,z11); + /* 2^5 - 2^0 = 31 */ mult(z2_5_0,t0,z9); + + /* 2^6 - 2^1 */ square(t0,z2_5_0); + /* 2^7 - 2^2 */ square(t1,t0); + /* 2^8 - 2^3 */ square(t0,t1); + /* 2^9 - 2^4 */ square(t1,t0); + /* 2^10 - 2^5 */ square(t0,t1); + /* 2^10 - 2^0 */ mult(z2_10_0,t0,z2_5_0); + + /* 2^11 - 2^1 */ square(t0,z2_10_0); + /* 2^12 - 2^2 */ square(t1,t0); + /* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) { square(t0,t1); square(t1,t0); } + /* 2^20 - 2^0 */ mult(z2_20_0,t1,z2_10_0); + + /* 2^21 - 2^1 */ square(t0,z2_20_0); + /* 2^22 - 2^2 */ square(t1,t0); + /* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) { square(t0,t1); square(t1,t0); } + /* 2^40 - 2^0 */ mult(t0,t1,z2_20_0); + + /* 2^41 - 2^1 */ square(t1,t0); + /* 2^42 - 2^2 */ square(t0,t1); + /* 2^50 - 2^10 */ for (i = 2;i < 10;i += 2) { square(t1,t0); square(t0,t1); } + /* 2^50 - 2^0 */ mult(z2_50_0,t0,z2_10_0); + + /* 2^51 - 2^1 */ square(t0,z2_50_0); + /* 2^52 - 2^2 */ square(t1,t0); + /* 2^100 - 2^50 */ for (i = 2;i < 50;i += 2) { square(t0,t1); square(t1,t0); } + /* 2^100 - 2^0 */ mult(z2_100_0,t1,z2_50_0); + + /* 2^101 - 2^1 */ square(t1,z2_100_0); + /* 2^102 - 2^2 */ square(t0,t1); + /* 2^200 - 2^100 */ for (i = 2;i < 100;i += 2) { square(t1,t0); square(t0,t1); } + /* 2^200 - 2^0 */ mult(t1,t0,z2_100_0); + + /* 2^201 - 2^1 */ square(t0,t1); + /* 2^202 - 2^2 */ square(t1,t0); + /* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) { square(t0,t1); square(t1,t0); } + /* 2^250 - 2^0 */ mult(t0,t1,z2_50_0); + + /* 2^251 - 2^1 */ square(t1,t0); + /* 2^252 - 2^2 */ square(t0,t1); + /* 2^253 - 2^3 */ square(t1,t0); + /* 2^254 - 2^4 */ square(t0,t1); + /* 2^255 - 2^5 */ square(t1,t0); + /* 2^255 - 21 */ mult(out,t1,z11); +} + +int crypto_scalarmult(unsigned char *q, + const unsigned char *n, + const unsigned char *p) +{ + unsigned int work[96]; + unsigned char e[32]; + unsigned int i; + for (i = 0;i < 32;++i) e[i] = n[i]; + e[0] &= 248; + e[31] &= 127; + e[31] |= 64; + for (i = 0;i < 32;++i) work[i] = p[i]; + mainloop(work,e); + recip(work + 32,work + 32); + mult(work + 64,work,work + 32); + freeze(work + 64); + for (i = 0;i < 32;++i) q[i] = work[64 + i]; + return 0; +} diff --git a/src/external/ed25519.c b/src/external/ed25519.c new file mode 100644 index 0000000..8cd5859 --- /dev/null +++ b/src/external/ed25519.c @@ -0,0 +1,222 @@ +/* $OpenBSD: ed25519.c,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/ed25519.c + */ + +#include "config.h" + +#include "libssh/libcrypto.h" +#include "libssh/wrapper.h" +#include "libssh/ge25519.h" +#include "libssh/sc25519.h" +#include "libssh/ed25519.h" + +/* + * Public Domain, Author: Daniel J. Bernstein + * Copied from nacl-20110221/crypto_verify/32/ref/verify.c + */ + +static int crypto_verify_32(const unsigned char *x,const unsigned char *y) +{ + unsigned int differentbits = 0; +#define F(i) differentbits |= x[i] ^ y[i]; + F(0) + F(1) + F(2) + F(3) + F(4) + F(5) + F(6) + F(7) + F(8) + F(9) + F(10) + F(11) + F(12) + F(13) + F(14) + F(15) + F(16) + F(17) + F(18) + F(19) + F(20) + F(21) + F(22) + F(23) + F(24) + F(25) + F(26) + F(27) + F(28) + F(29) + F(30) + F(31) + + return (1 & ((differentbits - 1) >> 8)) - 1; +} + +static void get_hram(unsigned char *hram, + const unsigned char *sm, + const unsigned char *pk, + unsigned char *playground, + uint64_t smlen) +{ + uint64_t i; + SHA512CTX ctx; + for (i = 0;i < 32;++i) playground[i] = sm[i]; + for (i = 32;i < 64;++i) playground[i] = pk[i-32]; + for (i = 64;i < smlen;++i) playground[i] = sm[i]; + + ctx = sha512_init(); + sha512_update(ctx, playground, smlen); + sha512_final(hram, ctx); +} + + +int crypto_sign_ed25519_keypair(unsigned char *pk, + unsigned char *sk) +{ + sc25519 scsk; + ge25519 gepk; + SHA512CTX ctx; + unsigned char extsk[64]; + int i; + int ok; + + ok = ssh_get_random(sk, 32, 0); + if (!ok) { + return -1; + } + + ctx = sha512_init(); + sha512_update(ctx, sk, 32); + sha512_final(extsk, ctx); + extsk[0] &= 248; + extsk[31] &= 127; + extsk[31] |= 64; + + sc25519_from32bytes(&scsk,extsk); + + ge25519_scalarmult_base(&gepk, &scsk); + ge25519_pack(pk, &gepk); + for(i=0;i<32;i++) { + sk[32 + i] = pk[i]; + } + + return 0; +} + +int crypto_sign_ed25519(unsigned char *sm, + uint64_t *smlen, + const unsigned char *m, + uint64_t mlen, + const unsigned char *sk) +{ + sc25519 sck, scs, scsk; + ge25519 ger; + SHA512CTX ctx; + unsigned char r[32]; + unsigned char s[32]; + unsigned char extsk[64]; + uint64_t i; + unsigned char hmg[SHA512_DIGEST_LEN]; + unsigned char hram[SHA512_DIGEST_LEN]; + + ctx = sha512_init(); + sha512_update(ctx, sk, 32); + sha512_final(extsk, ctx); + + extsk[0] &= 248; + extsk[31] &= 127; + extsk[31] |= 64; + + *smlen = mlen + 64; + for (i = 0;i < mlen; i++) { + sm[64 + i] = m[i]; + } + for (i = 0;i < 32; i++) { + sm[32 + i] = extsk[32+i]; + } + + /* Generate k as h(extsk[32],...,extsk[63],m) */ + ctx = sha512_init(); + sha512_update(ctx, sm + 32, mlen + 32); + sha512_final(hmg, ctx); + + /* Computation of R */ + sc25519_from64bytes(&sck, hmg); + ge25519_scalarmult_base(&ger, &sck); + ge25519_pack(r, &ger); + + /* Computation of s */ + for (i = 0; i < 32; i++) { + sm[i] = r[i]; + } + + get_hram(hram, sm, sk+32, sm, mlen+64); + + sc25519_from64bytes(&scs, hram); + sc25519_from32bytes(&scsk, extsk); + sc25519_mul(&scs, &scs, &scsk); + + sc25519_add(&scs, &scs, &sck); + + sc25519_to32bytes(s,&scs); /* cat s */ + for (i = 0;i < 32; i++) { + sm[32 + i] = s[i]; + } + + return 0; +} + +int crypto_sign_ed25519_open(unsigned char *m, + uint64_t *mlen, + const unsigned char *sm, + uint64_t smlen, + const unsigned char *pk) +{ + unsigned int i; + int ret; + unsigned char t2[32]; + ge25519 get1, get2; + sc25519 schram, scs; + unsigned char hram[SHA512_DIGEST_LEN]; + + *mlen = (uint64_t) -1; + if (smlen < 64) return -1; + + if (ge25519_unpackneg_vartime(&get1, pk)) { + return -1; + } + + get_hram(hram,sm,pk,m,smlen); + + sc25519_from64bytes(&schram, hram); + + sc25519_from32bytes(&scs, sm+32); + + ge25519_double_scalarmult_vartime(&get2, + &get1, + &schram, + &ge25519_base, + &scs); + ge25519_pack(t2, &get2); + + ret = crypto_verify_32(sm, t2); + if (ret != 0) { + for (i = 0; i < smlen - 64; i++) { + m[i] = sm[i + 64]; + } + *mlen = smlen-64; + } else { + for (i = 0; i < smlen - 64; i++) { + m[i] = 0; + } + } + + return ret; +} diff --git a/src/external/fe25519.c b/src/external/fe25519.c new file mode 100644 index 0000000..a7f26c9 --- /dev/null +++ b/src/external/fe25519.c @@ -0,0 +1,418 @@ +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/fe25519.c + */ + +#include "config.h" + +#define WINDOWSIZE 1 /* Should be 1,2, or 4 */ +#define WINDOWMASK ((1<>= 31; /* 1: yes; 0: no */ + return x; +} + +static uint32_t ge(uint32_t a,uint32_t b) /* 16-bit inputs */ +{ + unsigned int x = a; + + x -= (unsigned int) b; /* 0..65535: yes; 4294901761..4294967295: no */ + x >>= 31; /* 0: yes; 1: no */ + x ^= 1; /* 1: yes; 0: no */ + + return x; +} + +static uint32_t times19(uint32_t a) +{ + return (a << 4) + (a << 1) + a; +} + +static uint32_t times38(uint32_t a) +{ + return (a << 5) + (a << 2) + (a << 1); +} + +static void reduce_add_sub(fe25519 *r) +{ + uint32_t t; + int i,rep; + + for(rep = 0; rep < 4; rep++) { + t = r->v[31] >> 7; + r->v[31] &= 127; + t = times19(t); + r->v[0] += t; + for(i = 0; i < 31; i++) { + t = r->v[i] >> 8; + r->v[i+1] += t; + r->v[i] &= 255; + } + } +} + +static void reduce_mul(fe25519 *r) +{ + uint32_t t; + int i,rep; + + for(rep = 0; rep < 2; rep++) { + t = r->v[31] >> 7; + r->v[31] &= 127; + t = times19(t); + r->v[0] += t; + for(i = 0; i < 31; i++) { + t = r->v[i] >> 8; + r->v[i+1] += t; + r->v[i] &= 255; + } + } +} + +/* reduction modulo 2^255-19 */ +void fe25519_freeze(fe25519 *r) +{ + int i; + uint32_t m = equal(r->v[31],127); + + for (i = 30; i > 0; i--) { + m &= equal(r->v[i],255); + } + m &= ge(r->v[0],237); + + m = -m; + + r->v[31] -= m&127; + for (i = 30; i > 0; i--) { + r->v[i] -= m&255; + } + r->v[0] -= m&237; +} + +void fe25519_unpack(fe25519 *r, const unsigned char x[32]) +{ + int i; + + for (i = 0;i < 32; i++) { + r->v[i] = x[i]; + } + + r->v[31] &= 127; +} + +/* Assumes input x being reduced below 2^255 */ +void fe25519_pack(unsigned char r[32], const fe25519 *x) +{ + int i; + + fe25519 y = *x; + fe25519_freeze(&y); + + for (i = 0; i < 32; i++) { + r[i] = y.v[i]; + } +} + +uint32_t fe25519_iszero(const fe25519 *x) +{ + int i; + uint32_t r; + + fe25519 t = *x; + fe25519_freeze(&t); + + r = equal(t.v[0],0); + for (i = 1; i < 32; i++) { + r &= equal(t.v[i],0); + } + + return r; +} + +int fe25519_iseq_vartime(const fe25519 *x, const fe25519 *y) +{ + int i; + + fe25519 t1 = *x; + fe25519 t2 = *y; + fe25519_freeze(&t1); + fe25519_freeze(&t2); + + for (i = 0; i < 32; i++) { + if(t1.v[i] != t2.v[i]) { + return 0; + } + } + + return 1; +} + +void fe25519_cmov(fe25519 *r, const fe25519 *x, unsigned char b) +{ + int i; + uint32_t mask = b; + + mask = -mask; + + for (i = 0; i < 32; i++) { + r->v[i] ^= mask & (x->v[i] ^ r->v[i]); + } +} + +unsigned char fe25519_getparity(const fe25519 *x) +{ + fe25519 t = *x; + fe25519_freeze(&t); + + return t.v[0] & 1; +} + +void fe25519_setone(fe25519 *r) +{ + int i; + + r->v[0] = 1; + for (i = 1; i < 32; i++) { + r->v[i]=0; + } +} + +void fe25519_setzero(fe25519 *r) +{ + int i; + + for (i = 0; i < 32; i++) { + r->v[i]=0; + } +} + +void fe25519_neg(fe25519 *r, const fe25519 *x) +{ + fe25519 t; + int i; + + for (i = 0; i < 32; i++) { + t.v[i]=x->v[i]; + } + + fe25519_setzero(r); + fe25519_sub(r, r, &t); +} + +void fe25519_add(fe25519 *r, const fe25519 *x, const fe25519 *y) +{ + int i; + + for (i = 0; i < 32; i++) { + r->v[i] = x->v[i] + y->v[i]; + } + + reduce_add_sub(r); +} + +void fe25519_sub(fe25519 *r, const fe25519 *x, const fe25519 *y) +{ + int i; + uint32_t t[32]; + + t[0] = x->v[0] + 0x1da; + t[31] = x->v[31] + 0xfe; + + for (i = 1; i < 31; i++) { + t[i] = x->v[i] + 0x1fe; + } + + for (i = 0; i < 32; i++) { + r->v[i] = t[i] - y->v[i]; + } + + reduce_add_sub(r); +} + +void fe25519_mul(fe25519 *r, const fe25519 *x, const fe25519 *y) +{ + int i,j; + uint32_t t[63]; + + for (i = 0; i < 63; i++) { + t[i] = 0; + } + + for (i = 0; i < 32; i++) { + for (j = 0; j < 32; j++) { + t[i+j] += x->v[i] * y->v[j]; + } + } + + for (i = 32; i < 63; i++) { + r->v[i-32] = t[i-32] + times38(t[i]); + } + r->v[31] = t[31]; /* result now in r[0]...r[31] */ + + reduce_mul(r); +} + +void fe25519_square(fe25519 *r, const fe25519 *x) +{ + fe25519_mul(r, x, x); +} + +void fe25519_invert(fe25519 *r, const fe25519 *x) +{ + fe25519 z2; + fe25519 z9; + fe25519 z11; + fe25519 z2_5_0; + fe25519 z2_10_0; + fe25519 z2_20_0; + fe25519 z2_50_0; + fe25519 z2_100_0; + fe25519 t0; + fe25519 t1; + int i; + + /* 2 */ fe25519_square(&z2, x); + /* 4 */ fe25519_square(&t1, &z2); + /* 8 */ fe25519_square(&t0, &t1); + /* 9 */ fe25519_mul(&z9, &t0, x); + /* 11 */ fe25519_mul(&z11, &z9, &z2); + /* 22 */ fe25519_square(&t0, &z11); + /* 2^5 - 2^0 = 31 */ fe25519_mul(&z2_5_0, &t0, &z9); + + /* 2^6 - 2^1 */ fe25519_square(&t0, &z2_5_0); + /* 2^7 - 2^2 */ fe25519_square(&t1, &t0); + /* 2^8 - 2^3 */ fe25519_square(&t0, &t1); + /* 2^9 - 2^4 */ fe25519_square(&t1, &t0); + /* 2^10 - 2^5 */ fe25519_square(&t0, &t1); + /* 2^10 - 2^0 */ fe25519_mul(&z2_10_0, &t0, &z2_5_0); + + /* 2^11 - 2^1 */ fe25519_square(&t0, &z2_10_0); + /* 2^12 - 2^2 */ fe25519_square(&t1, &t0); + /* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) { + fe25519_square(&t0, &t1); + fe25519_square(&t1, &t0); + } + /* 2^20 - 2^0 */ fe25519_mul(&z2_20_0, &t1, &z2_10_0); + + /* 2^21 - 2^1 */ fe25519_square(&t0, &z2_20_0); + /* 2^22 - 2^2 */ fe25519_square(&t1, &t0); + /* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) { + fe25519_square(&t0, &t1); + fe25519_square(&t1,&t0); + } + /* 2^40 - 2^0 */ fe25519_mul(&t0, &t1, &z2_20_0); + + /* 2^41 - 2^1 */ fe25519_square(&t1, &t0); + /* 2^42 - 2^2 */ fe25519_square(&t0, &t1); + /* 2^50 - 2^10 */ for (i = 2; i < 10;i += 2) { + fe25519_square(&t1, &t0); + fe25519_square(&t0, &t1); + } + /* 2^50 - 2^0 */ fe25519_mul(&z2_50_0,&t0,&z2_10_0); + + /* 2^51 - 2^1 */ fe25519_square(&t0, &z2_50_0); + /* 2^52 - 2^2 */ fe25519_square(&t1, &t0); + /* 2^100 - 2^50 */ for (i = 2; i < 50; i += 2) { + fe25519_square(&t0, &t1); + fe25519_square(&t1,&t0); + } + /* 2^100 - 2^0 */ fe25519_mul(&z2_100_0, &t1, &z2_50_0); + + /* 2^101 - 2^1 */ fe25519_square(&t1, &z2_100_0); + /* 2^102 - 2^2 */ fe25519_square(&t0, &t1); + /* 2^200 - 2^100 */ for (i = 2; i < 100; i += 2) { + fe25519_square(&t1, &t0); + fe25519_square(&t0,&t1); + } + /* 2^200 - 2^0 */ fe25519_mul(&t1, &t0, &z2_100_0); + + /* 2^201 - 2^1 */ fe25519_square(&t0, &t1); + /* 2^202 - 2^2 */ fe25519_square(&t1, &t0); + /* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) { + fe25519_square(&t0, &t1); + fe25519_square(&t1,&t0); + } + /* 2^250 - 2^0 */ fe25519_mul(&t0, &t1, &z2_50_0); + + /* 2^251 - 2^1 */ fe25519_square(&t1, &t0); + /* 2^252 - 2^2 */ fe25519_square(&t0, &t1); + /* 2^253 - 2^3 */ fe25519_square(&t1, &t0); + /* 2^254 - 2^4 */ fe25519_square(&t0, &t1); + /* 2^255 - 2^5 */ fe25519_square(&t1, &t0); + /* 2^255 - 21 */ fe25519_mul(r, &t1, &z11); +} + +void fe25519_pow2523(fe25519 *r, const fe25519 *x) +{ + fe25519 z2; + fe25519 z9; + fe25519 z11; + fe25519 z2_5_0; + fe25519 z2_10_0; + fe25519 z2_20_0; + fe25519 z2_50_0; + fe25519 z2_100_0; + fe25519 t; + int i; + + /* 2 */ fe25519_square(&z2, x); + /* 4 */ fe25519_square(&t, &z2); + /* 8 */ fe25519_square(&t, &t); + /* 9 */ fe25519_mul(&z9, &t, x); + /* 11 */ fe25519_mul(&z11, &z9, &z2); + /* 22 */ fe25519_square(&t, &z11); + /* 2^5 - 2^0 = 31 */ fe25519_mul(&z2_5_0, &t, &z9); + + /* 2^6 - 2^1 */ fe25519_square(&t, &z2_5_0); + /* 2^10 - 2^5 */ for (i = 1; i < 5; i++) { + fe25519_square(&t,&t); + } + /* 2^10 - 2^0 */ fe25519_mul(&z2_10_0, &t, &z2_5_0); + + /* 2^11 - 2^1 */ fe25519_square(&t, &z2_10_0); + /* 2^20 - 2^10 */ for (i = 1; i < 10; i++) { + fe25519_square(&t, &t); + } + /* 2^20 - 2^0 */ fe25519_mul(&z2_20_0, &t, &z2_10_0); + + /* 2^21 - 2^1 */ fe25519_square(&t, &z2_20_0); + /* 2^40 - 2^20 */ for (i = 1; i < 20; i++) { + fe25519_square(&t,&t); + } + /* 2^40 - 2^0 */ fe25519_mul(&t, &t, &z2_20_0); + + /* 2^41 - 2^1 */ fe25519_square(&t, &t); + /* 2^50 - 2^10 */ for (i = 1; i < 10; i++) { + fe25519_square(&t,&t); + } + /* 2^50 - 2^0 */ fe25519_mul(&z2_50_0, &t, &z2_10_0); + + /* 2^51 - 2^1 */ fe25519_square(&t, &z2_50_0); + /* 2^100 - 2^50 */ for (i = 1; i < 50; i++) { + fe25519_square(&t, &t); + } + /* 2^100 - 2^0 */ fe25519_mul(&z2_100_0, &t, &z2_50_0); + + /* 2^101 - 2^1 */ fe25519_square(&t, &z2_100_0); + /* 2^200 - 2^100 */ for (i = 1; i < 100; i++) { + fe25519_square(&t, &t); + } + /* 2^200 - 2^0 */ fe25519_mul(&t, &t, &z2_100_0); + + /* 2^201 - 2^1 */ fe25519_square(&t, &t); + /* 2^250 - 2^50 */ for (i = 1; i < 50; i++) { + fe25519_square(&t, &t); + } + /* 2^250 - 2^0 */ fe25519_mul(&t, &t, &z2_50_0); + + /* 2^251 - 2^1 */ fe25519_square(&t, &t); + /* 2^252 - 2^2 */ fe25519_square(&t, &t); + /* 2^252 - 3 */ fe25519_mul(r, &t, x); +} diff --git a/src/external/ge25519.c b/src/external/ge25519.c new file mode 100644 index 0000000..ffeb1d5 --- /dev/null +++ b/src/external/ge25519.c @@ -0,0 +1,369 @@ +/* $OpenBSD: ge25519.c,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/ge25519.c + */ + +#include "config.h" + +#include "libssh/fe25519.h" +#include "libssh/sc25519.h" +#include "libssh/ge25519.h" + +/* + * Arithmetic on the twisted Edwards curve -x^2 + y^2 = 1 + dx^2y^2 + * with d = -(121665/121666) = 37095705934669439343138083508754565189542113879843219016388785533085940283555 + * Base point: (15112221349535400772501151409588531511454012693041857206046113283949847762202,46316835694926478169428394003475163141307993866256225615783033603165251855960); + */ + +/* d */ +static const fe25519 ge25519_ecd = { + {0xA3, 0x78, 0x59, 0x13, 0xCA, 0x4D, 0xEB, 0x75, + 0xAB, 0xD8, 0x41, 0x41, 0x4D, 0x0A, 0x70, 0x00, + 0x98, 0xE8, 0x79, 0x77, 0x79, 0x40, 0xC7, 0x8C, + 0x73, 0xFE, 0x6F, 0x2B, 0xEE, 0x6C, 0x03, 0x52} +}; + +/* 2*d */ +static const fe25519 ge25519_ec2d = { + {0x59, 0xF1, 0xB2, 0x26, 0x94, 0x9B, 0xD6, 0xEB, + 0x56, 0xB1, 0x83, 0x82, 0x9A, 0x14, 0xE0, 0x00, + 0x30, 0xD1, 0xF3, 0xEE, 0xF2, 0x80, 0x8E, 0x19, + 0xE7, 0xFC, 0xDF, 0x56, 0xDC, 0xD9, 0x06, 0x24} +}; + +/* sqrt(-1) */ +static const fe25519 ge25519_sqrtm1 = { + {0xB0, 0xA0, 0x0E, 0x4A, 0x27, 0x1B, 0xEE, 0xC4, + 0x78, 0xE4, 0x2F, 0xAD, 0x06, 0x18, 0x43, 0x2F, + 0xA7, 0xD7, 0xFB, 0x3D, 0x99, 0x00, 0x4D, 0x2B, + 0x0B, 0xDF, 0xC1, 0x4F, 0x80, 0x24, 0x83, 0x2B} +}; + +#define ge25519_p3 ge25519 + +typedef struct { + fe25519 x; + fe25519 z; + fe25519 y; + fe25519 t; +} ge25519_p1p1; + +typedef struct { + fe25519 x; + fe25519 y; + fe25519 z; +} ge25519_p2; + +typedef struct { + fe25519 x; + fe25519 y; +} ge25519_aff; + + +/* Packed coordinates of the base point */ +const ge25519 ge25519_base = { + {{0x1A, 0xD5, 0x25, 0x8F, 0x60, 0x2D, 0x56, 0xC9, + 0xB2, 0xA7, 0x25, 0x95, 0x60, 0xC7, 0x2C, 0x69, + 0x5C, 0xDC, 0xD6, 0xFD, 0x31, 0xE2, 0xA4, 0xC0, + 0xFE, 0x53, 0x6E, 0xCD, 0xD3, 0x36, 0x69, 0x21}}, + {{0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0xA3, 0xDD, 0xB7, 0xA5, 0xB3, 0x8A, 0xDE, 0x6D, + 0xF5, 0x52, 0x51, 0x77, 0x80, 0x9F, 0xF0, 0x20, + 0x7D, 0xE3, 0xAB, 0x64, 0x8E, 0x4E, 0xEA, 0x66, + 0x65, 0x76, 0x8B, 0xD7, 0x0F, 0x5F, 0x87, 0x67}} +}; + +/* Multiples of the base point in affine representation */ +static const ge25519_aff ge25519_base_multiples_affine[425] = { +#include "ge25519_base.data" +}; + +static void p1p1_to_p2(ge25519_p2 *r, const ge25519_p1p1 *p) +{ + fe25519_mul(&r->x, &p->x, &p->t); + fe25519_mul(&r->y, &p->y, &p->z); + fe25519_mul(&r->z, &p->z, &p->t); +} + +static void p1p1_to_p3(ge25519_p3 *r, const ge25519_p1p1 *p) +{ + p1p1_to_p2((ge25519_p2 *)r, p); + fe25519_mul(&r->t, &p->x, &p->y); +} + +static void ge25519_mixadd2(ge25519_p3 *r, const ge25519_aff *q) +{ + fe25519 a,b,t1,t2,c,d,e,f,g,h,qt; + fe25519_mul(&qt, &q->x, &q->y); + fe25519_sub(&a, &r->y, &r->x); /* A = (Y1-X1)*(Y2-X2) */ + fe25519_add(&b, &r->y, &r->x); /* B = (Y1+X1)*(Y2+X2) */ + fe25519_sub(&t1, &q->y, &q->x); + fe25519_add(&t2, &q->y, &q->x); + fe25519_mul(&a, &a, &t1); + fe25519_mul(&b, &b, &t2); + fe25519_sub(&e, &b, &a); /* E = B-A */ + fe25519_add(&h, &b, &a); /* H = B+A */ + fe25519_mul(&c, &r->t, &qt); /* C = T1*k*T2 */ + fe25519_mul(&c, &c, &ge25519_ec2d); + fe25519_add(&d, &r->z, &r->z); /* D = Z1*2 */ + fe25519_sub(&f, &d, &c); /* F = D-C */ + fe25519_add(&g, &d, &c); /* G = D+C */ + fe25519_mul(&r->x, &e, &f); + fe25519_mul(&r->y, &h, &g); + fe25519_mul(&r->z, &g, &f); + fe25519_mul(&r->t, &e, &h); +} + +static void add_p1p1(ge25519_p1p1 *r, const ge25519_p3 *p, const ge25519_p3 *q) +{ + fe25519 a, b, c, d, t; + + fe25519_sub(&a, &p->y, &p->x); /* A = (Y1-X1)*(Y2-X2) */ + fe25519_sub(&t, &q->y, &q->x); + fe25519_mul(&a, &a, &t); + fe25519_add(&b, &p->x, &p->y); /* B = (Y1+X1)*(Y2+X2) */ + fe25519_add(&t, &q->x, &q->y); + fe25519_mul(&b, &b, &t); + fe25519_mul(&c, &p->t, &q->t); /* C = T1*k*T2 */ + fe25519_mul(&c, &c, &ge25519_ec2d); + fe25519_mul(&d, &p->z, &q->z); /* D = Z1*2*Z2 */ + fe25519_add(&d, &d, &d); + fe25519_sub(&r->x, &b, &a); /* E = B-A */ + fe25519_sub(&r->t, &d, &c); /* F = D-C */ + fe25519_add(&r->z, &d, &c); /* G = D+C */ + fe25519_add(&r->y, &b, &a); /* H = B+A */ +} + +/* See http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#doubling-dbl-2008-hwcd */ +static void dbl_p1p1(ge25519_p1p1 *r, const ge25519_p2 *p) +{ + fe25519 a,b,c,d; + fe25519_square(&a, &p->x); + fe25519_square(&b, &p->y); + fe25519_square(&c, &p->z); + fe25519_add(&c, &c, &c); + fe25519_neg(&d, &a); + + fe25519_add(&r->x, &p->x, &p->y); + fe25519_square(&r->x, &r->x); + fe25519_sub(&r->x, &r->x, &a); + fe25519_sub(&r->x, &r->x, &b); + fe25519_add(&r->z, &d, &b); + fe25519_sub(&r->t, &r->z, &c); + fe25519_sub(&r->y, &d, &b); +} + +/* Constant-time version of: if(b) r = p */ +static void cmov_aff(ge25519_aff *r, const ge25519_aff *p, unsigned char b) +{ + fe25519_cmov(&r->x, &p->x, b); + fe25519_cmov(&r->y, &p->y, b); +} + +static unsigned char equal(signed char b,signed char c) +{ + unsigned char ub = b; + unsigned char uc = c; + unsigned char x = ub ^ uc; /* 0: yes; 1..255: no */ + uint32_t y = x; /* 0: yes; 1..255: no */ + + y -= 1; /* 4294967295: yes; 0..254: no */ + y >>= 31; /* 1: yes; 0: no */ + + return y; +} + +static unsigned char negative(signed char b) +{ + unsigned long long x = b; /* 18446744073709551361..18446744073709551615: yes; 0..255: no */ + + x >>= 63; /* 1: yes; 0: no */ + + return x; +} + +static void choose_t(ge25519_aff *t, unsigned long long pos, signed char b) +{ + /* constant time */ + fe25519 v; + + *t = ge25519_base_multiples_affine[5 * pos + 0]; + + cmov_aff(t, &ge25519_base_multiples_affine[5 * pos + 1], + equal(b,1) | equal(b,-1)); + cmov_aff(t, &ge25519_base_multiples_affine[5 * pos + 2], + equal(b,2) | equal(b,-2)); + cmov_aff(t, &ge25519_base_multiples_affine[5 * pos + 3], + equal(b,3) | equal(b,-3)); + cmov_aff(t, &ge25519_base_multiples_affine[5 * pos + 4], + equal(b,-4)); + + fe25519_neg(&v, &t->x); + fe25519_cmov(&t->x, &v, negative(b)); +} + +static void setneutral(ge25519 *r) +{ + fe25519_setzero(&r->x); + fe25519_setone(&r->y); + fe25519_setone(&r->z); + fe25519_setzero(&r->t); +} + +/* ******************************************************************** + * EXPORTED FUNCTIONS + ******************************************************************** */ + +/* return 0 on success, -1 otherwise */ +int ge25519_unpackneg_vartime(ge25519_p3 *r, const unsigned char p[32]) +{ + unsigned char par; + + fe25519 t, chk, num, den, den2, den4, den6; + fe25519_setone(&r->z); + par = p[31] >> 7; + fe25519_unpack(&r->y, p); + fe25519_square(&num, &r->y); /* x = y^2 */ + fe25519_mul(&den, &num, &ge25519_ecd); /* den = dy^2 */ + fe25519_sub(&num, &num, &r->z); /* x = y^2-1 */ + fe25519_add(&den, &r->z, &den); /* den = dy^2+1 */ + + /* Computation of sqrt(num/den) */ + /* 1.: computation of num^((p-5)/8)*den^((7p-35)/8) = (num*den^7)^((p-5)/8) */ + fe25519_square(&den2, &den); + fe25519_square(&den4, &den2); + fe25519_mul(&den6, &den4, &den2); + fe25519_mul(&t, &den6, &num); + fe25519_mul(&t, &t, &den); + + fe25519_pow2523(&t, &t); + /* 2. computation of r->x = t * num * den^3 */ + fe25519_mul(&t, &t, &num); + fe25519_mul(&t, &t, &den); + fe25519_mul(&t, &t, &den); + fe25519_mul(&r->x, &t, &den); + + /* 3. Check whether sqrt computation gave correct result, multiply by sqrt(-1) if not: */ + fe25519_square(&chk, &r->x); + fe25519_mul(&chk, &chk, &den); + if (!fe25519_iseq_vartime(&chk, &num)) { + fe25519_mul(&r->x, &r->x, &ge25519_sqrtm1); + } + + /* 4. Now we have one of the two square roots, except if input was not a square */ + fe25519_square(&chk, &r->x); + fe25519_mul(&chk, &chk, &den); + if (!fe25519_iseq_vartime(&chk, &num)) { + return -1; + } + + /* 5. Choose the desired square root according to parity: */ + if(fe25519_getparity(&r->x) != (1-par)) { + fe25519_neg(&r->x, &r->x); + } + + fe25519_mul(&r->t, &r->x, &r->y); + + return 0; +} + +void ge25519_pack(unsigned char r[32], const ge25519_p3 *p) +{ + fe25519 tx, ty, zi; + + fe25519_invert(&zi, &p->z); + fe25519_mul(&tx, &p->x, &zi); + fe25519_mul(&ty, &p->y, &zi); + fe25519_pack(r, &ty); + + r[31] ^= fe25519_getparity(&tx) << 7; +} + +int ge25519_isneutral_vartime(const ge25519_p3 *p) +{ + int ret = 1; + + if (!fe25519_iszero(&p->x)) { + ret = 0; + } + + if (!fe25519_iseq_vartime(&p->y, &p->z)) { + ret = 0; + } + + return ret; +} + +/* computes [s1]p1 + [s2]p2 */ +void ge25519_double_scalarmult_vartime(ge25519_p3 *r, const ge25519_p3 *p1, const sc25519 *s1, const ge25519_p3 *p2, const sc25519 *s2) +{ + ge25519_p1p1 tp1p1; + ge25519_p3 pre[16]; + unsigned char b[127]; + int i; + + /* precomputation s2 s1 */ + setneutral(pre); /* 00 00 */ + pre[1] = *p1; /* 00 01 */ + dbl_p1p1(&tp1p1,(ge25519_p2 *)p1); p1p1_to_p3( &pre[2], &tp1p1); /* 00 10 */ + add_p1p1(&tp1p1,&pre[1], &pre[2]); p1p1_to_p3( &pre[3], &tp1p1); /* 00 11 */ + pre[4] = *p2; /* 01 00 */ + add_p1p1(&tp1p1,&pre[1], &pre[4]); p1p1_to_p3( &pre[5], &tp1p1); /* 01 01 */ + add_p1p1(&tp1p1,&pre[2], &pre[4]); p1p1_to_p3( &pre[6], &tp1p1); /* 01 10 */ + add_p1p1(&tp1p1,&pre[3], &pre[4]); p1p1_to_p3( &pre[7], &tp1p1); /* 01 11 */ + dbl_p1p1(&tp1p1,(ge25519_p2 *)p2); p1p1_to_p3( &pre[8], &tp1p1); /* 10 00 */ + add_p1p1(&tp1p1,&pre[1], &pre[8]); p1p1_to_p3( &pre[9], &tp1p1); /* 10 01 */ + dbl_p1p1(&tp1p1,(ge25519_p2 *)&pre[5]); p1p1_to_p3(&pre[10], &tp1p1); /* 10 10 */ + add_p1p1(&tp1p1,&pre[3], &pre[8]); p1p1_to_p3(&pre[11], &tp1p1); /* 10 11 */ + add_p1p1(&tp1p1,&pre[4], &pre[8]); p1p1_to_p3(&pre[12], &tp1p1); /* 11 00 */ + add_p1p1(&tp1p1,&pre[1],&pre[12]); p1p1_to_p3(&pre[13], &tp1p1); /* 11 01 */ + add_p1p1(&tp1p1,&pre[2],&pre[12]); p1p1_to_p3(&pre[14], &tp1p1); /* 11 10 */ + add_p1p1(&tp1p1,&pre[3],&pre[12]); p1p1_to_p3(&pre[15], &tp1p1); /* 11 11 */ + + sc25519_2interleave2(b,s1,s2); + + /* scalar multiplication */ + *r = pre[b[126]]; + + for (i = 125; i >= 0; i--) { + dbl_p1p1(&tp1p1, (ge25519_p2 *)r); + p1p1_to_p2((ge25519_p2 *) r, &tp1p1); + dbl_p1p1(&tp1p1, (ge25519_p2 *)r); + if(b[i] != 0) { + p1p1_to_p3(r, &tp1p1); + add_p1p1(&tp1p1, r, &pre[b[i]]); + } + if (i != 0) { + p1p1_to_p2((ge25519_p2 *)r, &tp1p1); + } else { + p1p1_to_p3(r, &tp1p1); + } + } +} + +void ge25519_scalarmult_base(ge25519_p3 *r, const sc25519 *s) +{ + signed char b[85]; + int i; + ge25519_aff t; + + sc25519_window3(b,s); + + choose_t((ge25519_aff *)r, 0, b[0]); + fe25519_setone(&r->z); + fe25519_mul(&r->t, &r->x, &r->y); + for (i = 1; i < 85; i++) { + choose_t(&t, (unsigned long long) i, b[i]); + ge25519_mixadd2(r, &t); + } +} diff --git a/src/external/ge25519_base.data b/src/external/ge25519_base.data new file mode 100644 index 0000000..e6a4227 --- /dev/null +++ b/src/external/ge25519_base.data @@ -0,0 +1,858 @@ +/* $OpenBSD: ge25519_base.data,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/ge25519_base.data + */ + +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x1a, 0xd5, 0x25, 0x8f, 0x60, 0x2d, 0x56, 0xc9, 0xb2, 0xa7, 0x25, 0x95, 0x60, 0xc7, 0x2c, 0x69, 0x5c, 0xdc, 0xd6, 0xfd, 0x31, 0xe2, 0xa4, 0xc0, 0xfe, 0x53, 0x6e, 0xcd, 0xd3, 0x36, 0x69, 0x21}} , + {{0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}}}, +{{{0x0e, 0xce, 0x43, 0x28, 0x4e, 0xa1, 0xc5, 0x83, 0x5f, 0xa4, 0xd7, 0x15, 0x45, 0x8e, 0x0d, 0x08, 0xac, 0xe7, 0x33, 0x18, 0x7d, 0x3b, 0x04, 0x3d, 0x6c, 0x04, 0x5a, 0x9f, 0x4c, 0x38, 0xab, 0x36}} , + {{0xc9, 0xa3, 0xf8, 0x6a, 0xae, 0x46, 0x5f, 0x0e, 0x56, 0x51, 0x38, 0x64, 0x51, 0x0f, 0x39, 0x97, 0x56, 0x1f, 0xa2, 0xc9, 0xe8, 0x5e, 0xa2, 0x1d, 0xc2, 0x29, 0x23, 0x09, 0xf3, 0xcd, 0x60, 0x22}}}, +{{{0x5c, 0xe2, 0xf8, 0xd3, 0x5f, 0x48, 0x62, 0xac, 0x86, 0x48, 0x62, 0x81, 0x19, 0x98, 0x43, 0x63, 0x3a, 0xc8, 0xda, 0x3e, 0x74, 0xae, 0xf4, 0x1f, 0x49, 0x8f, 0x92, 0x22, 0x4a, 0x9c, 0xae, 0x67}} , + {{0xd4, 0xb4, 0xf5, 0x78, 0x48, 0x68, 0xc3, 0x02, 0x04, 0x03, 0x24, 0x67, 0x17, 0xec, 0x16, 0x9f, 0xf7, 0x9e, 0x26, 0x60, 0x8e, 0xa1, 0x26, 0xa1, 0xab, 0x69, 0xee, 0x77, 0xd1, 0xb1, 0x67, 0x12}}}, +{{{0x70, 0xf8, 0xc9, 0xc4, 0x57, 0xa6, 0x3a, 0x49, 0x47, 0x15, 0xce, 0x93, 0xc1, 0x9e, 0x73, 0x1a, 0xf9, 0x20, 0x35, 0x7a, 0xb8, 0xd4, 0x25, 0x83, 0x46, 0xf1, 0xcf, 0x56, 0xdb, 0xa8, 0x3d, 0x20}} , + {{0x2f, 0x11, 0x32, 0xca, 0x61, 0xab, 0x38, 0xdf, 0xf0, 0x0f, 0x2f, 0xea, 0x32, 0x28, 0xf2, 0x4c, 0x6c, 0x71, 0xd5, 0x80, 0x85, 0xb8, 0x0e, 0x47, 0xe1, 0x95, 0x15, 0xcb, 0x27, 0xe8, 0xd0, 0x47}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xc8, 0x84, 0xa5, 0x08, 0xbc, 0xfd, 0x87, 0x3b, 0x99, 0x8b, 0x69, 0x80, 0x7b, 0xc6, 0x3a, 0xeb, 0x93, 0xcf, 0x4e, 0xf8, 0x5c, 0x2d, 0x86, 0x42, 0xb6, 0x71, 0xd7, 0x97, 0x5f, 0xe1, 0x42, 0x67}} , + {{0xb4, 0xb9, 0x37, 0xfc, 0xa9, 0x5b, 0x2f, 0x1e, 0x93, 0xe4, 0x1e, 0x62, 0xfc, 0x3c, 0x78, 0x81, 0x8f, 0xf3, 0x8a, 0x66, 0x09, 0x6f, 0xad, 0x6e, 0x79, 0x73, 0xe5, 0xc9, 0x00, 0x06, 0xd3, 0x21}}}, +{{{0xf8, 0xf9, 0x28, 0x6c, 0x6d, 0x59, 0xb2, 0x59, 0x74, 0x23, 0xbf, 0xe7, 0x33, 0x8d, 0x57, 0x09, 0x91, 0x9c, 0x24, 0x08, 0x15, 0x2b, 0xe2, 0xb8, 0xee, 0x3a, 0xe5, 0x27, 0x06, 0x86, 0xa4, 0x23}} , + {{0xeb, 0x27, 0x67, 0xc1, 0x37, 0xab, 0x7a, 0xd8, 0x27, 0x9c, 0x07, 0x8e, 0xff, 0x11, 0x6a, 0xb0, 0x78, 0x6e, 0xad, 0x3a, 0x2e, 0x0f, 0x98, 0x9f, 0x72, 0xc3, 0x7f, 0x82, 0xf2, 0x96, 0x96, 0x70}}}, +{{{0x81, 0x6b, 0x88, 0xe8, 0x1e, 0xc7, 0x77, 0x96, 0x0e, 0xa1, 0xa9, 0x52, 0xe0, 0xd8, 0x0e, 0x61, 0x9e, 0x79, 0x2d, 0x95, 0x9c, 0x8d, 0x96, 0xe0, 0x06, 0x40, 0x5d, 0x87, 0x28, 0x5f, 0x98, 0x70}} , + {{0xf1, 0x79, 0x7b, 0xed, 0x4f, 0x44, 0xb2, 0xe7, 0x08, 0x0d, 0xc2, 0x08, 0x12, 0xd2, 0x9f, 0xdf, 0xcd, 0x93, 0x20, 0x8a, 0xcf, 0x33, 0xca, 0x6d, 0x89, 0xb9, 0x77, 0xc8, 0x93, 0x1b, 0x4e, 0x60}}}, +{{{0x26, 0x4f, 0x7e, 0x97, 0xf6, 0x40, 0xdd, 0x4f, 0xfc, 0x52, 0x78, 0xf9, 0x90, 0x31, 0x03, 0xe6, 0x7d, 0x56, 0x39, 0x0b, 0x1d, 0x56, 0x82, 0x85, 0xf9, 0x1a, 0x42, 0x17, 0x69, 0x6c, 0xcf, 0x39}} , + {{0x69, 0xd2, 0x06, 0x3a, 0x4f, 0x39, 0x2d, 0xf9, 0x38, 0x40, 0x8c, 0x4c, 0xe7, 0x05, 0x12, 0xb4, 0x78, 0x8b, 0xf8, 0xc0, 0xec, 0x93, 0xde, 0x7a, 0x6b, 0xce, 0x2c, 0xe1, 0x0e, 0xa9, 0x34, 0x44}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x0b, 0xa4, 0x3c, 0xb0, 0x0f, 0x7a, 0x51, 0xf1, 0x78, 0xd6, 0xd9, 0x6a, 0xfd, 0x46, 0xe8, 0xb8, 0xa8, 0x79, 0x1d, 0x87, 0xf9, 0x90, 0xf2, 0x9c, 0x13, 0x29, 0xf8, 0x0b, 0x20, 0x64, 0xfa, 0x05}} , + {{0x26, 0x09, 0xda, 0x17, 0xaf, 0x95, 0xd6, 0xfb, 0x6a, 0x19, 0x0d, 0x6e, 0x5e, 0x12, 0xf1, 0x99, 0x4c, 0xaa, 0xa8, 0x6f, 0x79, 0x86, 0xf4, 0x72, 0x28, 0x00, 0x26, 0xf9, 0xea, 0x9e, 0x19, 0x3d}}}, +{{{0x87, 0xdd, 0xcf, 0xf0, 0x5b, 0x49, 0xa2, 0x5d, 0x40, 0x7a, 0x23, 0x26, 0xa4, 0x7a, 0x83, 0x8a, 0xb7, 0x8b, 0xd2, 0x1a, 0xbf, 0xea, 0x02, 0x24, 0x08, 0x5f, 0x7b, 0xa9, 0xb1, 0xbe, 0x9d, 0x37}} , + {{0xfc, 0x86, 0x4b, 0x08, 0xee, 0xe7, 0xa0, 0xfd, 0x21, 0x45, 0x09, 0x34, 0xc1, 0x61, 0x32, 0x23, 0xfc, 0x9b, 0x55, 0x48, 0x53, 0x99, 0xf7, 0x63, 0xd0, 0x99, 0xce, 0x01, 0xe0, 0x9f, 0xeb, 0x28}}}, +{{{0x47, 0xfc, 0xab, 0x5a, 0x17, 0xf0, 0x85, 0x56, 0x3a, 0x30, 0x86, 0x20, 0x28, 0x4b, 0x8e, 0x44, 0x74, 0x3a, 0x6e, 0x02, 0xf1, 0x32, 0x8f, 0x9f, 0x3f, 0x08, 0x35, 0xe9, 0xca, 0x16, 0x5f, 0x6e}} , + {{0x1c, 0x59, 0x1c, 0x65, 0x5d, 0x34, 0xa4, 0x09, 0xcd, 0x13, 0x9c, 0x70, 0x7d, 0xb1, 0x2a, 0xc5, 0x88, 0xaf, 0x0b, 0x60, 0xc7, 0x9f, 0x34, 0x8d, 0xd6, 0xb7, 0x7f, 0xea, 0x78, 0x65, 0x8d, 0x77}}}, +{{{0x56, 0xa5, 0xc2, 0x0c, 0xdd, 0xbc, 0xb8, 0x20, 0x6d, 0x57, 0x61, 0xb5, 0xfb, 0x78, 0xb5, 0xd4, 0x49, 0x54, 0x90, 0x26, 0xc1, 0xcb, 0xe9, 0xe6, 0xbf, 0xec, 0x1d, 0x4e, 0xed, 0x07, 0x7e, 0x5e}} , + {{0xc7, 0xf6, 0x6c, 0x56, 0x31, 0x20, 0x14, 0x0e, 0xa8, 0xd9, 0x27, 0xc1, 0x9a, 0x3d, 0x1b, 0x7d, 0x0e, 0x26, 0xd3, 0x81, 0xaa, 0xeb, 0xf5, 0x6b, 0x79, 0x02, 0xf1, 0x51, 0x5c, 0x75, 0x55, 0x0f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x0a, 0x34, 0xcd, 0x82, 0x3c, 0x33, 0x09, 0x54, 0xd2, 0x61, 0x39, 0x30, 0x9b, 0xfd, 0xef, 0x21, 0x26, 0xd4, 0x70, 0xfa, 0xee, 0xf9, 0x31, 0x33, 0x73, 0x84, 0xd0, 0xb3, 0x81, 0xbf, 0xec, 0x2e}} , + {{0xe8, 0x93, 0x8b, 0x00, 0x64, 0xf7, 0x9c, 0xb8, 0x74, 0xe0, 0xe6, 0x49, 0x48, 0x4d, 0x4d, 0x48, 0xb6, 0x19, 0xa1, 0x40, 0xb7, 0xd9, 0x32, 0x41, 0x7c, 0x82, 0x37, 0xa1, 0x2d, 0xdc, 0xd2, 0x54}}}, +{{{0x68, 0x2b, 0x4a, 0x5b, 0xd5, 0xc7, 0x51, 0x91, 0x1d, 0xe1, 0x2a, 0x4b, 0xc4, 0x47, 0xf1, 0xbc, 0x7a, 0xb3, 0xcb, 0xc8, 0xb6, 0x7c, 0xac, 0x90, 0x05, 0xfd, 0xf3, 0xf9, 0x52, 0x3a, 0x11, 0x6b}} , + {{0x3d, 0xc1, 0x27, 0xf3, 0x59, 0x43, 0x95, 0x90, 0xc5, 0x96, 0x79, 0xf5, 0xf4, 0x95, 0x65, 0x29, 0x06, 0x9c, 0x51, 0x05, 0x18, 0xda, 0xb8, 0x2e, 0x79, 0x7e, 0x69, 0x59, 0x71, 0x01, 0xeb, 0x1a}}}, +{{{0x15, 0x06, 0x49, 0xb6, 0x8a, 0x3c, 0xea, 0x2f, 0x34, 0x20, 0x14, 0xc3, 0xaa, 0xd6, 0xaf, 0x2c, 0x3e, 0xbd, 0x65, 0x20, 0xe2, 0x4d, 0x4b, 0x3b, 0xeb, 0x9f, 0x4a, 0xc3, 0xad, 0xa4, 0x3b, 0x60}} , + {{0xbc, 0x58, 0xe6, 0xc0, 0x95, 0x2a, 0x2a, 0x81, 0x9a, 0x7a, 0xf3, 0xd2, 0x06, 0xbe, 0x48, 0xbc, 0x0c, 0xc5, 0x46, 0xe0, 0x6a, 0xd4, 0xac, 0x0f, 0xd9, 0xcc, 0x82, 0x34, 0x2c, 0xaf, 0xdb, 0x1f}}}, +{{{0xf7, 0x17, 0x13, 0xbd, 0xfb, 0xbc, 0xd2, 0xec, 0x45, 0xb3, 0x15, 0x31, 0xe9, 0xaf, 0x82, 0x84, 0x3d, 0x28, 0xc6, 0xfc, 0x11, 0xf5, 0x41, 0xb5, 0x8b, 0xd3, 0x12, 0x76, 0x52, 0xe7, 0x1a, 0x3c}} , + {{0x4e, 0x36, 0x11, 0x07, 0xa2, 0x15, 0x20, 0x51, 0xc4, 0x2a, 0xc3, 0x62, 0x8b, 0x5e, 0x7f, 0xa6, 0x0f, 0xf9, 0x45, 0x85, 0x6c, 0x11, 0x86, 0xb7, 0x7e, 0xe5, 0xd7, 0xf9, 0xc3, 0x91, 0x1c, 0x05}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xea, 0xd6, 0xde, 0x29, 0x3a, 0x00, 0xb9, 0x02, 0x59, 0xcb, 0x26, 0xc4, 0xba, 0x99, 0xb1, 0x97, 0x2f, 0x8e, 0x00, 0x92, 0x26, 0x4f, 0x52, 0xeb, 0x47, 0x1b, 0x89, 0x8b, 0x24, 0xc0, 0x13, 0x7d}} , + {{0xd5, 0x20, 0x5b, 0x80, 0xa6, 0x80, 0x20, 0x95, 0xc3, 0xe9, 0x9f, 0x8e, 0x87, 0x9e, 0x1e, 0x9e, 0x7a, 0xc7, 0xcc, 0x75, 0x6c, 0xa5, 0xf1, 0x91, 0x1a, 0xa8, 0x01, 0x2c, 0xab, 0x76, 0xa9, 0x59}}}, +{{{0xde, 0xc9, 0xb1, 0x31, 0x10, 0x16, 0xaa, 0x35, 0x14, 0x6a, 0xd4, 0xb5, 0x34, 0x82, 0x71, 0xd2, 0x4a, 0x5d, 0x9a, 0x1f, 0x53, 0x26, 0x3c, 0xe5, 0x8e, 0x8d, 0x33, 0x7f, 0xff, 0xa9, 0xd5, 0x17}} , + {{0x89, 0xaf, 0xf6, 0xa4, 0x64, 0xd5, 0x10, 0xe0, 0x1d, 0xad, 0xef, 0x44, 0xbd, 0xda, 0x83, 0xac, 0x7a, 0xa8, 0xf0, 0x1c, 0x07, 0xf9, 0xc3, 0x43, 0x6c, 0x3f, 0xb7, 0xd3, 0x87, 0x22, 0x02, 0x73}}}, +{{{0x64, 0x1d, 0x49, 0x13, 0x2f, 0x71, 0xec, 0x69, 0x87, 0xd0, 0x42, 0xee, 0x13, 0xec, 0xe3, 0xed, 0x56, 0x7b, 0xbf, 0xbd, 0x8c, 0x2f, 0x7d, 0x7b, 0x9d, 0x28, 0xec, 0x8e, 0x76, 0x2f, 0x6f, 0x08}} , + {{0x22, 0xf5, 0x5f, 0x4d, 0x15, 0xef, 0xfc, 0x4e, 0x57, 0x03, 0x36, 0x89, 0xf0, 0xeb, 0x5b, 0x91, 0xd6, 0xe2, 0xca, 0x01, 0xa5, 0xee, 0x52, 0xec, 0xa0, 0x3c, 0x8f, 0x33, 0x90, 0x5a, 0x94, 0x72}}}, +{{{0x8a, 0x4b, 0xe7, 0x38, 0xbc, 0xda, 0xc2, 0xb0, 0x85, 0xe1, 0x4a, 0xfe, 0x2d, 0x44, 0x84, 0xcb, 0x20, 0x6b, 0x2d, 0xbf, 0x11, 0x9c, 0xd7, 0xbe, 0xd3, 0x3e, 0x5f, 0xbf, 0x68, 0xbc, 0xa8, 0x07}} , + {{0x01, 0x89, 0x28, 0x22, 0x6a, 0x78, 0xaa, 0x29, 0x03, 0xc8, 0x74, 0x95, 0x03, 0x3e, 0xdc, 0xbd, 0x07, 0x13, 0xa8, 0xa2, 0x20, 0x2d, 0xb3, 0x18, 0x70, 0x42, 0xfd, 0x7a, 0xc4, 0xd7, 0x49, 0x72}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x02, 0xff, 0x32, 0x2b, 0x5c, 0x93, 0x54, 0x32, 0xe8, 0x57, 0x54, 0x1a, 0x8b, 0x33, 0x60, 0x65, 0xd3, 0x67, 0xa4, 0xc1, 0x26, 0xc4, 0xa4, 0x34, 0x1f, 0x9b, 0xa7, 0xa9, 0xf4, 0xd9, 0x4f, 0x5b}} , + {{0x46, 0x8d, 0xb0, 0x33, 0x54, 0x26, 0x5b, 0x68, 0xdf, 0xbb, 0xc5, 0xec, 0xc2, 0xf9, 0x3c, 0x5a, 0x37, 0xc1, 0x8e, 0x27, 0x47, 0xaa, 0x49, 0x5a, 0xf8, 0xfb, 0x68, 0x04, 0x23, 0xd1, 0xeb, 0x40}}}, +{{{0x65, 0xa5, 0x11, 0x84, 0x8a, 0x67, 0x9d, 0x9e, 0xd1, 0x44, 0x68, 0x7a, 0x34, 0xe1, 0x9f, 0xa3, 0x54, 0xcd, 0x07, 0xca, 0x79, 0x1f, 0x54, 0x2f, 0x13, 0x70, 0x4e, 0xee, 0xa2, 0xfa, 0xe7, 0x5d}} , + {{0x36, 0xec, 0x54, 0xf8, 0xce, 0xe4, 0x85, 0xdf, 0xf6, 0x6f, 0x1d, 0x90, 0x08, 0xbc, 0xe8, 0xc0, 0x92, 0x2d, 0x43, 0x6b, 0x92, 0xa9, 0x8e, 0xab, 0x0a, 0x2e, 0x1c, 0x1e, 0x64, 0x23, 0x9f, 0x2c}}}, +{{{0xa7, 0xd6, 0x2e, 0xd5, 0xcc, 0xd4, 0xcb, 0x5a, 0x3b, 0xa7, 0xf9, 0x46, 0x03, 0x1d, 0xad, 0x2b, 0x34, 0x31, 0x90, 0x00, 0x46, 0x08, 0x82, 0x14, 0xc4, 0xe0, 0x9c, 0xf0, 0xe3, 0x55, 0x43, 0x31}} , + {{0x60, 0xd6, 0xdd, 0x78, 0xe6, 0xd4, 0x22, 0x42, 0x1f, 0x00, 0xf9, 0xb1, 0x6a, 0x63, 0xe2, 0x92, 0x59, 0xd1, 0x1a, 0xb7, 0x00, 0x54, 0x29, 0xc9, 0xc1, 0xf6, 0x6f, 0x7a, 0xc5, 0x3c, 0x5f, 0x65}}}, +{{{0x27, 0x4f, 0xd0, 0x72, 0xb1, 0x11, 0x14, 0x27, 0x15, 0x94, 0x48, 0x81, 0x7e, 0x74, 0xd8, 0x32, 0xd5, 0xd1, 0x11, 0x28, 0x60, 0x63, 0x36, 0x32, 0x37, 0xb5, 0x13, 0x1c, 0xa0, 0x37, 0xe3, 0x74}} , + {{0xf1, 0x25, 0x4e, 0x11, 0x96, 0x67, 0xe6, 0x1c, 0xc2, 0xb2, 0x53, 0xe2, 0xda, 0x85, 0xee, 0xb2, 0x9f, 0x59, 0xf3, 0xba, 0xbd, 0xfa, 0xcf, 0x6e, 0xf9, 0xda, 0xa4, 0xb3, 0x02, 0x8f, 0x64, 0x08}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x34, 0x94, 0xf2, 0x64, 0x54, 0x47, 0x37, 0x07, 0x40, 0x8a, 0x20, 0xba, 0x4a, 0x55, 0xd7, 0x3f, 0x47, 0xba, 0x25, 0x23, 0x14, 0xb0, 0x2c, 0xe8, 0x55, 0xa8, 0xa6, 0xef, 0x51, 0xbd, 0x6f, 0x6a}} , + {{0x71, 0xd6, 0x16, 0x76, 0xb2, 0x06, 0xea, 0x79, 0xf5, 0xc4, 0xc3, 0x52, 0x7e, 0x61, 0xd1, 0xe1, 0xad, 0x70, 0x78, 0x1d, 0x16, 0x11, 0xf8, 0x7c, 0x2b, 0xfc, 0x55, 0x9f, 0x52, 0xf8, 0xf5, 0x16}}}, +{{{0x34, 0x96, 0x9a, 0xf6, 0xc5, 0xe0, 0x14, 0x03, 0x24, 0x0e, 0x4c, 0xad, 0x9e, 0x9a, 0x70, 0x23, 0x96, 0xb2, 0xf1, 0x2e, 0x9d, 0xc3, 0x32, 0x9b, 0x54, 0xa5, 0x73, 0xde, 0x88, 0xb1, 0x3e, 0x24}} , + {{0xf6, 0xe2, 0x4c, 0x1f, 0x5b, 0xb2, 0xaf, 0x82, 0xa5, 0xcf, 0x81, 0x10, 0x04, 0xef, 0xdb, 0xa2, 0xcc, 0x24, 0xb2, 0x7e, 0x0b, 0x7a, 0xeb, 0x01, 0xd8, 0x52, 0xf4, 0x51, 0x89, 0x29, 0x79, 0x37}}}, +{{{0x74, 0xde, 0x12, 0xf3, 0x68, 0xb7, 0x66, 0xc3, 0xee, 0x68, 0xdc, 0x81, 0xb5, 0x55, 0x99, 0xab, 0xd9, 0x28, 0x63, 0x6d, 0x8b, 0x40, 0x69, 0x75, 0x6c, 0xcd, 0x5c, 0x2a, 0x7e, 0x32, 0x7b, 0x29}} , + {{0x02, 0xcc, 0x22, 0x74, 0x4d, 0x19, 0x07, 0xc0, 0xda, 0xb5, 0x76, 0x51, 0x2a, 0xaa, 0xa6, 0x0a, 0x5f, 0x26, 0xd4, 0xbc, 0xaf, 0x48, 0x88, 0x7f, 0x02, 0xbc, 0xf2, 0xe1, 0xcf, 0xe9, 0xdd, 0x15}}}, +{{{0xed, 0xb5, 0x9a, 0x8c, 0x9a, 0xdd, 0x27, 0xf4, 0x7f, 0x47, 0xd9, 0x52, 0xa7, 0xcd, 0x65, 0xa5, 0x31, 0x22, 0xed, 0xa6, 0x63, 0x5b, 0x80, 0x4a, 0xad, 0x4d, 0xed, 0xbf, 0xee, 0x49, 0xb3, 0x06}} , + {{0xf8, 0x64, 0x8b, 0x60, 0x90, 0xe9, 0xde, 0x44, 0x77, 0xb9, 0x07, 0x36, 0x32, 0xc2, 0x50, 0xf5, 0x65, 0xdf, 0x48, 0x4c, 0x37, 0xaa, 0x68, 0xab, 0x9a, 0x1f, 0x3e, 0xff, 0x89, 0x92, 0xa0, 0x07}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x7d, 0x4f, 0x9c, 0x19, 0xc0, 0x4a, 0x31, 0xec, 0xf9, 0xaa, 0xeb, 0xb2, 0x16, 0x9c, 0xa3, 0x66, 0x5f, 0xd1, 0xd4, 0xed, 0xb8, 0x92, 0x1c, 0xab, 0xda, 0xea, 0xd9, 0x57, 0xdf, 0x4c, 0x2a, 0x48}} , + {{0x4b, 0xb0, 0x4e, 0x6e, 0x11, 0x3b, 0x51, 0xbd, 0x6a, 0xfd, 0xe4, 0x25, 0xa5, 0x5f, 0x11, 0x3f, 0x98, 0x92, 0x51, 0x14, 0xc6, 0x5f, 0x3c, 0x0b, 0xa8, 0xf7, 0xc2, 0x81, 0x43, 0xde, 0x91, 0x73}}}, +{{{0x3c, 0x8f, 0x9f, 0x33, 0x2a, 0x1f, 0x43, 0x33, 0x8f, 0x68, 0xff, 0x1f, 0x3d, 0x73, 0x6b, 0xbf, 0x68, 0xcc, 0x7d, 0x13, 0x6c, 0x24, 0x4b, 0xcc, 0x4d, 0x24, 0x0d, 0xfe, 0xde, 0x86, 0xad, 0x3b}} , + {{0x79, 0x51, 0x81, 0x01, 0xdc, 0x73, 0x53, 0xe0, 0x6e, 0x9b, 0xea, 0x68, 0x3f, 0x5c, 0x14, 0x84, 0x53, 0x8d, 0x4b, 0xc0, 0x9f, 0x9f, 0x89, 0x2b, 0x8c, 0xba, 0x86, 0xfa, 0xf2, 0xcd, 0xe3, 0x2d}}}, +{{{0x06, 0xf9, 0x29, 0x5a, 0xdb, 0x3d, 0x84, 0x52, 0xab, 0xcc, 0x6b, 0x60, 0x9d, 0xb7, 0x4a, 0x0e, 0x36, 0x63, 0x91, 0xad, 0xa0, 0x95, 0xb0, 0x97, 0x89, 0x4e, 0xcf, 0x7d, 0x3c, 0xe5, 0x7c, 0x28}} , + {{0x2e, 0x69, 0x98, 0xfd, 0xc6, 0xbd, 0xcc, 0xca, 0xdf, 0x9a, 0x44, 0x7e, 0x9d, 0xca, 0x89, 0x6d, 0xbf, 0x27, 0xc2, 0xf8, 0xcd, 0x46, 0x00, 0x2b, 0xb5, 0x58, 0x4e, 0xb7, 0x89, 0x09, 0xe9, 0x2d}}}, +{{{0x54, 0xbe, 0x75, 0xcb, 0x05, 0xb0, 0x54, 0xb7, 0xe7, 0x26, 0x86, 0x4a, 0xfc, 0x19, 0xcf, 0x27, 0x46, 0xd4, 0x22, 0x96, 0x5a, 0x11, 0xe8, 0xd5, 0x1b, 0xed, 0x71, 0xc5, 0x5d, 0xc8, 0xaf, 0x45}} , + {{0x40, 0x7b, 0x77, 0x57, 0x49, 0x9e, 0x80, 0x39, 0x23, 0xee, 0x81, 0x0b, 0x22, 0xcf, 0xdb, 0x7a, 0x2f, 0x14, 0xb8, 0x57, 0x8f, 0xa1, 0x39, 0x1e, 0x77, 0xfc, 0x0b, 0xa6, 0xbf, 0x8a, 0x0c, 0x6c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x77, 0x3a, 0xd4, 0xd8, 0x27, 0xcf, 0xe8, 0xa1, 0x72, 0x9d, 0xca, 0xdd, 0x0d, 0x96, 0xda, 0x79, 0xed, 0x56, 0x42, 0x15, 0x60, 0xc7, 0x1c, 0x6b, 0x26, 0x30, 0xf6, 0x6a, 0x95, 0x67, 0xf3, 0x0a}} , + {{0xc5, 0x08, 0xa4, 0x2b, 0x2f, 0xbd, 0x31, 0x81, 0x2a, 0xa6, 0xb6, 0xe4, 0x00, 0x91, 0xda, 0x3d, 0xb2, 0xb0, 0x96, 0xce, 0x8a, 0xd2, 0x8d, 0x70, 0xb3, 0xd3, 0x34, 0x01, 0x90, 0x8d, 0x10, 0x21}}}, +{{{0x33, 0x0d, 0xe7, 0xba, 0x4f, 0x07, 0xdf, 0x8d, 0xea, 0x7d, 0xa0, 0xc5, 0xd6, 0xb1, 0xb0, 0xe5, 0x57, 0x1b, 0x5b, 0xf5, 0x45, 0x13, 0x14, 0x64, 0x5a, 0xeb, 0x5c, 0xfc, 0x54, 0x01, 0x76, 0x2b}} , + {{0x02, 0x0c, 0xc2, 0xaf, 0x96, 0x36, 0xfe, 0x4a, 0xe2, 0x54, 0x20, 0x6a, 0xeb, 0xb2, 0x9f, 0x62, 0xd7, 0xce, 0xa2, 0x3f, 0x20, 0x11, 0x34, 0x37, 0xe0, 0x42, 0xed, 0x6f, 0xf9, 0x1a, 0xc8, 0x7d}}}, +{{{0xd8, 0xb9, 0x11, 0xe8, 0x36, 0x3f, 0x42, 0xc1, 0xca, 0xdc, 0xd3, 0xf1, 0xc8, 0x23, 0x3d, 0x4f, 0x51, 0x7b, 0x9d, 0x8d, 0xd8, 0xe4, 0xa0, 0xaa, 0xf3, 0x04, 0xd6, 0x11, 0x93, 0xc8, 0x35, 0x45}} , + {{0x61, 0x36, 0xd6, 0x08, 0x90, 0xbf, 0xa7, 0x7a, 0x97, 0x6c, 0x0f, 0x84, 0xd5, 0x33, 0x2d, 0x37, 0xc9, 0x6a, 0x80, 0x90, 0x3d, 0x0a, 0xa2, 0xaa, 0xe1, 0xb8, 0x84, 0xba, 0x61, 0x36, 0xdd, 0x69}}}, +{{{0x6b, 0xdb, 0x5b, 0x9c, 0xc6, 0x92, 0xbc, 0x23, 0xaf, 0xc5, 0xb8, 0x75, 0xf8, 0x42, 0xfa, 0xd6, 0xb6, 0x84, 0x94, 0x63, 0x98, 0x93, 0x48, 0x78, 0x38, 0xcd, 0xbb, 0x18, 0x34, 0xc3, 0xdb, 0x67}} , + {{0x96, 0xf3, 0x3a, 0x09, 0x56, 0xb0, 0x6f, 0x7c, 0x51, 0x1e, 0x1b, 0x39, 0x48, 0xea, 0xc9, 0x0c, 0x25, 0xa2, 0x7a, 0xca, 0xe7, 0x92, 0xfc, 0x59, 0x30, 0xa3, 0x89, 0x85, 0xdf, 0x6f, 0x43, 0x38}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x79, 0x84, 0x44, 0x19, 0xbd, 0xe9, 0x54, 0xc4, 0xc0, 0x6e, 0x2a, 0xa8, 0xa8, 0x9b, 0x43, 0xd5, 0x71, 0x22, 0x5f, 0xdc, 0x01, 0xfa, 0xdf, 0xb3, 0xb8, 0x47, 0x4b, 0x0a, 0xa5, 0x44, 0xea, 0x29}} , + {{0x05, 0x90, 0x50, 0xaf, 0x63, 0x5f, 0x9d, 0x9e, 0xe1, 0x9d, 0x38, 0x97, 0x1f, 0x6c, 0xac, 0x30, 0x46, 0xb2, 0x6a, 0x19, 0xd1, 0x4b, 0xdb, 0xbb, 0x8c, 0xda, 0x2e, 0xab, 0xc8, 0x5a, 0x77, 0x6c}}}, +{{{0x2b, 0xbe, 0xaf, 0xa1, 0x6d, 0x2f, 0x0b, 0xb1, 0x8f, 0xe3, 0xe0, 0x38, 0xcd, 0x0b, 0x41, 0x1b, 0x4a, 0x15, 0x07, 0xf3, 0x6f, 0xdc, 0xb8, 0xe9, 0xde, 0xb2, 0xa3, 0x40, 0x01, 0xa6, 0x45, 0x1e}} , + {{0x76, 0x0a, 0xda, 0x8d, 0x2c, 0x07, 0x3f, 0x89, 0x7d, 0x04, 0xad, 0x43, 0x50, 0x6e, 0xd2, 0x47, 0xcb, 0x8a, 0xe6, 0x85, 0x1a, 0x24, 0xf3, 0xd2, 0x60, 0xfd, 0xdf, 0x73, 0xa4, 0x0d, 0x73, 0x0e}}}, +{{{0xfd, 0x67, 0x6b, 0x71, 0x9b, 0x81, 0x53, 0x39, 0x39, 0xf4, 0xb8, 0xd5, 0xc3, 0x30, 0x9b, 0x3b, 0x7c, 0xa3, 0xf0, 0xd0, 0x84, 0x21, 0xd6, 0xbf, 0xb7, 0x4c, 0x87, 0x13, 0x45, 0x2d, 0xa7, 0x55}} , + {{0x5d, 0x04, 0xb3, 0x40, 0x28, 0x95, 0x2d, 0x30, 0x83, 0xec, 0x5e, 0xe4, 0xff, 0x75, 0xfe, 0x79, 0x26, 0x9d, 0x1d, 0x36, 0xcd, 0x0a, 0x15, 0xd2, 0x24, 0x14, 0x77, 0x71, 0xd7, 0x8a, 0x1b, 0x04}}}, +{{{0x5d, 0x93, 0xc9, 0xbe, 0xaa, 0x90, 0xcd, 0x9b, 0xfb, 0x73, 0x7e, 0xb0, 0x64, 0x98, 0x57, 0x44, 0x42, 0x41, 0xb1, 0xaf, 0xea, 0xc1, 0xc3, 0x22, 0xff, 0x60, 0x46, 0xcb, 0x61, 0x81, 0x70, 0x61}} , + {{0x0d, 0x82, 0xb9, 0xfe, 0x21, 0xcd, 0xc4, 0xf5, 0x98, 0x0c, 0x4e, 0x72, 0xee, 0x87, 0x49, 0xf8, 0xa1, 0x95, 0xdf, 0x8f, 0x2d, 0xbd, 0x21, 0x06, 0x7c, 0x15, 0xe8, 0x12, 0x6d, 0x93, 0xd6, 0x38}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x91, 0xf7, 0x51, 0xd9, 0xef, 0x7d, 0x42, 0x01, 0x13, 0xe9, 0xb8, 0x7f, 0xa6, 0x49, 0x17, 0x64, 0x21, 0x80, 0x83, 0x2c, 0x63, 0x4c, 0x60, 0x09, 0x59, 0x91, 0x92, 0x77, 0x39, 0x51, 0xf4, 0x48}} , + {{0x60, 0xd5, 0x22, 0x83, 0x08, 0x2f, 0xff, 0x99, 0x3e, 0x69, 0x6d, 0x88, 0xda, 0xe7, 0x5b, 0x52, 0x26, 0x31, 0x2a, 0xe5, 0x89, 0xde, 0x68, 0x90, 0xb6, 0x22, 0x5a, 0xbd, 0xd3, 0x85, 0x53, 0x31}}}, +{{{0xd8, 0xce, 0xdc, 0xf9, 0x3c, 0x4b, 0xa2, 0x1d, 0x2c, 0x2f, 0x36, 0xbe, 0x7a, 0xfc, 0xcd, 0xbc, 0xdc, 0xf9, 0x30, 0xbd, 0xff, 0x05, 0xc7, 0xe4, 0x8e, 0x17, 0x62, 0xf8, 0x4d, 0xa0, 0x56, 0x79}} , + {{0x82, 0xe7, 0xf6, 0xba, 0x53, 0x84, 0x0a, 0xa3, 0x34, 0xff, 0x3c, 0xa3, 0x6a, 0xa1, 0x37, 0xea, 0xdd, 0xb6, 0x95, 0xb3, 0x78, 0x19, 0x76, 0x1e, 0x55, 0x2f, 0x77, 0x2e, 0x7f, 0xc1, 0xea, 0x5e}}}, +{{{0x83, 0xe1, 0x6e, 0xa9, 0x07, 0x33, 0x3e, 0x83, 0xff, 0xcb, 0x1c, 0x9f, 0xb1, 0xa3, 0xb4, 0xc9, 0xe1, 0x07, 0x97, 0xff, 0xf8, 0x23, 0x8f, 0xce, 0x40, 0xfd, 0x2e, 0x5e, 0xdb, 0x16, 0x43, 0x2d}} , + {{0xba, 0x38, 0x02, 0xf7, 0x81, 0x43, 0x83, 0xa3, 0x20, 0x4f, 0x01, 0x3b, 0x8a, 0x04, 0x38, 0x31, 0xc6, 0x0f, 0xc8, 0xdf, 0xd7, 0xfa, 0x2f, 0x88, 0x3f, 0xfc, 0x0c, 0x76, 0xc4, 0xa6, 0x45, 0x72}}}, +{{{0xbb, 0x0c, 0xbc, 0x6a, 0xa4, 0x97, 0x17, 0x93, 0x2d, 0x6f, 0xde, 0x72, 0x10, 0x1c, 0x08, 0x2c, 0x0f, 0x80, 0x32, 0x68, 0x27, 0xd4, 0xab, 0xdd, 0xc5, 0x58, 0x61, 0x13, 0x6d, 0x11, 0x1e, 0x4d}} , + {{0x1a, 0xb9, 0xc9, 0x10, 0xfb, 0x1e, 0x4e, 0xf4, 0x84, 0x4b, 0x8a, 0x5e, 0x7b, 0x4b, 0xe8, 0x43, 0x8c, 0x8f, 0x00, 0xb5, 0x54, 0x13, 0xc5, 0x5c, 0xb6, 0x35, 0x4e, 0x9d, 0xe4, 0x5b, 0x41, 0x6d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x15, 0x7d, 0x12, 0x48, 0x82, 0x14, 0x42, 0xcd, 0x32, 0xd4, 0x4b, 0xc1, 0x72, 0x61, 0x2a, 0x8c, 0xec, 0xe2, 0xf8, 0x24, 0x45, 0x94, 0xe3, 0xbe, 0xdd, 0x67, 0xa8, 0x77, 0x5a, 0xae, 0x5b, 0x4b}} , + {{0xcb, 0x77, 0x9a, 0x20, 0xde, 0xb8, 0x23, 0xd9, 0xa0, 0x0f, 0x8c, 0x7b, 0xa5, 0xcb, 0xae, 0xb6, 0xec, 0x42, 0x67, 0x0e, 0x58, 0xa4, 0x75, 0x98, 0x21, 0x71, 0x84, 0xb3, 0xe0, 0x76, 0x94, 0x73}}}, +{{{0xdf, 0xfc, 0x69, 0x28, 0x23, 0x3f, 0x5b, 0xf8, 0x3b, 0x24, 0x37, 0xf3, 0x1d, 0xd5, 0x22, 0x6b, 0xd0, 0x98, 0xa8, 0x6c, 0xcf, 0xff, 0x06, 0xe1, 0x13, 0xdf, 0xb9, 0xc1, 0x0c, 0xa9, 0xbf, 0x33}} , + {{0xd9, 0x81, 0xda, 0xb2, 0x4f, 0x82, 0x9d, 0x43, 0x81, 0x09, 0xf1, 0xd2, 0x01, 0xef, 0xac, 0xf4, 0x2d, 0x7d, 0x01, 0x09, 0xf1, 0xff, 0xa5, 0x9f, 0xe5, 0xca, 0x27, 0x63, 0xdb, 0x20, 0xb1, 0x53}}}, +{{{0x67, 0x02, 0xe8, 0xad, 0xa9, 0x34, 0xd4, 0xf0, 0x15, 0x81, 0xaa, 0xc7, 0x4d, 0x87, 0x94, 0xea, 0x75, 0xe7, 0x4c, 0x94, 0x04, 0x0e, 0x69, 0x87, 0xe7, 0x51, 0x91, 0x10, 0x03, 0xc7, 0xbe, 0x56}} , + {{0x32, 0xfb, 0x86, 0xec, 0x33, 0x6b, 0x2e, 0x51, 0x2b, 0xc8, 0xfa, 0x6c, 0x70, 0x47, 0x7e, 0xce, 0x05, 0x0c, 0x71, 0xf3, 0xb4, 0x56, 0xa6, 0xdc, 0xcc, 0x78, 0x07, 0x75, 0xd0, 0xdd, 0xb2, 0x6a}}}, +{{{0xc6, 0xef, 0xb9, 0xc0, 0x2b, 0x22, 0x08, 0x1e, 0x71, 0x70, 0xb3, 0x35, 0x9c, 0x7a, 0x01, 0x92, 0x44, 0x9a, 0xf6, 0xb0, 0x58, 0x95, 0xc1, 0x9b, 0x02, 0xed, 0x2d, 0x7c, 0x34, 0x29, 0x49, 0x44}} , + {{0x45, 0x62, 0x1d, 0x2e, 0xff, 0x2a, 0x1c, 0x21, 0xa4, 0x25, 0x7b, 0x0d, 0x8c, 0x15, 0x39, 0xfc, 0x8f, 0x7c, 0xa5, 0x7d, 0x1e, 0x25, 0xa3, 0x45, 0xd6, 0xab, 0xbd, 0xcb, 0xc5, 0x5e, 0x78, 0x77}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xd0, 0xd3, 0x42, 0xed, 0x1d, 0x00, 0x3c, 0x15, 0x2c, 0x9c, 0x77, 0x81, 0xd2, 0x73, 0xd1, 0x06, 0xd5, 0xc4, 0x7f, 0x94, 0xbb, 0x92, 0x2d, 0x2c, 0x4b, 0x45, 0x4b, 0xe9, 0x2a, 0x89, 0x6b, 0x2b}} , + {{0xd2, 0x0c, 0x88, 0xc5, 0x48, 0x4d, 0xea, 0x0d, 0x4a, 0xc9, 0x52, 0x6a, 0x61, 0x79, 0xe9, 0x76, 0xf3, 0x85, 0x52, 0x5c, 0x1b, 0x2c, 0xe1, 0xd6, 0xc4, 0x0f, 0x18, 0x0e, 0x4e, 0xf6, 0x1c, 0x7f}}}, +{{{0xb4, 0x04, 0x2e, 0x42, 0xcb, 0x1f, 0x2b, 0x11, 0x51, 0x7b, 0x08, 0xac, 0xaa, 0x3e, 0x9e, 0x52, 0x60, 0xb7, 0xc2, 0x61, 0x57, 0x8c, 0x84, 0xd5, 0x18, 0xa6, 0x19, 0xfc, 0xb7, 0x75, 0x91, 0x1b}} , + {{0xe8, 0x68, 0xca, 0x44, 0xc8, 0x38, 0x38, 0xcc, 0x53, 0x0a, 0x32, 0x35, 0xcc, 0x52, 0xcb, 0x0e, 0xf7, 0xc5, 0xe7, 0xec, 0x3d, 0x85, 0xcc, 0x58, 0xe2, 0x17, 0x47, 0xff, 0x9f, 0xa5, 0x30, 0x17}}}, +{{{0xe3, 0xae, 0xc8, 0xc1, 0x71, 0x75, 0x31, 0x00, 0x37, 0x41, 0x5c, 0x0e, 0x39, 0xda, 0x73, 0xa0, 0xc7, 0x97, 0x36, 0x6c, 0x5b, 0xf2, 0xee, 0x64, 0x0a, 0x3d, 0x89, 0x1e, 0x1d, 0x49, 0x8c, 0x37}} , + {{0x4c, 0xe6, 0xb0, 0xc1, 0xa5, 0x2a, 0x82, 0x09, 0x08, 0xad, 0x79, 0x9c, 0x56, 0xf6, 0xf9, 0xc1, 0xd7, 0x7c, 0x39, 0x7f, 0x93, 0xca, 0x11, 0x55, 0xbf, 0x07, 0x1b, 0x82, 0x29, 0x69, 0x95, 0x5c}}}, +{{{0x87, 0xee, 0xa6, 0x56, 0x9e, 0xc2, 0x9a, 0x56, 0x24, 0x42, 0x85, 0x4d, 0x98, 0x31, 0x1e, 0x60, 0x4d, 0x87, 0x85, 0x04, 0xae, 0x46, 0x12, 0xf9, 0x8e, 0x7f, 0xe4, 0x7f, 0xf6, 0x1c, 0x37, 0x01}} , + {{0x73, 0x4c, 0xb6, 0xc5, 0xc4, 0xe9, 0x6c, 0x85, 0x48, 0x4a, 0x5a, 0xac, 0xd9, 0x1f, 0x43, 0xf8, 0x62, 0x5b, 0xee, 0x98, 0x2a, 0x33, 0x8e, 0x79, 0xce, 0x61, 0x06, 0x35, 0xd8, 0xd7, 0xca, 0x71}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x72, 0xd3, 0xae, 0xa6, 0xca, 0x8f, 0xcd, 0xcc, 0x78, 0x8e, 0x19, 0x4d, 0xa7, 0xd2, 0x27, 0xe9, 0xa4, 0x3c, 0x16, 0x5b, 0x84, 0x80, 0xf9, 0xd0, 0xcc, 0x6a, 0x1e, 0xca, 0x1e, 0x67, 0xbd, 0x63}} , + {{0x7b, 0x6e, 0x2a, 0xd2, 0x87, 0x48, 0xff, 0xa1, 0xca, 0xe9, 0x15, 0x85, 0xdc, 0xdb, 0x2c, 0x39, 0x12, 0x91, 0xa9, 0x20, 0xaa, 0x4f, 0x29, 0xf4, 0x15, 0x7a, 0xd2, 0xf5, 0x32, 0xcc, 0x60, 0x04}}}, +{{{0xe5, 0x10, 0x47, 0x3b, 0xfa, 0x90, 0xfc, 0x30, 0xb5, 0xea, 0x6f, 0x56, 0x8f, 0xfb, 0x0e, 0xa7, 0x3b, 0xc8, 0xb2, 0xff, 0x02, 0x7a, 0x33, 0x94, 0x93, 0x2a, 0x03, 0xe0, 0x96, 0x3a, 0x6c, 0x0f}} , + {{0x5a, 0x63, 0x67, 0xe1, 0x9b, 0x47, 0x78, 0x9f, 0x38, 0x79, 0xac, 0x97, 0x66, 0x1d, 0x5e, 0x51, 0xee, 0x24, 0x42, 0xe8, 0x58, 0x4b, 0x8a, 0x03, 0x75, 0x86, 0x37, 0x86, 0xe2, 0x97, 0x4e, 0x3d}}}, +{{{0x3f, 0x75, 0x8e, 0xb4, 0xff, 0xd8, 0xdd, 0xd6, 0x37, 0x57, 0x9d, 0x6d, 0x3b, 0xbd, 0xd5, 0x60, 0x88, 0x65, 0x9a, 0xb9, 0x4a, 0x68, 0x84, 0xa2, 0x67, 0xdd, 0x17, 0x25, 0x97, 0x04, 0x8b, 0x5e}} , + {{0xbb, 0x40, 0x5e, 0xbc, 0x16, 0x92, 0x05, 0xc4, 0xc0, 0x4e, 0x72, 0x90, 0x0e, 0xab, 0xcf, 0x8a, 0xed, 0xef, 0xb9, 0x2d, 0x3b, 0xf8, 0x43, 0x5b, 0xba, 0x2d, 0xeb, 0x2f, 0x52, 0xd2, 0xd1, 0x5a}}}, +{{{0x40, 0xb4, 0xab, 0xe6, 0xad, 0x9f, 0x46, 0x69, 0x4a, 0xb3, 0x8e, 0xaa, 0xea, 0x9c, 0x8a, 0x20, 0x16, 0x5d, 0x8c, 0x13, 0xbd, 0xf6, 0x1d, 0xc5, 0x24, 0xbd, 0x90, 0x2a, 0x1c, 0xc7, 0x13, 0x3b}} , + {{0x54, 0xdc, 0x16, 0x0d, 0x18, 0xbe, 0x35, 0x64, 0x61, 0x52, 0x02, 0x80, 0xaf, 0x05, 0xf7, 0xa6, 0x42, 0xd3, 0x8f, 0x2e, 0x79, 0x26, 0xa8, 0xbb, 0xb2, 0x17, 0x48, 0xb2, 0x7a, 0x0a, 0x89, 0x14}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x20, 0xa8, 0x88, 0xe3, 0x91, 0xc0, 0x6e, 0xbb, 0x8a, 0x27, 0x82, 0x51, 0x83, 0xb2, 0x28, 0xa9, 0x83, 0xeb, 0xa6, 0xa9, 0x4d, 0x17, 0x59, 0x22, 0x54, 0x00, 0x50, 0x45, 0xcb, 0x48, 0x4b, 0x18}} , + {{0x33, 0x7c, 0xe7, 0x26, 0xba, 0x4d, 0x32, 0xfe, 0x53, 0xf4, 0xfa, 0x83, 0xe3, 0xa5, 0x79, 0x66, 0x73, 0xef, 0x80, 0x23, 0x68, 0xc2, 0x60, 0xdd, 0xa9, 0x33, 0xdc, 0x03, 0x7a, 0xe0, 0xe0, 0x3e}}}, +{{{0x34, 0x5c, 0x13, 0xfb, 0xc0, 0xe3, 0x78, 0x2b, 0x54, 0x58, 0x22, 0x9b, 0x76, 0x81, 0x7f, 0x93, 0x9c, 0x25, 0x3c, 0xd2, 0xe9, 0x96, 0x21, 0x26, 0x08, 0xf5, 0xed, 0x95, 0x11, 0xae, 0x04, 0x5a}} , + {{0xb9, 0xe8, 0xc5, 0x12, 0x97, 0x1f, 0x83, 0xfe, 0x3e, 0x94, 0x99, 0xd4, 0x2d, 0xf9, 0x52, 0x59, 0x5c, 0x82, 0xa6, 0xf0, 0x75, 0x7e, 0xe8, 0xec, 0xcc, 0xac, 0x18, 0x21, 0x09, 0x67, 0x66, 0x67}}}, +{{{0xb3, 0x40, 0x29, 0xd1, 0xcb, 0x1b, 0x08, 0x9e, 0x9c, 0xb7, 0x53, 0xb9, 0x3b, 0x71, 0x08, 0x95, 0x12, 0x1a, 0x58, 0xaf, 0x7e, 0x82, 0x52, 0x43, 0x4f, 0x11, 0x39, 0xf4, 0x93, 0x1a, 0x26, 0x05}} , + {{0x6e, 0x44, 0xa3, 0xf9, 0x64, 0xaf, 0xe7, 0x6d, 0x7d, 0xdf, 0x1e, 0xac, 0x04, 0xea, 0x3b, 0x5f, 0x9b, 0xe8, 0x24, 0x9d, 0x0e, 0xe5, 0x2e, 0x3e, 0xdf, 0xa9, 0xf7, 0xd4, 0x50, 0x71, 0xf0, 0x78}}}, +{{{0x3e, 0xa8, 0x38, 0xc2, 0x57, 0x56, 0x42, 0x9a, 0xb1, 0xe2, 0xf8, 0x45, 0xaa, 0x11, 0x48, 0x5f, 0x17, 0xc4, 0x54, 0x27, 0xdc, 0x5d, 0xaa, 0xdd, 0x41, 0xbc, 0xdf, 0x81, 0xb9, 0x53, 0xee, 0x52}} , + {{0xc3, 0xf1, 0xa7, 0x6d, 0xb3, 0x5f, 0x92, 0x6f, 0xcc, 0x91, 0xb8, 0x95, 0x05, 0xdf, 0x3c, 0x64, 0x57, 0x39, 0x61, 0x51, 0xad, 0x8c, 0x38, 0x7b, 0xc8, 0xde, 0x00, 0x34, 0xbe, 0xa1, 0xb0, 0x7e}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x25, 0x24, 0x1d, 0x8a, 0x67, 0x20, 0xee, 0x42, 0xeb, 0x38, 0xed, 0x0b, 0x8b, 0xcd, 0x46, 0x9d, 0x5e, 0x6b, 0x1e, 0x24, 0x9d, 0x12, 0x05, 0x1a, 0xcc, 0x05, 0x4e, 0x92, 0x38, 0xe1, 0x1f, 0x50}} , + {{0x4e, 0xee, 0x1c, 0x91, 0xe6, 0x11, 0xbd, 0x8e, 0x55, 0x1a, 0x18, 0x75, 0x66, 0xaf, 0x4d, 0x7b, 0x0f, 0xae, 0x6d, 0x85, 0xca, 0x82, 0x58, 0x21, 0x9c, 0x18, 0xe0, 0xed, 0xec, 0x22, 0x80, 0x2f}}}, +{{{0x68, 0x3b, 0x0a, 0x39, 0x1d, 0x6a, 0x15, 0x57, 0xfc, 0xf0, 0x63, 0x54, 0xdb, 0x39, 0xdb, 0xe8, 0x5c, 0x64, 0xff, 0xa0, 0x09, 0x4f, 0x3b, 0xb7, 0x32, 0x60, 0x99, 0x94, 0xfd, 0x94, 0x82, 0x2d}} , + {{0x24, 0xf6, 0x5a, 0x44, 0xf1, 0x55, 0x2c, 0xdb, 0xea, 0x7c, 0x84, 0x7c, 0x01, 0xac, 0xe3, 0xfd, 0xc9, 0x27, 0xc1, 0x5a, 0xb9, 0xde, 0x4f, 0x5a, 0x90, 0xdd, 0xc6, 0x67, 0xaa, 0x6f, 0x8a, 0x3a}}}, +{{{0x78, 0x52, 0x87, 0xc9, 0x97, 0x63, 0xb1, 0xdd, 0x54, 0x5f, 0xc1, 0xf8, 0xf1, 0x06, 0xa6, 0xa8, 0xa3, 0x88, 0x82, 0xd4, 0xcb, 0xa6, 0x19, 0xdd, 0xd1, 0x11, 0x87, 0x08, 0x17, 0x4c, 0x37, 0x2a}} , + {{0xa1, 0x0c, 0xf3, 0x08, 0x43, 0xd9, 0x24, 0x1e, 0x83, 0xa7, 0xdf, 0x91, 0xca, 0xbd, 0x69, 0x47, 0x8d, 0x1b, 0xe2, 0xb9, 0x4e, 0xb5, 0xe1, 0x76, 0xb3, 0x1c, 0x93, 0x03, 0xce, 0x5f, 0xb3, 0x5a}}}, +{{{0x1d, 0xda, 0xe4, 0x61, 0x03, 0x50, 0xa9, 0x8b, 0x68, 0x18, 0xef, 0xb2, 0x1c, 0x84, 0x3b, 0xa2, 0x44, 0x95, 0xa3, 0x04, 0x3b, 0xd6, 0x99, 0x00, 0xaf, 0x76, 0x42, 0x67, 0x02, 0x7d, 0x85, 0x56}} , + {{0xce, 0x72, 0x0e, 0x29, 0x84, 0xb2, 0x7d, 0xd2, 0x45, 0xbe, 0x57, 0x06, 0xed, 0x7f, 0xcf, 0xed, 0xcd, 0xef, 0x19, 0xd6, 0xbc, 0x15, 0x79, 0x64, 0xd2, 0x18, 0xe3, 0x20, 0x67, 0x3a, 0x54, 0x0b}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x52, 0xfd, 0x04, 0xc5, 0xfb, 0x99, 0xe7, 0xe8, 0xfb, 0x8c, 0xe1, 0x42, 0x03, 0xef, 0x9d, 0xd9, 0x9e, 0x4d, 0xf7, 0x80, 0xcf, 0x2e, 0xcc, 0x9b, 0x45, 0xc9, 0x7b, 0x7a, 0xbc, 0x37, 0xa8, 0x52}} , + {{0x96, 0x11, 0x41, 0x8a, 0x47, 0x91, 0xfe, 0xb6, 0xda, 0x7a, 0x54, 0x63, 0xd1, 0x14, 0x35, 0x05, 0x86, 0x8c, 0xa9, 0x36, 0x3f, 0xf2, 0x85, 0x54, 0x4e, 0x92, 0xd8, 0x85, 0x01, 0x46, 0xd6, 0x50}}}, +{{{0x53, 0xcd, 0xf3, 0x86, 0x40, 0xe6, 0x39, 0x42, 0x95, 0xd6, 0xcb, 0x45, 0x1a, 0x20, 0xc8, 0x45, 0x4b, 0x32, 0x69, 0x04, 0xb1, 0xaf, 0x20, 0x46, 0xc7, 0x6b, 0x23, 0x5b, 0x69, 0xee, 0x30, 0x3f}} , + {{0x70, 0x83, 0x47, 0xc0, 0xdb, 0x55, 0x08, 0xa8, 0x7b, 0x18, 0x6d, 0xf5, 0x04, 0x5a, 0x20, 0x0c, 0x4a, 0x8c, 0x60, 0xae, 0xae, 0x0f, 0x64, 0x55, 0x55, 0x2e, 0xd5, 0x1d, 0x53, 0x31, 0x42, 0x41}}}, +{{{0xca, 0xfc, 0x88, 0x6b, 0x96, 0x78, 0x0a, 0x8b, 0x83, 0xdc, 0xbc, 0xaf, 0x40, 0xb6, 0x8d, 0x7f, 0xef, 0xb4, 0xd1, 0x3f, 0xcc, 0xa2, 0x74, 0xc9, 0xc2, 0x92, 0x55, 0x00, 0xab, 0xdb, 0xbf, 0x4f}} , + {{0x93, 0x1c, 0x06, 0x2d, 0x66, 0x65, 0x02, 0xa4, 0x97, 0x18, 0xfd, 0x00, 0xe7, 0xab, 0x03, 0xec, 0xce, 0xc1, 0xbf, 0x37, 0xf8, 0x13, 0x53, 0xa5, 0xe5, 0x0c, 0x3a, 0xa8, 0x55, 0xb9, 0xff, 0x68}}}, +{{{0xe4, 0xe6, 0x6d, 0x30, 0x7d, 0x30, 0x35, 0xc2, 0x78, 0x87, 0xf9, 0xfc, 0x6b, 0x5a, 0xc3, 0xb7, 0x65, 0xd8, 0x2e, 0xc7, 0xa5, 0x0c, 0xc6, 0xdc, 0x12, 0xaa, 0xd6, 0x4f, 0xc5, 0x38, 0xbc, 0x0e}} , + {{0xe2, 0x3c, 0x76, 0x86, 0x38, 0xf2, 0x7b, 0x2c, 0x16, 0x78, 0x8d, 0xf5, 0xa4, 0x15, 0xda, 0xdb, 0x26, 0x85, 0xa0, 0x56, 0xdd, 0x1d, 0xe3, 0xb3, 0xfd, 0x40, 0xef, 0xf2, 0xd9, 0xa1, 0xb3, 0x04}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xdb, 0x49, 0x0e, 0xe6, 0x58, 0x10, 0x7a, 0x52, 0xda, 0xb5, 0x7d, 0x37, 0x6a, 0x3e, 0xa1, 0x78, 0xce, 0xc7, 0x1c, 0x24, 0x23, 0xdb, 0x7d, 0xfb, 0x8c, 0x8d, 0xdc, 0x30, 0x67, 0x69, 0x75, 0x3b}} , + {{0xa9, 0xea, 0x6d, 0x16, 0x16, 0x60, 0xf4, 0x60, 0x87, 0x19, 0x44, 0x8c, 0x4a, 0x8b, 0x3e, 0xfb, 0x16, 0x00, 0x00, 0x54, 0xa6, 0x9e, 0x9f, 0xef, 0xcf, 0xd9, 0xd2, 0x4c, 0x74, 0x31, 0xd0, 0x34}}}, +{{{0xa4, 0xeb, 0x04, 0xa4, 0x8c, 0x8f, 0x71, 0x27, 0x95, 0x85, 0x5d, 0x55, 0x4b, 0xb1, 0x26, 0x26, 0xc8, 0xae, 0x6a, 0x7d, 0xa2, 0x21, 0xca, 0xce, 0x38, 0xab, 0x0f, 0xd0, 0xd5, 0x2b, 0x6b, 0x00}} , + {{0xe5, 0x67, 0x0c, 0xf1, 0x3a, 0x9a, 0xea, 0x09, 0x39, 0xef, 0xd1, 0x30, 0xbc, 0x33, 0xba, 0xb1, 0x6a, 0xc5, 0x27, 0x08, 0x7f, 0x54, 0x80, 0x3d, 0xab, 0xf6, 0x15, 0x7a, 0xc2, 0x40, 0x73, 0x72}}}, +{{{0x84, 0x56, 0x82, 0xb6, 0x12, 0x70, 0x7f, 0xf7, 0xf0, 0xbd, 0x5b, 0xa9, 0xd5, 0xc5, 0x5f, 0x59, 0xbf, 0x7f, 0xb3, 0x55, 0x22, 0x02, 0xc9, 0x44, 0x55, 0x87, 0x8f, 0x96, 0x98, 0x64, 0x6d, 0x15}} , + {{0xb0, 0x8b, 0xaa, 0x1e, 0xec, 0xc7, 0xa5, 0x8f, 0x1f, 0x92, 0x04, 0xc6, 0x05, 0xf6, 0xdf, 0xa1, 0xcc, 0x1f, 0x81, 0xf5, 0x0e, 0x9c, 0x57, 0xdc, 0xe3, 0xbb, 0x06, 0x87, 0x1e, 0xfe, 0x23, 0x6c}}}, +{{{0xd8, 0x2b, 0x5b, 0x16, 0xea, 0x20, 0xf1, 0xd3, 0x68, 0x8f, 0xae, 0x5b, 0xd0, 0xa9, 0x1a, 0x19, 0xa8, 0x36, 0xfb, 0x2b, 0x57, 0x88, 0x7d, 0x90, 0xd5, 0xa6, 0xf3, 0xdc, 0x38, 0x89, 0x4e, 0x1f}} , + {{0xcc, 0x19, 0xda, 0x9b, 0x3b, 0x43, 0x48, 0x21, 0x2e, 0x23, 0x4d, 0x3d, 0xae, 0xf8, 0x8c, 0xfc, 0xdd, 0xa6, 0x74, 0x37, 0x65, 0xca, 0xee, 0x1a, 0x19, 0x8e, 0x9f, 0x64, 0x6f, 0x0c, 0x8b, 0x5a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x25, 0xb9, 0xc2, 0xf0, 0x72, 0xb8, 0x15, 0x16, 0xcc, 0x8d, 0x3c, 0x6f, 0x25, 0xed, 0xf4, 0x46, 0x2e, 0x0c, 0x60, 0x0f, 0xe2, 0x84, 0x34, 0x55, 0x89, 0x59, 0x34, 0x1b, 0xf5, 0x8d, 0xfe, 0x08}} , + {{0xf8, 0xab, 0x93, 0xbc, 0x44, 0xba, 0x1b, 0x75, 0x4b, 0x49, 0x6f, 0xd0, 0x54, 0x2e, 0x63, 0xba, 0xb5, 0xea, 0xed, 0x32, 0x14, 0xc9, 0x94, 0xd8, 0xc5, 0xce, 0xf4, 0x10, 0x68, 0xe0, 0x38, 0x27}}}, +{{{0x74, 0x1c, 0x14, 0x9b, 0xd4, 0x64, 0x61, 0x71, 0x5a, 0xb6, 0x21, 0x33, 0x4f, 0xf7, 0x8e, 0xba, 0xa5, 0x48, 0x9a, 0xc7, 0xfa, 0x9a, 0xf0, 0xb4, 0x62, 0xad, 0xf2, 0x5e, 0xcc, 0x03, 0x24, 0x1a}} , + {{0xf5, 0x76, 0xfd, 0xe4, 0xaf, 0xb9, 0x03, 0x59, 0xce, 0x63, 0xd2, 0x3b, 0x1f, 0xcd, 0x21, 0x0c, 0xad, 0x44, 0xa5, 0x97, 0xac, 0x80, 0x11, 0x02, 0x9b, 0x0c, 0xe5, 0x8b, 0xcd, 0xfb, 0x79, 0x77}}}, +{{{0x15, 0xbe, 0x9a, 0x0d, 0xba, 0x38, 0x72, 0x20, 0x8a, 0xf5, 0xbe, 0x59, 0x93, 0x79, 0xb7, 0xf6, 0x6a, 0x0c, 0x38, 0x27, 0x1a, 0x60, 0xf4, 0x86, 0x3b, 0xab, 0x5a, 0x00, 0xa0, 0xce, 0x21, 0x7d}} , + {{0x6c, 0xba, 0x14, 0xc5, 0xea, 0x12, 0x9e, 0x2e, 0x82, 0x63, 0xce, 0x9b, 0x4a, 0xe7, 0x1d, 0xec, 0xf1, 0x2e, 0x51, 0x1c, 0xf4, 0xd0, 0x69, 0x15, 0x42, 0x9d, 0xa3, 0x3f, 0x0e, 0xbf, 0xe9, 0x5c}}}, +{{{0xe4, 0x0d, 0xf4, 0xbd, 0xee, 0x31, 0x10, 0xed, 0xcb, 0x12, 0x86, 0xad, 0xd4, 0x2f, 0x90, 0x37, 0x32, 0xc3, 0x0b, 0x73, 0xec, 0x97, 0x85, 0xa4, 0x01, 0x1c, 0x76, 0x35, 0xfe, 0x75, 0xdd, 0x71}} , + {{0x11, 0xa4, 0x88, 0x9f, 0x3e, 0x53, 0x69, 0x3b, 0x1b, 0xe0, 0xf7, 0xba, 0x9b, 0xad, 0x4e, 0x81, 0x5f, 0xb5, 0x5c, 0xae, 0xbe, 0x67, 0x86, 0x37, 0x34, 0x8e, 0x07, 0x32, 0x45, 0x4a, 0x67, 0x39}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x90, 0x70, 0x58, 0x20, 0x03, 0x1e, 0x67, 0xb2, 0xc8, 0x9b, 0x58, 0xc5, 0xb1, 0xeb, 0x2d, 0x4a, 0xde, 0x82, 0x8c, 0xf2, 0xd2, 0x14, 0xb8, 0x70, 0x61, 0x4e, 0x73, 0xd6, 0x0b, 0x6b, 0x0d, 0x30}} , + {{0x81, 0xfc, 0x55, 0x5c, 0xbf, 0xa7, 0xc4, 0xbd, 0xe2, 0xf0, 0x4b, 0x8f, 0xe9, 0x7d, 0x99, 0xfa, 0xd3, 0xab, 0xbc, 0xc7, 0x83, 0x2b, 0x04, 0x7f, 0x0c, 0x19, 0x43, 0x03, 0x3d, 0x07, 0xca, 0x40}}}, +{{{0xf9, 0xc8, 0xbe, 0x8c, 0x16, 0x81, 0x39, 0x96, 0xf6, 0x17, 0x58, 0xc8, 0x30, 0x58, 0xfb, 0xc2, 0x03, 0x45, 0xd2, 0x52, 0x76, 0xe0, 0x6a, 0x26, 0x28, 0x5c, 0x88, 0x59, 0x6a, 0x5a, 0x54, 0x42}} , + {{0x07, 0xb5, 0x2e, 0x2c, 0x67, 0x15, 0x9b, 0xfb, 0x83, 0x69, 0x1e, 0x0f, 0xda, 0xd6, 0x29, 0xb1, 0x60, 0xe0, 0xb2, 0xba, 0x69, 0xa2, 0x9e, 0xbd, 0xbd, 0xe0, 0x1c, 0xbd, 0xcd, 0x06, 0x64, 0x70}}}, +{{{0x41, 0xfa, 0x8c, 0xe1, 0x89, 0x8f, 0x27, 0xc8, 0x25, 0x8f, 0x6f, 0x5f, 0x55, 0xf8, 0xde, 0x95, 0x6d, 0x2f, 0x75, 0x16, 0x2b, 0x4e, 0x44, 0xfd, 0x86, 0x6e, 0xe9, 0x70, 0x39, 0x76, 0x97, 0x7e}} , + {{0x17, 0x62, 0x6b, 0x14, 0xa1, 0x7c, 0xd0, 0x79, 0x6e, 0xd8, 0x8a, 0xa5, 0x6d, 0x8c, 0x93, 0xd2, 0x3f, 0xec, 0x44, 0x8d, 0x6e, 0x91, 0x01, 0x8c, 0x8f, 0xee, 0x01, 0x8f, 0xc0, 0xb4, 0x85, 0x0e}}}, +{{{0x02, 0x3a, 0x70, 0x41, 0xe4, 0x11, 0x57, 0x23, 0xac, 0xe6, 0xfc, 0x54, 0x7e, 0xcd, 0xd7, 0x22, 0xcb, 0x76, 0x9f, 0x20, 0xce, 0xa0, 0x73, 0x76, 0x51, 0x3b, 0xa4, 0xf8, 0xe3, 0x62, 0x12, 0x6c}} , + {{0x7f, 0x00, 0x9c, 0x26, 0x0d, 0x6f, 0x48, 0x7f, 0x3a, 0x01, 0xed, 0xc5, 0x96, 0xb0, 0x1f, 0x4f, 0xa8, 0x02, 0x62, 0x27, 0x8a, 0x50, 0x8d, 0x9a, 0x8b, 0x52, 0x0f, 0x1e, 0xcf, 0x41, 0x38, 0x19}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xf5, 0x6c, 0xd4, 0x2f, 0x0f, 0x69, 0x0f, 0x87, 0x3f, 0x61, 0x65, 0x1e, 0x35, 0x34, 0x85, 0xba, 0x02, 0x30, 0xac, 0x25, 0x3d, 0xe2, 0x62, 0xf1, 0xcc, 0xe9, 0x1b, 0xc2, 0xef, 0x6a, 0x42, 0x57}} , + {{0x34, 0x1f, 0x2e, 0xac, 0xd1, 0xc7, 0x04, 0x52, 0x32, 0x66, 0xb2, 0x33, 0x73, 0x21, 0x34, 0x54, 0xf7, 0x71, 0xed, 0x06, 0xb0, 0xff, 0xa6, 0x59, 0x6f, 0x8a, 0x4e, 0xfb, 0x02, 0xb0, 0x45, 0x6b}}}, +{{{0xf5, 0x48, 0x0b, 0x03, 0xc5, 0x22, 0x7d, 0x80, 0x08, 0x53, 0xfe, 0x32, 0xb1, 0xa1, 0x8a, 0x74, 0x6f, 0xbd, 0x3f, 0x85, 0xf4, 0xcf, 0xf5, 0x60, 0xaf, 0x41, 0x7e, 0x3e, 0x46, 0xa3, 0x5a, 0x20}} , + {{0xaa, 0x35, 0x87, 0x44, 0x63, 0x66, 0x97, 0xf8, 0x6e, 0x55, 0x0c, 0x04, 0x3e, 0x35, 0x50, 0xbf, 0x93, 0x69, 0xd2, 0x8b, 0x05, 0x55, 0x99, 0xbe, 0xe2, 0x53, 0x61, 0xec, 0xe8, 0x08, 0x0b, 0x32}}}, +{{{0xb3, 0x10, 0x45, 0x02, 0x69, 0x59, 0x2e, 0x97, 0xd9, 0x64, 0xf8, 0xdb, 0x25, 0x80, 0xdc, 0xc4, 0xd5, 0x62, 0x3c, 0xed, 0x65, 0x91, 0xad, 0xd1, 0x57, 0x81, 0x94, 0xaa, 0xa1, 0x29, 0xfc, 0x68}} , + {{0xdd, 0xb5, 0x7d, 0xab, 0x5a, 0x21, 0x41, 0x53, 0xbb, 0x17, 0x79, 0x0d, 0xd1, 0xa8, 0x0c, 0x0c, 0x20, 0x88, 0x09, 0xe9, 0x84, 0xe8, 0x25, 0x11, 0x67, 0x7a, 0x8b, 0x1a, 0xe4, 0x5d, 0xe1, 0x5d}}}, +{{{0x37, 0xea, 0xfe, 0x65, 0x3b, 0x25, 0xe8, 0xe1, 0xc2, 0xc5, 0x02, 0xa4, 0xbe, 0x98, 0x0a, 0x2b, 0x61, 0xc1, 0x9b, 0xe2, 0xd5, 0x92, 0xe6, 0x9e, 0x7d, 0x1f, 0xca, 0x43, 0x88, 0x8b, 0x2c, 0x59}} , + {{0xe0, 0xb5, 0x00, 0x1d, 0x2a, 0x6f, 0xaf, 0x79, 0x86, 0x2f, 0xa6, 0x5a, 0x93, 0xd1, 0xfe, 0xae, 0x3a, 0xee, 0xdb, 0x7c, 0x61, 0xbe, 0x7c, 0x01, 0xf9, 0xfe, 0x52, 0xdc, 0xd8, 0x52, 0xa3, 0x42}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x22, 0xaf, 0x13, 0x37, 0xbd, 0x37, 0x71, 0xac, 0x04, 0x46, 0x63, 0xac, 0xa4, 0x77, 0xed, 0x25, 0x38, 0xe0, 0x15, 0xa8, 0x64, 0x00, 0x0d, 0xce, 0x51, 0x01, 0xa9, 0xbc, 0x0f, 0x03, 0x1c, 0x04}} , + {{0x89, 0xf9, 0x80, 0x07, 0xcf, 0x3f, 0xb3, 0xe9, 0xe7, 0x45, 0x44, 0x3d, 0x2a, 0x7c, 0xe9, 0xe4, 0x16, 0x5c, 0x5e, 0x65, 0x1c, 0xc7, 0x7d, 0xc6, 0x7a, 0xfb, 0x43, 0xee, 0x25, 0x76, 0x46, 0x72}}}, +{{{0x02, 0xa2, 0xed, 0xf4, 0x8f, 0x6b, 0x0b, 0x3e, 0xeb, 0x35, 0x1a, 0xd5, 0x7e, 0xdb, 0x78, 0x00, 0x96, 0x8a, 0xa0, 0xb4, 0xcf, 0x60, 0x4b, 0xd4, 0xd5, 0xf9, 0x2d, 0xbf, 0x88, 0xbd, 0x22, 0x62}} , + {{0x13, 0x53, 0xe4, 0x82, 0x57, 0xfa, 0x1e, 0x8f, 0x06, 0x2b, 0x90, 0xba, 0x08, 0xb6, 0x10, 0x54, 0x4f, 0x7c, 0x1b, 0x26, 0xed, 0xda, 0x6b, 0xdd, 0x25, 0xd0, 0x4e, 0xea, 0x42, 0xbb, 0x25, 0x03}}}, +{{{0x51, 0x16, 0x50, 0x7c, 0xd5, 0x5d, 0xf6, 0x99, 0xe8, 0x77, 0x72, 0x4e, 0xfa, 0x62, 0xcb, 0x76, 0x75, 0x0c, 0xe2, 0x71, 0x98, 0x92, 0xd5, 0xfa, 0x45, 0xdf, 0x5c, 0x6f, 0x1e, 0x9e, 0x28, 0x69}} , + {{0x0d, 0xac, 0x66, 0x6d, 0xc3, 0x8b, 0xba, 0x16, 0xb5, 0xe2, 0xa0, 0x0d, 0x0c, 0xbd, 0xa4, 0x8e, 0x18, 0x6c, 0xf2, 0xdc, 0xf9, 0xdc, 0x4a, 0x86, 0x25, 0x95, 0x14, 0xcb, 0xd8, 0x1a, 0x04, 0x0f}}}, +{{{0x97, 0xa5, 0xdb, 0x8b, 0x2d, 0xaa, 0x42, 0x11, 0x09, 0xf2, 0x93, 0xbb, 0xd9, 0x06, 0x84, 0x4e, 0x11, 0xa8, 0xa0, 0x25, 0x2b, 0xa6, 0x5f, 0xae, 0xc4, 0xb4, 0x4c, 0xc8, 0xab, 0xc7, 0x3b, 0x02}} , + {{0xee, 0xc9, 0x29, 0x0f, 0xdf, 0x11, 0x85, 0xed, 0xce, 0x0d, 0x62, 0x2c, 0x8f, 0x4b, 0xf9, 0x04, 0xe9, 0x06, 0x72, 0x1d, 0x37, 0x20, 0x50, 0xc9, 0x14, 0xeb, 0xec, 0x39, 0xa7, 0x97, 0x2b, 0x4d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x69, 0xd1, 0x39, 0xbd, 0xfb, 0x33, 0xbe, 0xc4, 0xf0, 0x5c, 0xef, 0xf0, 0x56, 0x68, 0xfc, 0x97, 0x47, 0xc8, 0x72, 0xb6, 0x53, 0xa4, 0x0a, 0x98, 0xa5, 0xb4, 0x37, 0x71, 0xcf, 0x66, 0x50, 0x6d}} , + {{0x17, 0xa4, 0x19, 0x52, 0x11, 0x47, 0xb3, 0x5c, 0x5b, 0xa9, 0x2e, 0x22, 0xb4, 0x00, 0x52, 0xf9, 0x57, 0x18, 0xb8, 0xbe, 0x5a, 0xe3, 0xab, 0x83, 0xc8, 0x87, 0x0a, 0x2a, 0xd8, 0x8c, 0xbb, 0x54}}}, +{{{0xa9, 0x62, 0x93, 0x85, 0xbe, 0xe8, 0x73, 0x4a, 0x0e, 0xb0, 0xb5, 0x2d, 0x94, 0x50, 0xaa, 0xd3, 0xb2, 0xea, 0x9d, 0x62, 0x76, 0x3b, 0x07, 0x34, 0x4e, 0x2d, 0x70, 0xc8, 0x9a, 0x15, 0x66, 0x6b}} , + {{0xc5, 0x96, 0xca, 0xc8, 0x22, 0x1a, 0xee, 0x5f, 0xe7, 0x31, 0x60, 0x22, 0x83, 0x08, 0x63, 0xce, 0xb9, 0x32, 0x44, 0x58, 0x5d, 0x3a, 0x9b, 0xe4, 0x04, 0xd5, 0xef, 0x38, 0xef, 0x4b, 0xdd, 0x19}}}, +{{{0x4d, 0xc2, 0x17, 0x75, 0xa1, 0x68, 0xcd, 0xc3, 0xc6, 0x03, 0x44, 0xe3, 0x78, 0x09, 0x91, 0x47, 0x3f, 0x0f, 0xe4, 0x92, 0x58, 0xfa, 0x7d, 0x1f, 0x20, 0x94, 0x58, 0x5e, 0xbc, 0x19, 0x02, 0x6f}} , + {{0x20, 0xd6, 0xd8, 0x91, 0x54, 0xa7, 0xf3, 0x20, 0x4b, 0x34, 0x06, 0xfa, 0x30, 0xc8, 0x6f, 0x14, 0x10, 0x65, 0x74, 0x13, 0x4e, 0xf0, 0x69, 0x26, 0xce, 0xcf, 0x90, 0xf4, 0xd0, 0xc5, 0xc8, 0x64}}}, +{{{0x26, 0xa2, 0x50, 0x02, 0x24, 0x72, 0xf1, 0xf0, 0x4e, 0x2d, 0x93, 0xd5, 0x08, 0xe7, 0xae, 0x38, 0xf7, 0x18, 0xa5, 0x32, 0x34, 0xc2, 0xf0, 0xa6, 0xec, 0xb9, 0x61, 0x7b, 0x64, 0x99, 0xac, 0x71}} , + {{0x25, 0xcf, 0x74, 0x55, 0x1b, 0xaa, 0xa9, 0x38, 0x41, 0x40, 0xd5, 0x95, 0x95, 0xab, 0x1c, 0x5e, 0xbc, 0x41, 0x7e, 0x14, 0x30, 0xbe, 0x13, 0x89, 0xf4, 0xe5, 0xeb, 0x28, 0xc0, 0xc2, 0x96, 0x3a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x2b, 0x77, 0x45, 0xec, 0x67, 0x76, 0x32, 0x4c, 0xb9, 0xdf, 0x25, 0x32, 0x6b, 0xcb, 0xe7, 0x14, 0x61, 0x43, 0xee, 0xba, 0x9b, 0x71, 0xef, 0xd2, 0x48, 0x65, 0xbb, 0x1b, 0x8a, 0x13, 0x1b, 0x22}} , + {{0x84, 0xad, 0x0c, 0x18, 0x38, 0x5a, 0xba, 0xd0, 0x98, 0x59, 0xbf, 0x37, 0xb0, 0x4f, 0x97, 0x60, 0x20, 0xb3, 0x9b, 0x97, 0xf6, 0x08, 0x6c, 0xa4, 0xff, 0xfb, 0xb7, 0xfa, 0x95, 0xb2, 0x51, 0x79}}}, +{{{0x28, 0x5c, 0x3f, 0xdb, 0x6b, 0x18, 0x3b, 0x5c, 0xd1, 0x04, 0x28, 0xde, 0x85, 0x52, 0x31, 0xb5, 0xbb, 0xf6, 0xa9, 0xed, 0xbe, 0x28, 0x4f, 0xb3, 0x7e, 0x05, 0x6a, 0xdb, 0x95, 0x0d, 0x1b, 0x1c}} , + {{0xd5, 0xc5, 0xc3, 0x9a, 0x0a, 0xd0, 0x31, 0x3e, 0x07, 0x36, 0x8e, 0xc0, 0x8a, 0x62, 0xb1, 0xca, 0xd6, 0x0e, 0x1e, 0x9d, 0xef, 0xab, 0x98, 0x4d, 0xbb, 0x6c, 0x05, 0xe0, 0xe4, 0x5d, 0xbd, 0x57}}}, +{{{0xcc, 0x21, 0x27, 0xce, 0xfd, 0xa9, 0x94, 0x8e, 0xe1, 0xab, 0x49, 0xe0, 0x46, 0x26, 0xa1, 0xa8, 0x8c, 0xa1, 0x99, 0x1d, 0xb4, 0x27, 0x6d, 0x2d, 0xc8, 0x39, 0x30, 0x5e, 0x37, 0x52, 0xc4, 0x6e}} , + {{0xa9, 0x85, 0xf4, 0xe7, 0xb0, 0x15, 0x33, 0x84, 0x1b, 0x14, 0x1a, 0x02, 0xd9, 0x3b, 0xad, 0x0f, 0x43, 0x6c, 0xea, 0x3e, 0x0f, 0x7e, 0xda, 0xdd, 0x6b, 0x4c, 0x7f, 0x6e, 0xd4, 0x6b, 0xbf, 0x0f}}}, +{{{0x47, 0x9f, 0x7c, 0x56, 0x7c, 0x43, 0x91, 0x1c, 0xbb, 0x4e, 0x72, 0x3e, 0x64, 0xab, 0xa0, 0xa0, 0xdf, 0xb4, 0xd8, 0x87, 0x3a, 0xbd, 0xa8, 0x48, 0xc9, 0xb8, 0xef, 0x2e, 0xad, 0x6f, 0x84, 0x4f}} , + {{0x2d, 0x2d, 0xf0, 0x1b, 0x7e, 0x2a, 0x6c, 0xf8, 0xa9, 0x6a, 0xe1, 0xf0, 0x99, 0xa1, 0x67, 0x9a, 0xd4, 0x13, 0xca, 0xca, 0xba, 0x27, 0x92, 0xaa, 0xa1, 0x5d, 0x50, 0xde, 0xcc, 0x40, 0x26, 0x0a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x9f, 0x3e, 0xf2, 0xb2, 0x90, 0xce, 0xdb, 0x64, 0x3e, 0x03, 0xdd, 0x37, 0x36, 0x54, 0x70, 0x76, 0x24, 0xb5, 0x69, 0x03, 0xfc, 0xa0, 0x2b, 0x74, 0xb2, 0x05, 0x0e, 0xcc, 0xd8, 0x1f, 0x6a, 0x1f}} , + {{0x19, 0x5e, 0x60, 0x69, 0x58, 0x86, 0xa0, 0x31, 0xbd, 0x32, 0xe9, 0x2c, 0x5c, 0xd2, 0x85, 0xba, 0x40, 0x64, 0xa8, 0x74, 0xf8, 0x0e, 0x1c, 0xb3, 0xa9, 0x69, 0xe8, 0x1e, 0x40, 0x64, 0x99, 0x77}}}, +{{{0x6c, 0x32, 0x4f, 0xfd, 0xbb, 0x5c, 0xbb, 0x8d, 0x64, 0x66, 0x4a, 0x71, 0x1f, 0x79, 0xa3, 0xad, 0x8d, 0xf9, 0xd4, 0xec, 0xcf, 0x67, 0x70, 0xfa, 0x05, 0x4a, 0x0f, 0x6e, 0xaf, 0x87, 0x0a, 0x6f}} , + {{0xc6, 0x36, 0x6e, 0x6c, 0x8c, 0x24, 0x09, 0x60, 0xbe, 0x26, 0xd2, 0x4c, 0x5e, 0x17, 0xca, 0x5f, 0x1d, 0xcc, 0x87, 0xe8, 0x42, 0x6a, 0xcb, 0xcb, 0x7d, 0x92, 0x05, 0x35, 0x81, 0x13, 0x60, 0x6b}}}, +{{{0xf4, 0x15, 0xcd, 0x0f, 0x0a, 0xaf, 0x4e, 0x6b, 0x51, 0xfd, 0x14, 0xc4, 0x2e, 0x13, 0x86, 0x74, 0x44, 0xcb, 0x66, 0x6b, 0xb6, 0x9d, 0x74, 0x56, 0x32, 0xac, 0x8d, 0x8e, 0x8c, 0x8c, 0x8c, 0x39}} , + {{0xca, 0x59, 0x74, 0x1a, 0x11, 0xef, 0x6d, 0xf7, 0x39, 0x5c, 0x3b, 0x1f, 0xfa, 0xe3, 0x40, 0x41, 0x23, 0x9e, 0xf6, 0xd1, 0x21, 0xa2, 0xbf, 0xad, 0x65, 0x42, 0x6b, 0x59, 0x8a, 0xe8, 0xc5, 0x7f}}}, +{{{0x64, 0x05, 0x7a, 0x84, 0x4a, 0x13, 0xc3, 0xf6, 0xb0, 0x6e, 0x9a, 0x6b, 0x53, 0x6b, 0x32, 0xda, 0xd9, 0x74, 0x75, 0xc4, 0xba, 0x64, 0x3d, 0x3b, 0x08, 0xdd, 0x10, 0x46, 0xef, 0xc7, 0x90, 0x1f}} , + {{0x7b, 0x2f, 0x3a, 0xce, 0xc8, 0xa1, 0x79, 0x3c, 0x30, 0x12, 0x44, 0x28, 0xf6, 0xbc, 0xff, 0xfd, 0xf4, 0xc0, 0x97, 0xb0, 0xcc, 0xc3, 0x13, 0x7a, 0xb9, 0x9a, 0x16, 0xe4, 0xcb, 0x4c, 0x34, 0x63}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x07, 0x4e, 0xd3, 0x2d, 0x09, 0x33, 0x0e, 0xd2, 0x0d, 0xbe, 0x3e, 0xe7, 0xe4, 0xaa, 0xb7, 0x00, 0x8b, 0xe8, 0xad, 0xaa, 0x7a, 0x8d, 0x34, 0x28, 0xa9, 0x81, 0x94, 0xc5, 0xe7, 0x42, 0xac, 0x47}} , + {{0x24, 0x89, 0x7a, 0x8f, 0xb5, 0x9b, 0xf0, 0xc2, 0x03, 0x64, 0xd0, 0x1e, 0xf5, 0xa4, 0xb2, 0xf3, 0x74, 0xe9, 0x1a, 0x16, 0xfd, 0xcb, 0x15, 0xea, 0xeb, 0x10, 0x6c, 0x35, 0xd1, 0xc1, 0xa6, 0x28}}}, +{{{0xcc, 0xd5, 0x39, 0xfc, 0xa5, 0xa4, 0xad, 0x32, 0x15, 0xce, 0x19, 0xe8, 0x34, 0x2b, 0x1c, 0x60, 0x91, 0xfc, 0x05, 0xa9, 0xb3, 0xdc, 0x80, 0x29, 0xc4, 0x20, 0x79, 0x06, 0x39, 0xc0, 0xe2, 0x22}} , + {{0xbb, 0xa8, 0xe1, 0x89, 0x70, 0x57, 0x18, 0x54, 0x3c, 0xf6, 0x0d, 0x82, 0x12, 0x05, 0x87, 0x96, 0x06, 0x39, 0xe3, 0xf8, 0xb3, 0x95, 0xe5, 0xd7, 0x26, 0xbf, 0x09, 0x5a, 0x94, 0xf9, 0x1c, 0x63}}}, +{{{0x2b, 0x8c, 0x2d, 0x9a, 0x8b, 0x84, 0xf2, 0x56, 0xfb, 0xad, 0x2e, 0x7f, 0xb7, 0xfc, 0x30, 0xe1, 0x35, 0x89, 0xba, 0x4d, 0xa8, 0x6d, 0xce, 0x8c, 0x8b, 0x30, 0xe0, 0xda, 0x29, 0x18, 0x11, 0x17}} , + {{0x19, 0xa6, 0x5a, 0x65, 0x93, 0xc3, 0xb5, 0x31, 0x22, 0x4f, 0xf3, 0xf6, 0x0f, 0xeb, 0x28, 0xc3, 0x7c, 0xeb, 0xce, 0x86, 0xec, 0x67, 0x76, 0x6e, 0x35, 0x45, 0x7b, 0xd8, 0x6b, 0x92, 0x01, 0x65}}}, +{{{0x3d, 0xd5, 0x9a, 0x64, 0x73, 0x36, 0xb1, 0xd6, 0x86, 0x98, 0x42, 0x3f, 0x8a, 0xf1, 0xc7, 0xf5, 0x42, 0xa8, 0x9c, 0x52, 0xa8, 0xdc, 0xf9, 0x24, 0x3f, 0x4a, 0xa1, 0xa4, 0x5b, 0xe8, 0x62, 0x1a}} , + {{0xc5, 0xbd, 0xc8, 0x14, 0xd5, 0x0d, 0xeb, 0xe1, 0xa5, 0xe6, 0x83, 0x11, 0x09, 0x00, 0x1d, 0x55, 0x83, 0x51, 0x7e, 0x75, 0x00, 0x81, 0xb9, 0xcb, 0xd8, 0xc5, 0xe5, 0xa1, 0xd9, 0x17, 0x6d, 0x1f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xea, 0xf9, 0xe4, 0xe9, 0xe1, 0x52, 0x3f, 0x51, 0x19, 0x0d, 0xdd, 0xd9, 0x9d, 0x93, 0x31, 0x87, 0x23, 0x09, 0xd5, 0x83, 0xeb, 0x92, 0x09, 0x76, 0x6e, 0xe3, 0xf8, 0xc0, 0xa2, 0x66, 0xb5, 0x36}} , + {{0x3a, 0xbb, 0x39, 0xed, 0x32, 0x02, 0xe7, 0x43, 0x7a, 0x38, 0x14, 0x84, 0xe3, 0x44, 0xd2, 0x5e, 0x94, 0xdd, 0x78, 0x89, 0x55, 0x4c, 0x73, 0x9e, 0xe1, 0xe4, 0x3e, 0x43, 0xd0, 0x4a, 0xde, 0x1b}}}, +{{{0xb2, 0xe7, 0x8f, 0xe3, 0xa3, 0xc5, 0xcb, 0x72, 0xee, 0x79, 0x41, 0xf8, 0xdf, 0xee, 0x65, 0xc5, 0x45, 0x77, 0x27, 0x3c, 0xbd, 0x58, 0xd3, 0x75, 0xe2, 0x04, 0x4b, 0xbb, 0x65, 0xf3, 0xc8, 0x0f}} , + {{0x24, 0x7b, 0x93, 0x34, 0xb5, 0xe2, 0x74, 0x48, 0xcd, 0xa0, 0x0b, 0x92, 0x97, 0x66, 0x39, 0xf4, 0xb0, 0xe2, 0x5d, 0x39, 0x6a, 0x5b, 0x45, 0x17, 0x78, 0x1e, 0xdb, 0x91, 0x81, 0x1c, 0xf9, 0x16}}}, +{{{0x16, 0xdf, 0xd1, 0x5a, 0xd5, 0xe9, 0x4e, 0x58, 0x95, 0x93, 0x5f, 0x51, 0x09, 0xc3, 0x2a, 0xc9, 0xd4, 0x55, 0x48, 0x79, 0xa4, 0xa3, 0xb2, 0xc3, 0x62, 0xaa, 0x8c, 0xe8, 0xad, 0x47, 0x39, 0x1b}} , + {{0x46, 0xda, 0x9e, 0x51, 0x3a, 0xe6, 0xd1, 0xa6, 0xbb, 0x4d, 0x7b, 0x08, 0xbe, 0x8c, 0xd5, 0xf3, 0x3f, 0xfd, 0xf7, 0x44, 0x80, 0x2d, 0x53, 0x4b, 0xd0, 0x87, 0x68, 0xc1, 0xb5, 0xd8, 0xf7, 0x07}}}, +{{{0xf4, 0x10, 0x46, 0xbe, 0xb7, 0xd2, 0xd1, 0xce, 0x5e, 0x76, 0xa2, 0xd7, 0x03, 0xdc, 0xe4, 0x81, 0x5a, 0xf6, 0x3c, 0xde, 0xae, 0x7a, 0x9d, 0x21, 0x34, 0xa5, 0xf6, 0xa9, 0x73, 0xe2, 0x8d, 0x60}} , + {{0xfa, 0x44, 0x71, 0xf6, 0x41, 0xd8, 0xc6, 0x58, 0x13, 0x37, 0xeb, 0x84, 0x0f, 0x96, 0xc7, 0xdc, 0xc8, 0xa9, 0x7a, 0x83, 0xb2, 0x2f, 0x31, 0xb1, 0x1a, 0xd8, 0x98, 0x3f, 0x11, 0xd0, 0x31, 0x3b}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x81, 0xd5, 0x34, 0x16, 0x01, 0xa3, 0x93, 0xea, 0x52, 0x94, 0xec, 0x93, 0xb7, 0x81, 0x11, 0x2d, 0x58, 0xf9, 0xb5, 0x0a, 0xaa, 0x4f, 0xf6, 0x2e, 0x3f, 0x36, 0xbf, 0x33, 0x5a, 0xe7, 0xd1, 0x08}} , + {{0x1a, 0xcf, 0x42, 0xae, 0xcc, 0xb5, 0x77, 0x39, 0xc4, 0x5b, 0x5b, 0xd0, 0x26, 0x59, 0x27, 0xd0, 0x55, 0x71, 0x12, 0x9d, 0x88, 0x3d, 0x9c, 0xea, 0x41, 0x6a, 0xf0, 0x50, 0x93, 0x93, 0xdd, 0x47}}}, +{{{0x6f, 0xc9, 0x51, 0x6d, 0x1c, 0xaa, 0xf5, 0xa5, 0x90, 0x3f, 0x14, 0xe2, 0x6e, 0x8e, 0x64, 0xfd, 0xac, 0xe0, 0x4e, 0x22, 0xe5, 0xc1, 0xbc, 0x29, 0x0a, 0x6a, 0x9e, 0xa1, 0x60, 0xcb, 0x2f, 0x0b}} , + {{0xdc, 0x39, 0x32, 0xf3, 0xa1, 0x44, 0xe9, 0xc5, 0xc3, 0x78, 0xfb, 0x95, 0x47, 0x34, 0x35, 0x34, 0xe8, 0x25, 0xde, 0x93, 0xc6, 0xb4, 0x76, 0x6d, 0x86, 0x13, 0xc6, 0xe9, 0x68, 0xb5, 0x01, 0x63}}}, +{{{0x1f, 0x9a, 0x52, 0x64, 0x97, 0xd9, 0x1c, 0x08, 0x51, 0x6f, 0x26, 0x9d, 0xaa, 0x93, 0x33, 0x43, 0xfa, 0x77, 0xe9, 0x62, 0x9b, 0x5d, 0x18, 0x75, 0xeb, 0x78, 0xf7, 0x87, 0x8f, 0x41, 0xb4, 0x4d}} , + {{0x13, 0xa8, 0x82, 0x3e, 0xe9, 0x13, 0xad, 0xeb, 0x01, 0xca, 0xcf, 0xda, 0xcd, 0xf7, 0x6c, 0xc7, 0x7a, 0xdc, 0x1e, 0x6e, 0xc8, 0x4e, 0x55, 0x62, 0x80, 0xea, 0x78, 0x0c, 0x86, 0xb9, 0x40, 0x51}}}, +{{{0x27, 0xae, 0xd3, 0x0d, 0x4c, 0x8f, 0x34, 0xea, 0x7d, 0x3c, 0xe5, 0x8a, 0xcf, 0x5b, 0x92, 0xd8, 0x30, 0x16, 0xb4, 0xa3, 0x75, 0xff, 0xeb, 0x27, 0xc8, 0x5c, 0x6c, 0xc2, 0xee, 0x6c, 0x21, 0x0b}} , + {{0xc3, 0xba, 0x12, 0x53, 0x2a, 0xaa, 0x77, 0xad, 0x19, 0x78, 0x55, 0x8a, 0x2e, 0x60, 0x87, 0xc2, 0x6e, 0x91, 0x38, 0x91, 0x3f, 0x7a, 0xc5, 0x24, 0x8f, 0x51, 0xc5, 0xde, 0xb0, 0x53, 0x30, 0x56}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x02, 0xfe, 0x54, 0x12, 0x18, 0xca, 0x7d, 0xa5, 0x68, 0x43, 0xa3, 0x6d, 0x14, 0x2a, 0x6a, 0xa5, 0x8e, 0x32, 0xe7, 0x63, 0x4f, 0xe3, 0xc6, 0x44, 0x3e, 0xab, 0x63, 0xca, 0x17, 0x86, 0x74, 0x3f}} , + {{0x1e, 0x64, 0xc1, 0x7d, 0x52, 0xdc, 0x13, 0x5a, 0xa1, 0x9c, 0x4e, 0xee, 0x99, 0x28, 0xbb, 0x4c, 0xee, 0xac, 0xa9, 0x1b, 0x89, 0xa2, 0x38, 0x39, 0x7b, 0xc4, 0x0f, 0x42, 0xe6, 0x89, 0xed, 0x0f}}}, +{{{0xf3, 0x3c, 0x8c, 0x80, 0x83, 0x10, 0x8a, 0x37, 0x50, 0x9c, 0xb4, 0xdf, 0x3f, 0x8c, 0xf7, 0x23, 0x07, 0xd6, 0xff, 0xa0, 0x82, 0x6c, 0x75, 0x3b, 0xe4, 0xb5, 0xbb, 0xe4, 0xe6, 0x50, 0xf0, 0x08}} , + {{0x62, 0xee, 0x75, 0x48, 0x92, 0x33, 0xf2, 0xf4, 0xad, 0x15, 0x7a, 0xa1, 0x01, 0x46, 0xa9, 0x32, 0x06, 0x88, 0xb6, 0x36, 0x47, 0x35, 0xb9, 0xb4, 0x42, 0x85, 0x76, 0xf0, 0x48, 0x00, 0x90, 0x38}}}, +{{{0x51, 0x15, 0x9d, 0xc3, 0x95, 0xd1, 0x39, 0xbb, 0x64, 0x9d, 0x15, 0x81, 0xc1, 0x68, 0xd0, 0xb6, 0xa4, 0x2c, 0x7d, 0x5e, 0x02, 0x39, 0x00, 0xe0, 0x3b, 0xa4, 0xcc, 0xca, 0x1d, 0x81, 0x24, 0x10}} , + {{0xe7, 0x29, 0xf9, 0x37, 0xd9, 0x46, 0x5a, 0xcd, 0x70, 0xfe, 0x4d, 0x5b, 0xbf, 0xa5, 0xcf, 0x91, 0xf4, 0xef, 0xee, 0x8a, 0x29, 0xd0, 0xe7, 0xc4, 0x25, 0x92, 0x8a, 0xff, 0x36, 0xfc, 0xe4, 0x49}}}, +{{{0xbd, 0x00, 0xb9, 0x04, 0x7d, 0x35, 0xfc, 0xeb, 0xd0, 0x0b, 0x05, 0x32, 0x52, 0x7a, 0x89, 0x24, 0x75, 0x50, 0xe1, 0x63, 0x02, 0x82, 0x8e, 0xe7, 0x85, 0x0c, 0xf2, 0x56, 0x44, 0x37, 0x83, 0x25}} , + {{0x8f, 0xa1, 0xce, 0xcb, 0x60, 0xda, 0x12, 0x02, 0x1e, 0x29, 0x39, 0x2a, 0x03, 0xb7, 0xeb, 0x77, 0x40, 0xea, 0xc9, 0x2b, 0x2c, 0xd5, 0x7d, 0x7e, 0x2c, 0xc7, 0x5a, 0xfd, 0xff, 0xc4, 0xd1, 0x62}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x1d, 0x88, 0x98, 0x5b, 0x4e, 0xfc, 0x41, 0x24, 0x05, 0xe6, 0x50, 0x2b, 0xae, 0x96, 0x51, 0xd9, 0x6b, 0x72, 0xb2, 0x33, 0x42, 0x98, 0x68, 0xbb, 0x10, 0x5a, 0x7a, 0x8c, 0x9d, 0x07, 0xb4, 0x05}} , + {{0x2f, 0x61, 0x9f, 0xd7, 0xa8, 0x3f, 0x83, 0x8c, 0x10, 0x69, 0x90, 0xe6, 0xcf, 0xd2, 0x63, 0xa3, 0xe4, 0x54, 0x7e, 0xe5, 0x69, 0x13, 0x1c, 0x90, 0x57, 0xaa, 0xe9, 0x53, 0x22, 0x43, 0x29, 0x23}}}, +{{{0xe5, 0x1c, 0xf8, 0x0a, 0xfd, 0x2d, 0x7e, 0xf5, 0xf5, 0x70, 0x7d, 0x41, 0x6b, 0x11, 0xfe, 0xbe, 0x99, 0xd1, 0x55, 0x29, 0x31, 0xbf, 0xc0, 0x97, 0x6c, 0xd5, 0x35, 0xcc, 0x5e, 0x8b, 0xd9, 0x69}} , + {{0x8e, 0x4e, 0x9f, 0x25, 0xf8, 0x81, 0x54, 0x2d, 0x0e, 0xd5, 0x54, 0x81, 0x9b, 0xa6, 0x92, 0xce, 0x4b, 0xe9, 0x8f, 0x24, 0x3b, 0xca, 0xe0, 0x44, 0xab, 0x36, 0xfe, 0xfb, 0x87, 0xd4, 0x26, 0x3e}}}, +{{{0x0f, 0x93, 0x9c, 0x11, 0xe7, 0xdb, 0xf1, 0xf0, 0x85, 0x43, 0x28, 0x15, 0x37, 0xdd, 0xde, 0x27, 0xdf, 0xad, 0x3e, 0x49, 0x4f, 0xe0, 0x5b, 0xf6, 0x80, 0x59, 0x15, 0x3c, 0x85, 0xb7, 0x3e, 0x12}} , + {{0xf5, 0xff, 0xcc, 0xf0, 0xb4, 0x12, 0x03, 0x5f, 0xc9, 0x84, 0xcb, 0x1d, 0x17, 0xe0, 0xbc, 0xcc, 0x03, 0x62, 0xa9, 0x8b, 0x94, 0xa6, 0xaa, 0x18, 0xcb, 0x27, 0x8d, 0x49, 0xa6, 0x17, 0x15, 0x07}}}, +{{{0xd9, 0xb6, 0xd4, 0x9d, 0xd4, 0x6a, 0xaf, 0x70, 0x07, 0x2c, 0x10, 0x9e, 0xbd, 0x11, 0xad, 0xe4, 0x26, 0x33, 0x70, 0x92, 0x78, 0x1c, 0x74, 0x9f, 0x75, 0x60, 0x56, 0xf4, 0x39, 0xa8, 0xa8, 0x62}} , + {{0x3b, 0xbf, 0x55, 0x35, 0x61, 0x8b, 0x44, 0x97, 0xe8, 0x3a, 0x55, 0xc1, 0xc8, 0x3b, 0xfd, 0x95, 0x29, 0x11, 0x60, 0x96, 0x1e, 0xcb, 0x11, 0x9d, 0xc2, 0x03, 0x8a, 0x1b, 0xc6, 0xd6, 0x45, 0x3d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x7e, 0x0e, 0x50, 0xb2, 0xcc, 0x0d, 0x6b, 0xa6, 0x71, 0x5b, 0x42, 0xed, 0xbd, 0xaf, 0xac, 0xf0, 0xfc, 0x12, 0xa2, 0x3f, 0x4e, 0xda, 0xe8, 0x11, 0xf3, 0x23, 0xe1, 0x04, 0x62, 0x03, 0x1c, 0x4e}} , + {{0xc8, 0xb1, 0x1b, 0x6f, 0x73, 0x61, 0x3d, 0x27, 0x0d, 0x7d, 0x7a, 0x25, 0x5f, 0x73, 0x0e, 0x2f, 0x93, 0xf6, 0x24, 0xd8, 0x4f, 0x90, 0xac, 0xa2, 0x62, 0x0a, 0xf0, 0x61, 0xd9, 0x08, 0x59, 0x6a}}}, +{{{0x6f, 0x2d, 0x55, 0xf8, 0x2f, 0x8e, 0xf0, 0x18, 0x3b, 0xea, 0xdd, 0x26, 0x72, 0xd1, 0xf5, 0xfe, 0xe5, 0xb8, 0xe6, 0xd3, 0x10, 0x48, 0x46, 0x49, 0x3a, 0x9f, 0x5e, 0x45, 0x6b, 0x90, 0xe8, 0x7f}} , + {{0xd3, 0x76, 0x69, 0x33, 0x7b, 0xb9, 0x40, 0x70, 0xee, 0xa6, 0x29, 0x6b, 0xdd, 0xd0, 0x5d, 0x8d, 0xc1, 0x3e, 0x4a, 0xea, 0x37, 0xb1, 0x03, 0x02, 0x03, 0x35, 0xf1, 0x28, 0x9d, 0xff, 0x00, 0x13}}}, +{{{0x7a, 0xdb, 0x12, 0xd2, 0x8a, 0x82, 0x03, 0x1b, 0x1e, 0xaf, 0xf9, 0x4b, 0x9c, 0xbe, 0xae, 0x7c, 0xe4, 0x94, 0x2a, 0x23, 0xb3, 0x62, 0x86, 0xe7, 0xfd, 0x23, 0xaa, 0x99, 0xbd, 0x2b, 0x11, 0x6c}} , + {{0x8d, 0xa6, 0xd5, 0xac, 0x9d, 0xcc, 0x68, 0x75, 0x7f, 0xc3, 0x4d, 0x4b, 0xdd, 0x6c, 0xbb, 0x11, 0x5a, 0x60, 0xe5, 0xbd, 0x7d, 0x27, 0x8b, 0xda, 0xb4, 0x95, 0xf6, 0x03, 0x27, 0xa4, 0x92, 0x3f}}}, +{{{0x22, 0xd6, 0xb5, 0x17, 0x84, 0xbf, 0x12, 0xcc, 0x23, 0x14, 0x4a, 0xdf, 0x14, 0x31, 0xbc, 0xa1, 0xac, 0x6e, 0xab, 0xfa, 0x57, 0x11, 0x53, 0xb3, 0x27, 0xe6, 0xf9, 0x47, 0x33, 0x44, 0x34, 0x1e}} , + {{0x79, 0xfc, 0xa6, 0xb4, 0x0b, 0x35, 0x20, 0xc9, 0x4d, 0x22, 0x84, 0xc4, 0xa9, 0x20, 0xec, 0x89, 0x94, 0xba, 0x66, 0x56, 0x48, 0xb9, 0x87, 0x7f, 0xca, 0x1e, 0x06, 0xed, 0xa5, 0x55, 0x59, 0x29}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x56, 0xe1, 0xf5, 0xf1, 0xd5, 0xab, 0xa8, 0x2b, 0xae, 0x89, 0xf3, 0xcf, 0x56, 0x9f, 0xf2, 0x4b, 0x31, 0xbc, 0x18, 0xa9, 0x06, 0x5b, 0xbe, 0xb4, 0x61, 0xf8, 0xb2, 0x06, 0x9c, 0x81, 0xab, 0x4c}} , + {{0x1f, 0x68, 0x76, 0x01, 0x16, 0x38, 0x2b, 0x0f, 0x77, 0x97, 0x92, 0x67, 0x4e, 0x86, 0x6a, 0x8b, 0xe5, 0xe8, 0x0c, 0xf7, 0x36, 0x39, 0xb5, 0x33, 0xe6, 0xcf, 0x5e, 0xbd, 0x18, 0xfb, 0x10, 0x1f}}}, +{{{0x83, 0xf0, 0x0d, 0x63, 0xef, 0x53, 0x6b, 0xb5, 0x6b, 0xf9, 0x83, 0xcf, 0xde, 0x04, 0x22, 0x9b, 0x2c, 0x0a, 0xe0, 0xa5, 0xd8, 0xc7, 0x9c, 0xa5, 0xa3, 0xf6, 0x6f, 0xcf, 0x90, 0x6b, 0x68, 0x7c}} , + {{0x33, 0x15, 0xd7, 0x7f, 0x1a, 0xd5, 0x21, 0x58, 0xc4, 0x18, 0xa5, 0xf0, 0xcc, 0x73, 0xa8, 0xfd, 0xfa, 0x18, 0xd1, 0x03, 0x91, 0x8d, 0x52, 0xd2, 0xa3, 0xa4, 0xd3, 0xb1, 0xea, 0x1d, 0x0f, 0x00}}}, +{{{0xcc, 0x48, 0x83, 0x90, 0xe5, 0xfd, 0x3f, 0x84, 0xaa, 0xf9, 0x8b, 0x82, 0x59, 0x24, 0x34, 0x68, 0x4f, 0x1c, 0x23, 0xd9, 0xcc, 0x71, 0xe1, 0x7f, 0x8c, 0xaf, 0xf1, 0xee, 0x00, 0xb6, 0xa0, 0x77}} , + {{0xf5, 0x1a, 0x61, 0xf7, 0x37, 0x9d, 0x00, 0xf4, 0xf2, 0x69, 0x6f, 0x4b, 0x01, 0x85, 0x19, 0x45, 0x4d, 0x7f, 0x02, 0x7c, 0x6a, 0x05, 0x47, 0x6c, 0x1f, 0x81, 0x20, 0xd4, 0xe8, 0x50, 0x27, 0x72}}}, +{{{0x2c, 0x3a, 0xe5, 0xad, 0xf4, 0xdd, 0x2d, 0xf7, 0x5c, 0x44, 0xb5, 0x5b, 0x21, 0xa3, 0x89, 0x5f, 0x96, 0x45, 0xca, 0x4d, 0xa4, 0x21, 0x99, 0x70, 0xda, 0xc4, 0xc4, 0xa0, 0xe5, 0xf4, 0xec, 0x0a}} , + {{0x07, 0x68, 0x21, 0x65, 0xe9, 0x08, 0xa0, 0x0b, 0x6a, 0x4a, 0xba, 0xb5, 0x80, 0xaf, 0xd0, 0x1b, 0xc5, 0xf5, 0x4b, 0x73, 0x50, 0x60, 0x2d, 0x71, 0x69, 0x61, 0x0e, 0xc0, 0x20, 0x40, 0x30, 0x19}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xd0, 0x75, 0x57, 0x3b, 0xeb, 0x5c, 0x14, 0x56, 0x50, 0xc9, 0x4f, 0xb8, 0xb8, 0x1e, 0xa3, 0xf4, 0xab, 0xf5, 0xa9, 0x20, 0x15, 0x94, 0x82, 0xda, 0x96, 0x1c, 0x9b, 0x59, 0x8c, 0xff, 0xf4, 0x51}} , + {{0xc1, 0x3a, 0x86, 0xd7, 0xb0, 0x06, 0x84, 0x7f, 0x1b, 0xbd, 0xd4, 0x07, 0x78, 0x80, 0x2e, 0xb1, 0xb4, 0xee, 0x52, 0x38, 0xee, 0x9a, 0xf9, 0xf6, 0xf3, 0x41, 0x6e, 0xd4, 0x88, 0x95, 0xac, 0x35}}}, +{{{0x41, 0x97, 0xbf, 0x71, 0x6a, 0x9b, 0x72, 0xec, 0xf3, 0xf8, 0x6b, 0xe6, 0x0e, 0x6c, 0x69, 0xa5, 0x2f, 0x68, 0x52, 0xd8, 0x61, 0x81, 0xc0, 0x63, 0x3f, 0xa6, 0x3c, 0x13, 0x90, 0xe6, 0x8d, 0x56}} , + {{0xe8, 0x39, 0x30, 0x77, 0x23, 0xb1, 0xfd, 0x1b, 0x3d, 0x3e, 0x74, 0x4d, 0x7f, 0xae, 0x5b, 0x3a, 0xb4, 0x65, 0x0e, 0x3a, 0x43, 0xdc, 0xdc, 0x41, 0x47, 0xe6, 0xe8, 0x92, 0x09, 0x22, 0x48, 0x4c}}}, +{{{0x85, 0x57, 0x9f, 0xb5, 0xc8, 0x06, 0xb2, 0x9f, 0x47, 0x3f, 0xf0, 0xfa, 0xe6, 0xa9, 0xb1, 0x9b, 0x6f, 0x96, 0x7d, 0xf9, 0xa4, 0x65, 0x09, 0x75, 0x32, 0xa6, 0x6c, 0x7f, 0x47, 0x4b, 0x2f, 0x4f}} , + {{0x34, 0xe9, 0x59, 0x93, 0x9d, 0x26, 0x80, 0x54, 0xf2, 0xcc, 0x3c, 0xc2, 0x25, 0x85, 0xe3, 0x6a, 0xc1, 0x62, 0x04, 0xa7, 0x08, 0x32, 0x6d, 0xa1, 0x39, 0x84, 0x8a, 0x3b, 0x87, 0x5f, 0x11, 0x13}}}, +{{{0xda, 0x03, 0x34, 0x66, 0xc4, 0x0c, 0x73, 0x6e, 0xbc, 0x24, 0xb5, 0xf9, 0x70, 0x81, 0x52, 0xe9, 0xf4, 0x7c, 0x23, 0xdd, 0x9f, 0xb8, 0x46, 0xef, 0x1d, 0x22, 0x55, 0x7d, 0x71, 0xc4, 0x42, 0x33}} , + {{0xc5, 0x37, 0x69, 0x5b, 0xa8, 0xc6, 0x9d, 0xa4, 0xfc, 0x61, 0x6e, 0x68, 0x46, 0xea, 0xd7, 0x1c, 0x67, 0xd2, 0x7d, 0xfa, 0xf1, 0xcc, 0x54, 0x8d, 0x36, 0x35, 0xc9, 0x00, 0xdf, 0x6c, 0x67, 0x50}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x9a, 0x4d, 0x42, 0x29, 0x5d, 0xa4, 0x6b, 0x6f, 0xa8, 0x8a, 0x4d, 0x91, 0x7b, 0xd2, 0xdf, 0x36, 0xef, 0x01, 0x22, 0xc5, 0xcc, 0x8d, 0xeb, 0x58, 0x3d, 0xb3, 0x50, 0xfc, 0x8b, 0x97, 0x96, 0x33}} , + {{0x93, 0x33, 0x07, 0xc8, 0x4a, 0xca, 0xd0, 0xb1, 0xab, 0xbd, 0xdd, 0xa7, 0x7c, 0xac, 0x3e, 0x45, 0xcb, 0xcc, 0x07, 0x91, 0xbf, 0x35, 0x9d, 0xcb, 0x7d, 0x12, 0x3c, 0x11, 0x59, 0x13, 0xcf, 0x5c}}}, +{{{0x45, 0xb8, 0x41, 0xd7, 0xab, 0x07, 0x15, 0x00, 0x8e, 0xce, 0xdf, 0xb2, 0x43, 0x5c, 0x01, 0xdc, 0xf4, 0x01, 0x51, 0x95, 0x10, 0x5a, 0xf6, 0x24, 0x24, 0xa0, 0x19, 0x3a, 0x09, 0x2a, 0xaa, 0x3f}} , + {{0xdc, 0x8e, 0xeb, 0xc6, 0xbf, 0xdd, 0x11, 0x7b, 0xe7, 0x47, 0xe6, 0xce, 0xe7, 0xb6, 0xc5, 0xe8, 0x8a, 0xdc, 0x4b, 0x57, 0x15, 0x3b, 0x66, 0xca, 0x89, 0xa3, 0xfd, 0xac, 0x0d, 0xe1, 0x1d, 0x7a}}}, +{{{0x89, 0xef, 0xbf, 0x03, 0x75, 0xd0, 0x29, 0x50, 0xcb, 0x7d, 0xd6, 0xbe, 0xad, 0x5f, 0x7b, 0x00, 0x32, 0xaa, 0x98, 0xed, 0x3f, 0x8f, 0x92, 0xcb, 0x81, 0x56, 0x01, 0x63, 0x64, 0xa3, 0x38, 0x39}} , + {{0x8b, 0xa4, 0xd6, 0x50, 0xb4, 0xaa, 0x5d, 0x64, 0x64, 0x76, 0x2e, 0xa1, 0xa6, 0xb3, 0xb8, 0x7c, 0x7a, 0x56, 0xf5, 0x5c, 0x4e, 0x84, 0x5c, 0xfb, 0xdd, 0xca, 0x48, 0x8b, 0x48, 0xb9, 0xba, 0x34}}}, +{{{0xc5, 0xe3, 0xe8, 0xae, 0x17, 0x27, 0xe3, 0x64, 0x60, 0x71, 0x47, 0x29, 0x02, 0x0f, 0x92, 0x5d, 0x10, 0x93, 0xc8, 0x0e, 0xa1, 0xed, 0xba, 0xa9, 0x96, 0x1c, 0xc5, 0x76, 0x30, 0xcd, 0xf9, 0x30}} , + {{0x95, 0xb0, 0xbd, 0x8c, 0xbc, 0xa7, 0x4f, 0x7e, 0xfd, 0x4e, 0x3a, 0xbf, 0x5f, 0x04, 0x79, 0x80, 0x2b, 0x5a, 0x9f, 0x4f, 0x68, 0x21, 0x19, 0x71, 0xc6, 0x20, 0x01, 0x42, 0xaa, 0xdf, 0xae, 0x2c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x90, 0x6e, 0x7e, 0x4b, 0x71, 0x93, 0xc0, 0x72, 0xed, 0xeb, 0x71, 0x24, 0x97, 0x26, 0x9c, 0xfe, 0xcb, 0x3e, 0x59, 0x19, 0xa8, 0x0f, 0x75, 0x7d, 0xbe, 0x18, 0xe6, 0x96, 0x1e, 0x95, 0x70, 0x60}} , + {{0x89, 0x66, 0x3e, 0x1d, 0x4c, 0x5f, 0xfe, 0xc0, 0x04, 0x43, 0xd6, 0x44, 0x19, 0xb5, 0xad, 0xc7, 0x22, 0xdc, 0x71, 0x28, 0x64, 0xde, 0x41, 0x38, 0x27, 0x8f, 0x2c, 0x6b, 0x08, 0xb8, 0xb8, 0x7b}}}, +{{{0x3d, 0x70, 0x27, 0x9d, 0xd9, 0xaf, 0xb1, 0x27, 0xaf, 0xe3, 0x5d, 0x1e, 0x3a, 0x30, 0x54, 0x61, 0x60, 0xe8, 0xc3, 0x26, 0x3a, 0xbc, 0x7e, 0xf5, 0x81, 0xdd, 0x64, 0x01, 0x04, 0xeb, 0xc0, 0x1e}} , + {{0xda, 0x2c, 0xa4, 0xd1, 0xa1, 0xc3, 0x5c, 0x6e, 0x32, 0x07, 0x1f, 0xb8, 0x0e, 0x19, 0x9e, 0x99, 0x29, 0x33, 0x9a, 0xae, 0x7a, 0xed, 0x68, 0x42, 0x69, 0x7c, 0x07, 0xb3, 0x38, 0x2c, 0xf6, 0x3d}}}, +{{{0x64, 0xaa, 0xb5, 0x88, 0x79, 0x65, 0x38, 0x8c, 0x94, 0xd6, 0x62, 0x37, 0x7d, 0x64, 0xcd, 0x3a, 0xeb, 0xff, 0xe8, 0x81, 0x09, 0xc7, 0x6a, 0x50, 0x09, 0x0d, 0x28, 0x03, 0x0d, 0x9a, 0x93, 0x0a}} , + {{0x42, 0xa3, 0xf1, 0xc5, 0xb4, 0x0f, 0xd8, 0xc8, 0x8d, 0x15, 0x31, 0xbd, 0xf8, 0x07, 0x8b, 0xcd, 0x08, 0x8a, 0xfb, 0x18, 0x07, 0xfe, 0x8e, 0x52, 0x86, 0xef, 0xbe, 0xec, 0x49, 0x52, 0x99, 0x08}}}, +{{{0x0f, 0xa9, 0xd5, 0x01, 0xaa, 0x48, 0x4f, 0x28, 0x66, 0x32, 0x1a, 0xba, 0x7c, 0xea, 0x11, 0x80, 0x17, 0x18, 0x9b, 0x56, 0x88, 0x25, 0x06, 0x69, 0x12, 0x2c, 0xea, 0x56, 0x69, 0x41, 0x24, 0x19}} , + {{0xde, 0x21, 0xf0, 0xda, 0x8a, 0xfb, 0xb1, 0xb8, 0xcd, 0xc8, 0x6a, 0x82, 0x19, 0x73, 0xdb, 0xc7, 0xcf, 0x88, 0xeb, 0x96, 0xee, 0x6f, 0xfb, 0x06, 0xd2, 0xcd, 0x7d, 0x7b, 0x12, 0x28, 0x8e, 0x0c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x93, 0x44, 0x97, 0xce, 0x28, 0xff, 0x3a, 0x40, 0xc4, 0xf5, 0xf6, 0x9b, 0xf4, 0x6b, 0x07, 0x84, 0xfb, 0x98, 0xd8, 0xec, 0x8c, 0x03, 0x57, 0xec, 0x49, 0xed, 0x63, 0xb6, 0xaa, 0xff, 0x98, 0x28}} , + {{0x3d, 0x16, 0x35, 0xf3, 0x46, 0xbc, 0xb3, 0xf4, 0xc6, 0xb6, 0x4f, 0xfa, 0xf4, 0xa0, 0x13, 0xe6, 0x57, 0x45, 0x93, 0xb9, 0xbc, 0xd6, 0x59, 0xe7, 0x77, 0x94, 0x6c, 0xab, 0x96, 0x3b, 0x4f, 0x09}}}, +{{{0x5a, 0xf7, 0x6b, 0x01, 0x12, 0x4f, 0x51, 0xc1, 0x70, 0x84, 0x94, 0x47, 0xb2, 0x01, 0x6c, 0x71, 0xd7, 0xcc, 0x17, 0x66, 0x0f, 0x59, 0x5d, 0x5d, 0x10, 0x01, 0x57, 0x11, 0xf5, 0xdd, 0xe2, 0x34}} , + {{0x26, 0xd9, 0x1f, 0x5c, 0x58, 0xac, 0x8b, 0x03, 0xd2, 0xc3, 0x85, 0x0f, 0x3a, 0xc3, 0x7f, 0x6d, 0x8e, 0x86, 0xcd, 0x52, 0x74, 0x8f, 0x55, 0x77, 0x17, 0xb7, 0x8e, 0xb7, 0x88, 0xea, 0xda, 0x1b}}}, +{{{0xb6, 0xea, 0x0e, 0x40, 0x93, 0x20, 0x79, 0x35, 0x6a, 0x61, 0x84, 0x5a, 0x07, 0x6d, 0xf9, 0x77, 0x6f, 0xed, 0x69, 0x1c, 0x0d, 0x25, 0x76, 0xcc, 0xf0, 0xdb, 0xbb, 0xc5, 0xad, 0xe2, 0x26, 0x57}} , + {{0xcf, 0xe8, 0x0e, 0x6b, 0x96, 0x7d, 0xed, 0x27, 0xd1, 0x3c, 0xa9, 0xd9, 0x50, 0xa9, 0x98, 0x84, 0x5e, 0x86, 0xef, 0xd6, 0xf0, 0xf8, 0x0e, 0x89, 0x05, 0x2f, 0xd9, 0x5f, 0x15, 0x5f, 0x73, 0x79}}}, +{{{0xc8, 0x5c, 0x16, 0xfe, 0xed, 0x9f, 0x26, 0x56, 0xf6, 0x4b, 0x9f, 0xa7, 0x0a, 0x85, 0xfe, 0xa5, 0x8c, 0x87, 0xdd, 0x98, 0xce, 0x4e, 0xc3, 0x58, 0x55, 0xb2, 0x7b, 0x3d, 0xd8, 0x6b, 0xb5, 0x4c}} , + {{0x65, 0x38, 0xa0, 0x15, 0xfa, 0xa7, 0xb4, 0x8f, 0xeb, 0xc4, 0x86, 0x9b, 0x30, 0xa5, 0x5e, 0x4d, 0xea, 0x8a, 0x9a, 0x9f, 0x1a, 0xd8, 0x5b, 0x53, 0x14, 0x19, 0x25, 0x63, 0xb4, 0x6f, 0x1f, 0x5d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xac, 0x8f, 0xbc, 0x1e, 0x7d, 0x8b, 0x5a, 0x0b, 0x8d, 0xaf, 0x76, 0x2e, 0x71, 0xe3, 0x3b, 0x6f, 0x53, 0x2f, 0x3e, 0x90, 0x95, 0xd4, 0x35, 0x14, 0x4f, 0x8c, 0x3c, 0xce, 0x57, 0x1c, 0x76, 0x49}} , + {{0xa8, 0x50, 0xe1, 0x61, 0x6b, 0x57, 0x35, 0xeb, 0x44, 0x0b, 0x0c, 0x6e, 0xf9, 0x25, 0x80, 0x74, 0xf2, 0x8f, 0x6f, 0x7a, 0x3e, 0x7f, 0x2d, 0xf3, 0x4e, 0x09, 0x65, 0x10, 0x5e, 0x03, 0x25, 0x32}}}, +{{{0xa9, 0x60, 0xdc, 0x0f, 0x64, 0xe5, 0x1d, 0xe2, 0x8d, 0x4f, 0x79, 0x2f, 0x0e, 0x24, 0x02, 0x00, 0x05, 0x77, 0x43, 0x25, 0x3d, 0x6a, 0xc7, 0xb7, 0xbf, 0x04, 0x08, 0x65, 0xf4, 0x39, 0x4b, 0x65}} , + {{0x96, 0x19, 0x12, 0x6b, 0x6a, 0xb7, 0xe3, 0xdc, 0x45, 0x9b, 0xdb, 0xb4, 0xa8, 0xae, 0xdc, 0xa8, 0x14, 0x44, 0x65, 0x62, 0xce, 0x34, 0x9a, 0x84, 0x18, 0x12, 0x01, 0xf1, 0xe2, 0x7b, 0xce, 0x50}}}, +{{{0x41, 0x21, 0x30, 0x53, 0x1b, 0x47, 0x01, 0xb7, 0x18, 0xd8, 0x82, 0x57, 0xbd, 0xa3, 0x60, 0xf0, 0x32, 0xf6, 0x5b, 0xf0, 0x30, 0x88, 0x91, 0x59, 0xfd, 0x90, 0xa2, 0xb9, 0x55, 0x93, 0x21, 0x34}} , + {{0x97, 0x67, 0x9e, 0xeb, 0x6a, 0xf9, 0x6e, 0xd6, 0x73, 0xe8, 0x6b, 0x29, 0xec, 0x63, 0x82, 0x00, 0xa8, 0x99, 0x1c, 0x1d, 0x30, 0xc8, 0x90, 0x52, 0x90, 0xb6, 0x6a, 0x80, 0x4e, 0xff, 0x4b, 0x51}}}, +{{{0x0f, 0x7d, 0x63, 0x8c, 0x6e, 0x5c, 0xde, 0x30, 0xdf, 0x65, 0xfa, 0x2e, 0xb0, 0xa3, 0x25, 0x05, 0x54, 0xbd, 0x25, 0xba, 0x06, 0xae, 0xdf, 0x8b, 0xd9, 0x1b, 0xea, 0x38, 0xb3, 0x05, 0x16, 0x09}} , + {{0xc7, 0x8c, 0xbf, 0x64, 0x28, 0xad, 0xf8, 0xa5, 0x5a, 0x6f, 0xc9, 0xba, 0xd5, 0x7f, 0xd5, 0xd6, 0xbd, 0x66, 0x2f, 0x3d, 0xaa, 0x54, 0xf6, 0xba, 0x32, 0x22, 0x9a, 0x1e, 0x52, 0x05, 0xf4, 0x1d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xaa, 0x1f, 0xbb, 0xeb, 0xfe, 0xe4, 0x87, 0xfc, 0xb1, 0x2c, 0xb7, 0x88, 0xf4, 0xc6, 0xb9, 0xf5, 0x24, 0x46, 0xf2, 0xa5, 0x9f, 0x8f, 0x8a, 0x93, 0x70, 0x69, 0xd4, 0x56, 0xec, 0xfd, 0x06, 0x46}} , + {{0x4e, 0x66, 0xcf, 0x4e, 0x34, 0xce, 0x0c, 0xd9, 0xa6, 0x50, 0xd6, 0x5e, 0x95, 0xaf, 0xe9, 0x58, 0xfa, 0xee, 0x9b, 0xb8, 0xa5, 0x0f, 0x35, 0xe0, 0x43, 0x82, 0x6d, 0x65, 0xe6, 0xd9, 0x00, 0x0f}}}, +{{{0x7b, 0x75, 0x3a, 0xfc, 0x64, 0xd3, 0x29, 0x7e, 0xdd, 0x49, 0x9a, 0x59, 0x53, 0xbf, 0xb4, 0xa7, 0x52, 0xb3, 0x05, 0xab, 0xc3, 0xaf, 0x16, 0x1a, 0x85, 0x42, 0x32, 0xa2, 0x86, 0xfa, 0x39, 0x43}} , + {{0x0e, 0x4b, 0xa3, 0x63, 0x8a, 0xfe, 0xa5, 0x58, 0xf1, 0x13, 0xbd, 0x9d, 0xaa, 0x7f, 0x76, 0x40, 0x70, 0x81, 0x10, 0x75, 0x99, 0xbb, 0xbe, 0x0b, 0x16, 0xe9, 0xba, 0x62, 0x34, 0xcc, 0x07, 0x6d}}}, +{{{0xc3, 0xf1, 0xc6, 0x93, 0x65, 0xee, 0x0b, 0xbc, 0xea, 0x14, 0xf0, 0xc1, 0xf8, 0x84, 0x89, 0xc2, 0xc9, 0xd7, 0xea, 0x34, 0xca, 0xa7, 0xc4, 0x99, 0xd5, 0x50, 0x69, 0xcb, 0xd6, 0x21, 0x63, 0x7c}} , + {{0x99, 0xeb, 0x7c, 0x31, 0x73, 0x64, 0x67, 0x7f, 0x0c, 0x66, 0xaa, 0x8c, 0x69, 0x91, 0xe2, 0x26, 0xd3, 0x23, 0xe2, 0x76, 0x5d, 0x32, 0x52, 0xdf, 0x5d, 0xc5, 0x8f, 0xb7, 0x7c, 0x84, 0xb3, 0x70}}}, +{{{0xeb, 0x01, 0xc7, 0x36, 0x97, 0x4e, 0xb6, 0xab, 0x5f, 0x0d, 0x2c, 0xba, 0x67, 0x64, 0x55, 0xde, 0xbc, 0xff, 0xa6, 0xec, 0x04, 0xd3, 0x8d, 0x39, 0x56, 0x5e, 0xee, 0xf8, 0xe4, 0x2e, 0x33, 0x62}} , + {{0x65, 0xef, 0xb8, 0x9f, 0xc8, 0x4b, 0xa7, 0xfd, 0x21, 0x49, 0x9b, 0x92, 0x35, 0x82, 0xd6, 0x0a, 0x9b, 0xf2, 0x79, 0xf1, 0x47, 0x2f, 0x6a, 0x7e, 0x9f, 0xcf, 0x18, 0x02, 0x3c, 0xfb, 0x1b, 0x3e}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x2f, 0x8b, 0xc8, 0x40, 0x51, 0xd1, 0xac, 0x1a, 0x0b, 0xe4, 0xa9, 0xa2, 0x42, 0x21, 0x19, 0x2f, 0x7b, 0x97, 0xbf, 0xf7, 0x57, 0x6d, 0x3f, 0x3d, 0x4f, 0x0f, 0xe2, 0xb2, 0x81, 0x00, 0x9e, 0x7b}} , + {{0x8c, 0x85, 0x2b, 0xc4, 0xfc, 0xf1, 0xab, 0xe8, 0x79, 0x22, 0xc4, 0x84, 0x17, 0x3a, 0xfa, 0x86, 0xa6, 0x7d, 0xf9, 0xf3, 0x6f, 0x03, 0x57, 0x20, 0x4d, 0x79, 0xf9, 0x6e, 0x71, 0x54, 0x38, 0x09}}}, +{{{0x40, 0x29, 0x74, 0xa8, 0x2f, 0x5e, 0xf9, 0x79, 0xa4, 0xf3, 0x3e, 0xb9, 0xfd, 0x33, 0x31, 0xac, 0x9a, 0x69, 0x88, 0x1e, 0x77, 0x21, 0x2d, 0xf3, 0x91, 0x52, 0x26, 0x15, 0xb2, 0xa6, 0xcf, 0x7e}} , + {{0xc6, 0x20, 0x47, 0x6c, 0xa4, 0x7d, 0xcb, 0x63, 0xea, 0x5b, 0x03, 0xdf, 0x3e, 0x88, 0x81, 0x6d, 0xce, 0x07, 0x42, 0x18, 0x60, 0x7e, 0x7b, 0x55, 0xfe, 0x6a, 0xf3, 0xda, 0x5c, 0x8b, 0x95, 0x10}}}, +{{{0x62, 0xe4, 0x0d, 0x03, 0xb4, 0xd7, 0xcd, 0xfa, 0xbd, 0x46, 0xdf, 0x93, 0x71, 0x10, 0x2c, 0xa8, 0x3b, 0xb6, 0x09, 0x05, 0x70, 0x84, 0x43, 0x29, 0xa8, 0x59, 0xf5, 0x8e, 0x10, 0xe4, 0xd7, 0x20}} , + {{0x57, 0x82, 0x1c, 0xab, 0xbf, 0x62, 0x70, 0xe8, 0xc4, 0xcf, 0xf0, 0x28, 0x6e, 0x16, 0x3c, 0x08, 0x78, 0x89, 0x85, 0x46, 0x0f, 0xf6, 0x7f, 0xcf, 0xcb, 0x7e, 0xb8, 0x25, 0xe9, 0x5a, 0xfa, 0x03}}}, +{{{0xfb, 0x95, 0x92, 0x63, 0x50, 0xfc, 0x62, 0xf0, 0xa4, 0x5e, 0x8c, 0x18, 0xc2, 0x17, 0x24, 0xb7, 0x78, 0xc2, 0xa9, 0xe7, 0x6a, 0x32, 0xd6, 0x29, 0x85, 0xaf, 0xcb, 0x8d, 0x91, 0x13, 0xda, 0x6b}} , + {{0x36, 0x0a, 0xc2, 0xb6, 0x4b, 0xa5, 0x5d, 0x07, 0x17, 0x41, 0x31, 0x5f, 0x62, 0x46, 0xf8, 0x92, 0xf9, 0x66, 0x48, 0x73, 0xa6, 0x97, 0x0d, 0x7d, 0x88, 0xee, 0x62, 0xb1, 0x03, 0xa8, 0x3f, 0x2c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x4a, 0xb1, 0x70, 0x8a, 0xa9, 0xe8, 0x63, 0x79, 0x00, 0xe2, 0x25, 0x16, 0xca, 0x4b, 0x0f, 0xa4, 0x66, 0xad, 0x19, 0x9f, 0x88, 0x67, 0x0c, 0x8b, 0xc2, 0x4a, 0x5b, 0x2b, 0x6d, 0x95, 0xaf, 0x19}} , + {{0x8b, 0x9d, 0xb6, 0xcc, 0x60, 0xb4, 0x72, 0x4f, 0x17, 0x69, 0x5a, 0x4a, 0x68, 0x34, 0xab, 0xa1, 0x45, 0x32, 0x3c, 0x83, 0x87, 0x72, 0x30, 0x54, 0x77, 0x68, 0xae, 0xfb, 0xb5, 0x8b, 0x22, 0x5e}}}, +{{{0xf1, 0xb9, 0x87, 0x35, 0xc5, 0xbb, 0xb9, 0xcf, 0xf5, 0xd6, 0xcd, 0xd5, 0x0c, 0x7c, 0x0e, 0xe6, 0x90, 0x34, 0xfb, 0x51, 0x42, 0x1e, 0x6d, 0xac, 0x9a, 0x46, 0xc4, 0x97, 0x29, 0x32, 0xbf, 0x45}} , + {{0x66, 0x9e, 0xc6, 0x24, 0xc0, 0xed, 0xa5, 0x5d, 0x88, 0xd4, 0xf0, 0x73, 0x97, 0x7b, 0xea, 0x7f, 0x42, 0xff, 0x21, 0xa0, 0x9b, 0x2f, 0x9a, 0xfd, 0x53, 0x57, 0x07, 0x84, 0x48, 0x88, 0x9d, 0x52}}}, +{{{0xc6, 0x96, 0x48, 0x34, 0x2a, 0x06, 0xaf, 0x94, 0x3d, 0xf4, 0x1a, 0xcf, 0xf2, 0xc0, 0x21, 0xc2, 0x42, 0x5e, 0xc8, 0x2f, 0x35, 0xa2, 0x3e, 0x29, 0xfa, 0x0c, 0x84, 0xe5, 0x89, 0x72, 0x7c, 0x06}} , + {{0x32, 0x65, 0x03, 0xe5, 0x89, 0xa6, 0x6e, 0xb3, 0x5b, 0x8e, 0xca, 0xeb, 0xfe, 0x22, 0x56, 0x8b, 0x5d, 0x14, 0x4b, 0x4d, 0xf9, 0xbe, 0xb5, 0xf5, 0xe6, 0x5c, 0x7b, 0x8b, 0xf4, 0x13, 0x11, 0x34}}}, +{{{0x07, 0xc6, 0x22, 0x15, 0xe2, 0x9c, 0x60, 0xa2, 0x19, 0xd9, 0x27, 0xae, 0x37, 0x4e, 0xa6, 0xc9, 0x80, 0xa6, 0x91, 0x8f, 0x12, 0x49, 0xe5, 0x00, 0x18, 0x47, 0xd1, 0xd7, 0x28, 0x22, 0x63, 0x39}} , + {{0xe8, 0xe2, 0x00, 0x7e, 0xf2, 0x9e, 0x1e, 0x99, 0x39, 0x95, 0x04, 0xbd, 0x1e, 0x67, 0x7b, 0xb2, 0x26, 0xac, 0xe6, 0xaa, 0xe2, 0x46, 0xd5, 0xe4, 0xe8, 0x86, 0xbd, 0xab, 0x7c, 0x55, 0x59, 0x6f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x24, 0x64, 0x6e, 0x9b, 0x35, 0x71, 0x78, 0xce, 0x33, 0x03, 0x21, 0x33, 0x36, 0xf1, 0x73, 0x9b, 0xb9, 0x15, 0x8b, 0x2c, 0x69, 0xcf, 0x4d, 0xed, 0x4f, 0x4d, 0x57, 0x14, 0x13, 0x82, 0xa4, 0x4d}} , + {{0x65, 0x6e, 0x0a, 0xa4, 0x59, 0x07, 0x17, 0xf2, 0x6b, 0x4a, 0x1f, 0x6e, 0xf6, 0xb5, 0xbc, 0x62, 0xe4, 0xb6, 0xda, 0xa2, 0x93, 0xbc, 0x29, 0x05, 0xd2, 0xd2, 0x73, 0x46, 0x03, 0x16, 0x40, 0x31}}}, +{{{0x4c, 0x73, 0x6d, 0x15, 0xbd, 0xa1, 0x4d, 0x5c, 0x13, 0x0b, 0x24, 0x06, 0x98, 0x78, 0x1c, 0x5b, 0xeb, 0x1f, 0x18, 0x54, 0x43, 0xd9, 0x55, 0x66, 0xda, 0x29, 0x21, 0xe8, 0xb8, 0x3c, 0x42, 0x22}} , + {{0xb4, 0xcd, 0x08, 0x6f, 0x15, 0x23, 0x1a, 0x0b, 0x22, 0xed, 0xd1, 0xf1, 0xa7, 0xc7, 0x73, 0x45, 0xf3, 0x9e, 0xce, 0x76, 0xb7, 0xf6, 0x39, 0xb6, 0x8e, 0x79, 0xbe, 0xe9, 0x9b, 0xcf, 0x7d, 0x62}}}, +{{{0x92, 0x5b, 0xfc, 0x72, 0xfd, 0xba, 0xf1, 0xfd, 0xa6, 0x7c, 0x95, 0xe3, 0x61, 0x3f, 0xe9, 0x03, 0xd4, 0x2b, 0xd4, 0x20, 0xd9, 0xdb, 0x4d, 0x32, 0x3e, 0xf5, 0x11, 0x64, 0xe3, 0xb4, 0xbe, 0x32}} , + {{0x86, 0x17, 0x90, 0xe7, 0xc9, 0x1f, 0x10, 0xa5, 0x6a, 0x2d, 0x39, 0xd0, 0x3b, 0xc4, 0xa6, 0xe9, 0x59, 0x13, 0xda, 0x1a, 0xe6, 0xa0, 0xb9, 0x3c, 0x50, 0xb8, 0x40, 0x7c, 0x15, 0x36, 0x5a, 0x42}}}, +{{{0xb4, 0x0b, 0x32, 0xab, 0xdc, 0x04, 0x51, 0x55, 0x21, 0x1e, 0x0b, 0x75, 0x99, 0x89, 0x73, 0x35, 0x3a, 0x91, 0x2b, 0xfe, 0xe7, 0x49, 0xea, 0x76, 0xc1, 0xf9, 0x46, 0xb9, 0x53, 0x02, 0x23, 0x04}} , + {{0xfc, 0x5a, 0x1e, 0x1d, 0x74, 0x58, 0x95, 0xa6, 0x8f, 0x7b, 0x97, 0x3e, 0x17, 0x3b, 0x79, 0x2d, 0xa6, 0x57, 0xef, 0x45, 0x02, 0x0b, 0x4d, 0x6e, 0x9e, 0x93, 0x8d, 0x2f, 0xd9, 0x9d, 0xdb, 0x04}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xc0, 0xd7, 0x56, 0x97, 0x58, 0x91, 0xde, 0x09, 0x4f, 0x9f, 0xbe, 0x63, 0xb0, 0x83, 0x86, 0x43, 0x5d, 0xbc, 0xe0, 0xf3, 0xc0, 0x75, 0xbf, 0x8b, 0x8e, 0xaa, 0xf7, 0x8b, 0x64, 0x6e, 0xb0, 0x63}} , + {{0x16, 0xae, 0x8b, 0xe0, 0x9b, 0x24, 0x68, 0x5c, 0x44, 0xc2, 0xd0, 0x08, 0xb7, 0x7b, 0x62, 0xfd, 0x7f, 0xd8, 0xd4, 0xb7, 0x50, 0xfd, 0x2c, 0x1b, 0xbf, 0x41, 0x95, 0xd9, 0x8e, 0xd8, 0x17, 0x1b}}}, +{{{0x86, 0x55, 0x37, 0x8e, 0xc3, 0x38, 0x48, 0x14, 0xb5, 0x97, 0xd2, 0xa7, 0x54, 0x45, 0xf1, 0x35, 0x44, 0x38, 0x9e, 0xf1, 0x1b, 0xb6, 0x34, 0x00, 0x3c, 0x96, 0xee, 0x29, 0x00, 0xea, 0x2c, 0x0b}} , + {{0xea, 0xda, 0x99, 0x9e, 0x19, 0x83, 0x66, 0x6d, 0xe9, 0x76, 0x87, 0x50, 0xd1, 0xfd, 0x3c, 0x60, 0x87, 0xc6, 0x41, 0xd9, 0x8e, 0xdb, 0x5e, 0xde, 0xaa, 0x9a, 0xd3, 0x28, 0xda, 0x95, 0xea, 0x47}}}, +{{{0xd0, 0x80, 0xba, 0x19, 0xae, 0x1d, 0xa9, 0x79, 0xf6, 0x3f, 0xac, 0x5d, 0x6f, 0x96, 0x1f, 0x2a, 0xce, 0x29, 0xb2, 0xff, 0x37, 0xf1, 0x94, 0x8f, 0x0c, 0xb5, 0x28, 0xba, 0x9a, 0x21, 0xf6, 0x66}} , + {{0x02, 0xfb, 0x54, 0xb8, 0x05, 0xf3, 0x81, 0x52, 0x69, 0x34, 0x46, 0x9d, 0x86, 0x76, 0x8f, 0xd7, 0xf8, 0x6a, 0x66, 0xff, 0xe6, 0xa7, 0x90, 0xf7, 0x5e, 0xcd, 0x6a, 0x9b, 0x55, 0xfc, 0x9d, 0x48}}}, +{{{0xbd, 0xaa, 0x13, 0xe6, 0xcd, 0x45, 0x4a, 0xa4, 0x59, 0x0a, 0x64, 0xb1, 0x98, 0xd6, 0x34, 0x13, 0x04, 0xe6, 0x97, 0x94, 0x06, 0xcb, 0xd4, 0x4e, 0xbb, 0x96, 0xcd, 0xd1, 0x57, 0xd1, 0xe3, 0x06}} , + {{0x7a, 0x6c, 0x45, 0x27, 0xc4, 0x93, 0x7f, 0x7d, 0x7c, 0x62, 0x50, 0x38, 0x3a, 0x6b, 0xb5, 0x88, 0xc6, 0xd9, 0xf1, 0x78, 0x19, 0xb9, 0x39, 0x93, 0x3d, 0xc9, 0xe0, 0x9c, 0x3c, 0xce, 0xf5, 0x72}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x24, 0xea, 0x23, 0x7d, 0x56, 0x2c, 0xe2, 0x59, 0x0e, 0x85, 0x60, 0x04, 0x88, 0x5a, 0x74, 0x1e, 0x4b, 0xef, 0x13, 0xda, 0x4c, 0xff, 0x83, 0x45, 0x85, 0x3f, 0x08, 0x95, 0x2c, 0x20, 0x13, 0x1f}} , + {{0x48, 0x5f, 0x27, 0x90, 0x5c, 0x02, 0x42, 0xad, 0x78, 0x47, 0x5c, 0xb5, 0x7e, 0x08, 0x85, 0x00, 0xfa, 0x7f, 0xfd, 0xfd, 0xe7, 0x09, 0x11, 0xf2, 0x7e, 0x1b, 0x38, 0x6c, 0x35, 0x6d, 0x33, 0x66}}}, +{{{0x93, 0x03, 0x36, 0x81, 0xac, 0xe4, 0x20, 0x09, 0x35, 0x4c, 0x45, 0xb2, 0x1e, 0x4c, 0x14, 0x21, 0xe6, 0xe9, 0x8a, 0x7b, 0x8d, 0xfe, 0x1e, 0xc6, 0x3e, 0xc1, 0x35, 0xfa, 0xe7, 0x70, 0x4e, 0x1d}} , + {{0x61, 0x2e, 0xc2, 0xdd, 0x95, 0x57, 0xd1, 0xab, 0x80, 0xe8, 0x63, 0x17, 0xb5, 0x48, 0xe4, 0x8a, 0x11, 0x9e, 0x72, 0xbe, 0x85, 0x8d, 0x51, 0x0a, 0xf2, 0x9f, 0xe0, 0x1c, 0xa9, 0x07, 0x28, 0x7b}}}, +{{{0xbb, 0x71, 0x14, 0x5e, 0x26, 0x8c, 0x3d, 0xc8, 0xe9, 0x7c, 0xd3, 0xd6, 0xd1, 0x2f, 0x07, 0x6d, 0xe6, 0xdf, 0xfb, 0x79, 0xd6, 0x99, 0x59, 0x96, 0x48, 0x40, 0x0f, 0x3a, 0x7b, 0xb2, 0xa0, 0x72}} , + {{0x4e, 0x3b, 0x69, 0xc8, 0x43, 0x75, 0x51, 0x6c, 0x79, 0x56, 0xe4, 0xcb, 0xf7, 0xa6, 0x51, 0xc2, 0x2c, 0x42, 0x0b, 0xd4, 0x82, 0x20, 0x1c, 0x01, 0x08, 0x66, 0xd7, 0xbf, 0x04, 0x56, 0xfc, 0x02}}}, +{{{0x24, 0xe8, 0xb7, 0x60, 0xae, 0x47, 0x80, 0xfc, 0xe5, 0x23, 0xe7, 0xc2, 0xc9, 0x85, 0xe6, 0x98, 0xa0, 0x29, 0x4e, 0xe1, 0x84, 0x39, 0x2d, 0x95, 0x2c, 0xf3, 0x45, 0x3c, 0xff, 0xaf, 0x27, 0x4c}} , + {{0x6b, 0xa6, 0xf5, 0x4b, 0x11, 0xbd, 0xba, 0x5b, 0x9e, 0xc4, 0xa4, 0x51, 0x1e, 0xbe, 0xd0, 0x90, 0x3a, 0x9c, 0xc2, 0x26, 0xb6, 0x1e, 0xf1, 0x95, 0x7d, 0xc8, 0x6d, 0x52, 0xe6, 0x99, 0x2c, 0x5f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x85, 0xe0, 0x24, 0x32, 0xb4, 0xd1, 0xef, 0xfc, 0x69, 0xa2, 0xbf, 0x8f, 0x72, 0x2c, 0x95, 0xf6, 0xe4, 0x6e, 0x7d, 0x90, 0xf7, 0x57, 0x81, 0xa0, 0xf7, 0xda, 0xef, 0x33, 0x07, 0xe3, 0x6b, 0x78}} , + {{0x36, 0x27, 0x3e, 0xc6, 0x12, 0x07, 0xab, 0x4e, 0xbe, 0x69, 0x9d, 0xb3, 0xbe, 0x08, 0x7c, 0x2a, 0x47, 0x08, 0xfd, 0xd4, 0xcd, 0x0e, 0x27, 0x34, 0x5b, 0x98, 0x34, 0x2f, 0x77, 0x5f, 0x3a, 0x65}}}, +{{{0x13, 0xaa, 0x2e, 0x4c, 0xf0, 0x22, 0xb8, 0x6c, 0xb3, 0x19, 0x4d, 0xeb, 0x6b, 0xd0, 0xa4, 0xc6, 0x9c, 0xdd, 0xc8, 0x5b, 0x81, 0x57, 0x89, 0xdf, 0x33, 0xa9, 0x68, 0x49, 0x80, 0xe4, 0xfe, 0x21}} , + {{0x00, 0x17, 0x90, 0x30, 0xe9, 0xd3, 0x60, 0x30, 0x31, 0xc2, 0x72, 0x89, 0x7a, 0x36, 0xa5, 0xbd, 0x39, 0x83, 0x85, 0x50, 0xa1, 0x5d, 0x6c, 0x41, 0x1d, 0xb5, 0x2c, 0x07, 0x40, 0x77, 0x0b, 0x50}}}, +{{{0x64, 0x34, 0xec, 0xc0, 0x9e, 0x44, 0x41, 0xaf, 0xa0, 0x36, 0x05, 0x6d, 0xea, 0x30, 0x25, 0x46, 0x35, 0x24, 0x9d, 0x86, 0xbd, 0x95, 0xf1, 0x6a, 0x46, 0xd7, 0x94, 0x54, 0xf9, 0x3b, 0xbd, 0x5d}} , + {{0x77, 0x5b, 0xe2, 0x37, 0xc7, 0xe1, 0x7c, 0x13, 0x8c, 0x9f, 0x7b, 0x7b, 0x2a, 0xce, 0x42, 0xa3, 0xb9, 0x2a, 0x99, 0xa8, 0xc0, 0xd8, 0x3c, 0x86, 0xb0, 0xfb, 0xe9, 0x76, 0x77, 0xf7, 0xf5, 0x56}}}, +{{{0xdf, 0xb3, 0x46, 0x11, 0x6e, 0x13, 0xb7, 0x28, 0x4e, 0x56, 0xdd, 0xf1, 0xac, 0xad, 0x58, 0xc3, 0xf8, 0x88, 0x94, 0x5e, 0x06, 0x98, 0xa1, 0xe4, 0x6a, 0xfb, 0x0a, 0x49, 0x5d, 0x8a, 0xfe, 0x77}} , + {{0x46, 0x02, 0xf5, 0xa5, 0xaf, 0xc5, 0x75, 0x6d, 0xba, 0x45, 0x35, 0x0a, 0xfe, 0xc9, 0xac, 0x22, 0x91, 0x8d, 0x21, 0x95, 0x33, 0x03, 0xc0, 0x8a, 0x16, 0xf3, 0x39, 0xe0, 0x01, 0x0f, 0x53, 0x3c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x34, 0x75, 0x37, 0x1f, 0x34, 0x4e, 0xa9, 0x1d, 0x68, 0x67, 0xf8, 0x49, 0x98, 0x96, 0xfc, 0x4c, 0x65, 0x97, 0xf7, 0x02, 0x4a, 0x52, 0x6c, 0x01, 0xbd, 0x48, 0xbb, 0x1b, 0xed, 0xa4, 0xe2, 0x53}} , + {{0x59, 0xd5, 0x9b, 0x5a, 0xa2, 0x90, 0xd3, 0xb8, 0x37, 0x4c, 0x55, 0x82, 0x28, 0x08, 0x0f, 0x7f, 0xaa, 0x81, 0x65, 0xe0, 0x0c, 0x52, 0xc9, 0xa3, 0x32, 0x27, 0x64, 0xda, 0xfd, 0x34, 0x23, 0x5a}}}, +{{{0xb5, 0xb0, 0x0c, 0x4d, 0xb3, 0x7b, 0x23, 0xc8, 0x1f, 0x8a, 0x39, 0x66, 0xe6, 0xba, 0x4c, 0x10, 0x37, 0xca, 0x9c, 0x7c, 0x05, 0x9e, 0xff, 0xc0, 0xf8, 0x8e, 0xb1, 0x8f, 0x6f, 0x67, 0x18, 0x26}} , + {{0x4b, 0x41, 0x13, 0x54, 0x23, 0x1a, 0xa4, 0x4e, 0xa9, 0x8b, 0x1e, 0x4b, 0xfc, 0x15, 0x24, 0xbb, 0x7e, 0xcb, 0xb6, 0x1e, 0x1b, 0xf5, 0xf2, 0xc8, 0x56, 0xec, 0x32, 0xa2, 0x60, 0x5b, 0xa0, 0x2a}}}, +{{{0xa4, 0x29, 0x47, 0x86, 0x2e, 0x92, 0x4f, 0x11, 0x4f, 0xf3, 0xb2, 0x5c, 0xd5, 0x3e, 0xa6, 0xb9, 0xc8, 0xe2, 0x33, 0x11, 0x1f, 0x01, 0x8f, 0xb0, 0x9b, 0xc7, 0xa5, 0xff, 0x83, 0x0f, 0x1e, 0x28}} , + {{0x1d, 0x29, 0x7a, 0xa1, 0xec, 0x8e, 0xb5, 0xad, 0xea, 0x02, 0x68, 0x60, 0x74, 0x29, 0x1c, 0xa5, 0xcf, 0xc8, 0x3b, 0x7d, 0x8b, 0x2b, 0x7c, 0xad, 0xa4, 0x40, 0x17, 0x51, 0x59, 0x7c, 0x2e, 0x5d}}}, +{{{0x0a, 0x6c, 0x4f, 0xbc, 0x3e, 0x32, 0xe7, 0x4a, 0x1a, 0x13, 0xc1, 0x49, 0x38, 0xbf, 0xf7, 0xc2, 0xd3, 0x8f, 0x6b, 0xad, 0x52, 0xf7, 0xcf, 0xbc, 0x27, 0xcb, 0x40, 0x67, 0x76, 0xcd, 0x6d, 0x56}} , + {{0xe5, 0xb0, 0x27, 0xad, 0xbe, 0x9b, 0xf2, 0xb5, 0x63, 0xde, 0x3a, 0x23, 0x95, 0xb7, 0x0a, 0x7e, 0xf3, 0x9e, 0x45, 0x6f, 0x19, 0x39, 0x75, 0x8f, 0x39, 0x3d, 0x0f, 0xc0, 0x9f, 0xf1, 0xe9, 0x51}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x88, 0xaa, 0x14, 0x24, 0x86, 0x94, 0x11, 0x12, 0x3e, 0x1a, 0xb5, 0xcc, 0xbb, 0xe0, 0x9c, 0xd5, 0x9c, 0x6d, 0xba, 0x58, 0x72, 0x8d, 0xfb, 0x22, 0x7b, 0x9f, 0x7c, 0x94, 0x30, 0xb3, 0x51, 0x21}} , + {{0xf6, 0x74, 0x3d, 0xf2, 0xaf, 0xd0, 0x1e, 0x03, 0x7c, 0x23, 0x6b, 0xc9, 0xfc, 0x25, 0x70, 0x90, 0xdc, 0x9a, 0xa4, 0xfb, 0x49, 0xfc, 0x3d, 0x0a, 0x35, 0x38, 0x6f, 0xe4, 0x7e, 0x50, 0x01, 0x2a}}}, +{{{0xd6, 0xe3, 0x96, 0x61, 0x3a, 0xfd, 0xef, 0x9b, 0x1f, 0x90, 0xa4, 0x24, 0x14, 0x5b, 0xc8, 0xde, 0x50, 0xb1, 0x1d, 0xaf, 0xe8, 0x55, 0x8a, 0x87, 0x0d, 0xfe, 0xaa, 0x3b, 0x82, 0x2c, 0x8d, 0x7b}} , + {{0x85, 0x0c, 0xaf, 0xf8, 0x83, 0x44, 0x49, 0xd9, 0x45, 0xcf, 0xf7, 0x48, 0xd9, 0x53, 0xb4, 0xf1, 0x65, 0xa0, 0xe1, 0xc3, 0xb3, 0x15, 0xed, 0x89, 0x9b, 0x4f, 0x62, 0xb3, 0x57, 0xa5, 0x45, 0x1c}}}, +{{{0x8f, 0x12, 0xea, 0xaf, 0xd1, 0x1f, 0x79, 0x10, 0x0b, 0xf6, 0xa3, 0x7b, 0xea, 0xac, 0x8b, 0x57, 0x32, 0x62, 0xe7, 0x06, 0x12, 0x51, 0xa0, 0x3b, 0x43, 0x5e, 0xa4, 0x20, 0x78, 0x31, 0xce, 0x0d}} , + {{0x84, 0x7c, 0xc2, 0xa6, 0x91, 0x23, 0xce, 0xbd, 0xdc, 0xf9, 0xce, 0xd5, 0x75, 0x30, 0x22, 0xe6, 0xf9, 0x43, 0x62, 0x0d, 0xf7, 0x75, 0x9d, 0x7f, 0x8c, 0xff, 0x7d, 0xe4, 0x72, 0xac, 0x9f, 0x1c}}}, +{{{0x88, 0xc1, 0x99, 0xd0, 0x3c, 0x1c, 0x5d, 0xb4, 0xef, 0x13, 0x0f, 0x90, 0xb9, 0x36, 0x2f, 0x95, 0x95, 0xc6, 0xdc, 0xde, 0x0a, 0x51, 0xe2, 0x8d, 0xf3, 0xbc, 0x51, 0xec, 0xdf, 0xb1, 0xa2, 0x5f}} , + {{0x2e, 0x68, 0xa1, 0x23, 0x7d, 0x9b, 0x40, 0x69, 0x85, 0x7b, 0x42, 0xbf, 0x90, 0x4b, 0xd6, 0x40, 0x2f, 0xd7, 0x52, 0x52, 0xb2, 0x21, 0xde, 0x64, 0xbd, 0x88, 0xc3, 0x6d, 0xa5, 0xfa, 0x81, 0x3f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xfb, 0xfd, 0x47, 0x7b, 0x8a, 0x66, 0x9e, 0x79, 0x2e, 0x64, 0x82, 0xef, 0xf7, 0x21, 0xec, 0xf6, 0xd8, 0x86, 0x09, 0x31, 0x7c, 0xdd, 0x03, 0x6a, 0x58, 0xa0, 0x77, 0xb7, 0x9b, 0x8c, 0x87, 0x1f}} , + {{0x55, 0x47, 0xe4, 0xa8, 0x3d, 0x55, 0x21, 0x34, 0xab, 0x1d, 0xae, 0xe0, 0xf4, 0xea, 0xdb, 0xc5, 0xb9, 0x58, 0xbf, 0xc4, 0x2a, 0x89, 0x31, 0x1a, 0xf4, 0x2d, 0xe1, 0xca, 0x37, 0x99, 0x47, 0x59}}}, +{{{0xc7, 0xca, 0x63, 0xc1, 0x49, 0xa9, 0x35, 0x45, 0x55, 0x7e, 0xda, 0x64, 0x32, 0x07, 0x50, 0xf7, 0x32, 0xac, 0xde, 0x75, 0x58, 0x9b, 0x11, 0xb2, 0x3a, 0x1f, 0xf5, 0xf7, 0x79, 0x04, 0xe6, 0x08}} , + {{0x46, 0xfa, 0x22, 0x4b, 0xfa, 0xe1, 0xfe, 0x96, 0xfc, 0x67, 0xba, 0x67, 0x97, 0xc4, 0xe7, 0x1b, 0x86, 0x90, 0x5f, 0xee, 0xf4, 0x5b, 0x11, 0xb2, 0xcd, 0xad, 0xee, 0xc2, 0x48, 0x6c, 0x2b, 0x1b}}}, +{{{0xe3, 0x39, 0x62, 0xb4, 0x4f, 0x31, 0x04, 0xc9, 0xda, 0xd5, 0x73, 0x51, 0x57, 0xc5, 0xb8, 0xf3, 0xa3, 0x43, 0x70, 0xe4, 0x61, 0x81, 0x84, 0xe2, 0xbb, 0xbf, 0x4f, 0x9e, 0xa4, 0x5e, 0x74, 0x06}} , + {{0x29, 0xac, 0xff, 0x27, 0xe0, 0x59, 0xbe, 0x39, 0x9c, 0x0d, 0x83, 0xd7, 0x10, 0x0b, 0x15, 0xb7, 0xe1, 0xc2, 0x2c, 0x30, 0x73, 0x80, 0x3a, 0x7d, 0x5d, 0xab, 0x58, 0x6b, 0xc1, 0xf0, 0xf4, 0x22}}}, +{{{0xfe, 0x7f, 0xfb, 0x35, 0x7d, 0xc6, 0x01, 0x23, 0x28, 0xc4, 0x02, 0xac, 0x1f, 0x42, 0xb4, 0x9d, 0xfc, 0x00, 0x94, 0xa5, 0xee, 0xca, 0xda, 0x97, 0x09, 0x41, 0x77, 0x87, 0x5d, 0x7b, 0x87, 0x78}} , + {{0xf5, 0xfb, 0x90, 0x2d, 0x81, 0x19, 0x9e, 0x2f, 0x6d, 0x85, 0x88, 0x8c, 0x40, 0x5c, 0x77, 0x41, 0x4d, 0x01, 0x19, 0x76, 0x60, 0xe8, 0x4c, 0x48, 0xe4, 0x33, 0x83, 0x32, 0x6c, 0xb4, 0x41, 0x03}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xff, 0x10, 0xc2, 0x09, 0x4f, 0x6e, 0xf4, 0xd2, 0xdf, 0x7e, 0xca, 0x7b, 0x1c, 0x1d, 0xba, 0xa3, 0xb6, 0xda, 0x67, 0x33, 0xd4, 0x87, 0x36, 0x4b, 0x11, 0x20, 0x05, 0xa6, 0x29, 0xc1, 0x87, 0x17}} , + {{0xf6, 0x96, 0xca, 0x2f, 0xda, 0x38, 0xa7, 0x1b, 0xfc, 0xca, 0x7d, 0xfe, 0x08, 0x89, 0xe2, 0x47, 0x2b, 0x6a, 0x5d, 0x4b, 0xfa, 0xa1, 0xb4, 0xde, 0xb6, 0xc2, 0x31, 0x51, 0xf5, 0xe0, 0xa4, 0x0b}}}, +{{{0x5c, 0xe5, 0xc6, 0x04, 0x8e, 0x2b, 0x57, 0xbe, 0x38, 0x85, 0x23, 0xcb, 0xb7, 0xbe, 0x4f, 0xa9, 0xd3, 0x6e, 0x12, 0xaa, 0xd5, 0xb2, 0x2e, 0x93, 0x29, 0x9a, 0x4a, 0x88, 0x18, 0x43, 0xf5, 0x01}} , + {{0x50, 0xfc, 0xdb, 0xa2, 0x59, 0x21, 0x8d, 0xbd, 0x7e, 0x33, 0xae, 0x2f, 0x87, 0x1a, 0xd0, 0x97, 0xc7, 0x0d, 0x4d, 0x63, 0x01, 0xef, 0x05, 0x84, 0xec, 0x40, 0xdd, 0xa8, 0x0a, 0x4f, 0x70, 0x0b}}}, +{{{0x41, 0x69, 0x01, 0x67, 0x5c, 0xd3, 0x8a, 0xc5, 0xcf, 0x3f, 0xd1, 0x57, 0xd1, 0x67, 0x3e, 0x01, 0x39, 0xb5, 0xcb, 0x81, 0x56, 0x96, 0x26, 0xb6, 0xc2, 0xe7, 0x5c, 0xfb, 0x63, 0x97, 0x58, 0x06}} , + {{0x0c, 0x0e, 0xf3, 0xba, 0xf0, 0xe5, 0xba, 0xb2, 0x57, 0x77, 0xc6, 0x20, 0x9b, 0x89, 0x24, 0xbe, 0xf2, 0x9c, 0x8a, 0xba, 0x69, 0xc1, 0xf1, 0xb0, 0x4f, 0x2a, 0x05, 0x9a, 0xee, 0x10, 0x7e, 0x36}}}, +{{{0x3f, 0x26, 0xe9, 0x40, 0xe9, 0x03, 0xad, 0x06, 0x69, 0x91, 0xe0, 0xd1, 0x89, 0x60, 0x84, 0x79, 0xde, 0x27, 0x6d, 0xe6, 0x76, 0xbd, 0xea, 0xe6, 0xae, 0x48, 0xc3, 0x67, 0xc0, 0x57, 0xcd, 0x2f}} , + {{0x7f, 0xc1, 0xdc, 0xb9, 0xc7, 0xbc, 0x86, 0x3d, 0x55, 0x4b, 0x28, 0x7a, 0xfb, 0x4d, 0xc7, 0xf8, 0xbc, 0x67, 0x2a, 0x60, 0x4d, 0x8f, 0x07, 0x0b, 0x1a, 0x17, 0xbf, 0xfa, 0xac, 0xa7, 0x3d, 0x1a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x91, 0x3f, 0xed, 0x5e, 0x18, 0x78, 0x3f, 0x23, 0x2c, 0x0d, 0x8c, 0x44, 0x00, 0xe8, 0xfb, 0xe9, 0x8e, 0xd6, 0xd1, 0x36, 0x58, 0x57, 0x9e, 0xae, 0x4b, 0x5c, 0x0b, 0x07, 0xbc, 0x6b, 0x55, 0x2b}} , + {{0x6f, 0x4d, 0x17, 0xd7, 0xe1, 0x84, 0xd9, 0x78, 0xb1, 0x90, 0xfd, 0x2e, 0xb3, 0xb5, 0x19, 0x3f, 0x1b, 0xfa, 0xc0, 0x68, 0xb3, 0xdd, 0x00, 0x2e, 0x89, 0xbd, 0x7e, 0x80, 0x32, 0x13, 0xa0, 0x7b}}}, +{{{0x1a, 0x6f, 0x40, 0xaf, 0x44, 0x44, 0xb0, 0x43, 0x8f, 0x0d, 0xd0, 0x1e, 0xc4, 0x0b, 0x19, 0x5d, 0x8e, 0xfe, 0xc1, 0xf3, 0xc5, 0x5c, 0x91, 0xf8, 0x04, 0x4e, 0xbe, 0x90, 0xb4, 0x47, 0x5c, 0x3f}} , + {{0xb0, 0x3b, 0x2c, 0xf3, 0xfe, 0x32, 0x71, 0x07, 0x3f, 0xaa, 0xba, 0x45, 0x60, 0xa8, 0x8d, 0xea, 0x54, 0xcb, 0x39, 0x10, 0xb4, 0xf2, 0x8b, 0xd2, 0x14, 0x82, 0x42, 0x07, 0x8e, 0xe9, 0x7c, 0x53}}}, +{{{0xb0, 0xae, 0xc1, 0x8d, 0xc9, 0x8f, 0xb9, 0x7a, 0x77, 0xef, 0xba, 0x79, 0xa0, 0x3c, 0xa8, 0xf5, 0x6a, 0xe2, 0x3f, 0x5d, 0x00, 0xe3, 0x4b, 0x45, 0x24, 0x7b, 0x43, 0x78, 0x55, 0x1d, 0x2b, 0x1e}} , + {{0x01, 0xb8, 0xd6, 0x16, 0x67, 0xa0, 0x15, 0xb9, 0xe1, 0x58, 0xa4, 0xa7, 0x31, 0x37, 0x77, 0x2f, 0x8b, 0x12, 0x9f, 0xf4, 0x3f, 0xc7, 0x36, 0x66, 0xd2, 0xa8, 0x56, 0xf7, 0x7f, 0x74, 0xc6, 0x41}}}, +{{{0x5d, 0xf8, 0xb4, 0xa8, 0x30, 0xdd, 0xcc, 0x38, 0xa5, 0xd3, 0xca, 0xd8, 0xd1, 0xf8, 0xb2, 0x31, 0x91, 0xd4, 0x72, 0x05, 0x57, 0x4a, 0x3b, 0x82, 0x4a, 0xc6, 0x68, 0x20, 0xe2, 0x18, 0x41, 0x61}} , + {{0x19, 0xd4, 0x8d, 0x47, 0x29, 0x12, 0x65, 0xb0, 0x11, 0x78, 0x47, 0xb5, 0xcb, 0xa3, 0xa5, 0xfa, 0x05, 0x85, 0x54, 0xa9, 0x33, 0x97, 0x8d, 0x2b, 0xc2, 0xfe, 0x99, 0x35, 0x28, 0xe5, 0xeb, 0x63}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xb1, 0x3f, 0x3f, 0xef, 0xd8, 0xf4, 0xfc, 0xb3, 0xa0, 0x60, 0x50, 0x06, 0x2b, 0x29, 0x52, 0x70, 0x15, 0x0b, 0x24, 0x24, 0xf8, 0x5f, 0x79, 0x18, 0xcc, 0xff, 0x89, 0x99, 0x84, 0xa1, 0xae, 0x13}} , + {{0x44, 0x1f, 0xb8, 0xc2, 0x01, 0xc1, 0x30, 0x19, 0x55, 0x05, 0x60, 0x10, 0xa4, 0x6c, 0x2d, 0x67, 0x70, 0xe5, 0x25, 0x1b, 0xf2, 0xbf, 0xdd, 0xfb, 0x70, 0x2b, 0xa1, 0x8c, 0x9c, 0x94, 0x84, 0x08}}}, +{{{0xe7, 0xc4, 0x43, 0x4d, 0xc9, 0x2b, 0x69, 0x5d, 0x1d, 0x3c, 0xaf, 0xbb, 0x43, 0x38, 0x4e, 0x98, 0x3d, 0xed, 0x0d, 0x21, 0x03, 0xfd, 0xf0, 0x99, 0x47, 0x04, 0xb0, 0x98, 0x69, 0x55, 0x72, 0x0f}} , + {{0x5e, 0xdf, 0x15, 0x53, 0x3b, 0x86, 0x80, 0xb0, 0xf1, 0x70, 0x68, 0x8f, 0x66, 0x7c, 0x0e, 0x49, 0x1a, 0xd8, 0x6b, 0xfe, 0x4e, 0xef, 0xca, 0x47, 0xd4, 0x03, 0xc1, 0x37, 0x50, 0x9c, 0xc1, 0x16}}}, +{{{0xcd, 0x24, 0xc6, 0x3e, 0x0c, 0x82, 0x9b, 0x91, 0x2b, 0x61, 0x4a, 0xb2, 0x0f, 0x88, 0x55, 0x5f, 0x5a, 0x57, 0xff, 0xe5, 0x74, 0x0b, 0x13, 0x43, 0x00, 0xd8, 0x6b, 0xcf, 0xd2, 0x15, 0x03, 0x2c}} , + {{0xdc, 0xff, 0x15, 0x61, 0x2f, 0x4a, 0x2f, 0x62, 0xf2, 0x04, 0x2f, 0xb5, 0x0c, 0xb7, 0x1e, 0x3f, 0x74, 0x1a, 0x0f, 0xd7, 0xea, 0xcd, 0xd9, 0x7d, 0xf6, 0x12, 0x0e, 0x2f, 0xdb, 0x5a, 0x3b, 0x16}}}, +{{{0x1b, 0x37, 0x47, 0xe3, 0xf5, 0x9e, 0xea, 0x2c, 0x2a, 0xe7, 0x82, 0x36, 0xf4, 0x1f, 0x81, 0x47, 0x92, 0x4b, 0x69, 0x0e, 0x11, 0x8c, 0x5d, 0x53, 0x5b, 0x81, 0x27, 0x08, 0xbc, 0xa0, 0xae, 0x25}} , + {{0x69, 0x32, 0xa1, 0x05, 0x11, 0x42, 0x00, 0xd2, 0x59, 0xac, 0x4d, 0x62, 0x8b, 0x13, 0xe2, 0x50, 0x5d, 0xa0, 0x9d, 0x9b, 0xfd, 0xbb, 0x12, 0x41, 0x75, 0x41, 0x9e, 0xcc, 0xdc, 0xc7, 0xdc, 0x5d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xd9, 0xe3, 0x38, 0x06, 0x46, 0x70, 0x82, 0x5e, 0x28, 0x49, 0x79, 0xff, 0x25, 0xd2, 0x4e, 0x29, 0x8d, 0x06, 0xb0, 0x23, 0xae, 0x9b, 0x66, 0xe4, 0x7d, 0xc0, 0x70, 0x91, 0xa3, 0xfc, 0xec, 0x4e}} , + {{0x62, 0x12, 0x37, 0x6a, 0x30, 0xf6, 0x1e, 0xfb, 0x14, 0x5c, 0x0d, 0x0e, 0xb7, 0x81, 0x6a, 0xe7, 0x08, 0x05, 0xac, 0xaa, 0x38, 0x46, 0xe2, 0x73, 0xea, 0x4b, 0x07, 0x81, 0x43, 0x7c, 0x9e, 0x5e}}}, +{{{0xfc, 0xf9, 0x21, 0x4f, 0x2e, 0x76, 0x9b, 0x1f, 0x28, 0x60, 0x77, 0x43, 0x32, 0x9d, 0xbe, 0x17, 0x30, 0x2a, 0xc6, 0x18, 0x92, 0x66, 0x62, 0x30, 0x98, 0x40, 0x11, 0xa6, 0x7f, 0x18, 0x84, 0x28}} , + {{0x3f, 0xab, 0xd3, 0xf4, 0x8a, 0x76, 0xa1, 0x3c, 0xca, 0x2d, 0x49, 0xc3, 0xea, 0x08, 0x0b, 0x85, 0x17, 0x2a, 0xc3, 0x6c, 0x08, 0xfd, 0x57, 0x9f, 0x3d, 0x5f, 0xdf, 0x67, 0x68, 0x42, 0x00, 0x32}}}, +{{{0x51, 0x60, 0x1b, 0x06, 0x4f, 0x8a, 0x21, 0xba, 0x38, 0xa8, 0xba, 0xd6, 0x40, 0xf6, 0xe9, 0x9b, 0x76, 0x4d, 0x56, 0x21, 0x5b, 0x0a, 0x9b, 0x2e, 0x4f, 0x3d, 0x81, 0x32, 0x08, 0x9f, 0x97, 0x5b}} , + {{0xe5, 0x44, 0xec, 0x06, 0x9d, 0x90, 0x79, 0x9f, 0xd3, 0xe0, 0x79, 0xaf, 0x8f, 0x10, 0xfd, 0xdd, 0x04, 0xae, 0x27, 0x97, 0x46, 0x33, 0x79, 0xea, 0xb8, 0x4e, 0xca, 0x5a, 0x59, 0x57, 0xe1, 0x0e}}}, +{{{0x1a, 0xda, 0xf3, 0xa5, 0x41, 0x43, 0x28, 0xfc, 0x7e, 0xe7, 0x71, 0xea, 0xc6, 0x3b, 0x59, 0xcc, 0x2e, 0xd3, 0x40, 0xec, 0xb3, 0x13, 0x6f, 0x44, 0xcd, 0x13, 0xb2, 0x37, 0xf2, 0x6e, 0xd9, 0x1c}} , + {{0xe3, 0xdb, 0x60, 0xcd, 0x5c, 0x4a, 0x18, 0x0f, 0xef, 0x73, 0x36, 0x71, 0x8c, 0xf6, 0x11, 0xb4, 0xd8, 0xce, 0x17, 0x5e, 0x4f, 0x26, 0x77, 0x97, 0x5f, 0xcb, 0xef, 0x91, 0xeb, 0x6a, 0x62, 0x7a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x18, 0x4a, 0xa2, 0x97, 0x08, 0x81, 0x2d, 0x83, 0xc4, 0xcc, 0xf0, 0x83, 0x7e, 0xec, 0x0d, 0x95, 0x4c, 0x5b, 0xfb, 0xfa, 0x98, 0x80, 0x4a, 0x66, 0x56, 0x0c, 0x51, 0xb3, 0xf2, 0x04, 0x5d, 0x27}} , + {{0x3b, 0xb9, 0xb8, 0x06, 0x5a, 0x2e, 0xfe, 0xc3, 0x82, 0x37, 0x9c, 0xa3, 0x11, 0x1f, 0x9c, 0xa6, 0xda, 0x63, 0x48, 0x9b, 0xad, 0xde, 0x2d, 0xa6, 0xbc, 0x6e, 0x32, 0xda, 0x27, 0x65, 0xdd, 0x57}}}, +{{{0x84, 0x4f, 0x37, 0x31, 0x7d, 0x2e, 0xbc, 0xad, 0x87, 0x07, 0x2a, 0x6b, 0x37, 0xfc, 0x5f, 0xeb, 0x4e, 0x75, 0x35, 0xa6, 0xde, 0xab, 0x0a, 0x19, 0x3a, 0xb7, 0xb1, 0xef, 0x92, 0x6a, 0x3b, 0x3c}} , + {{0x3b, 0xb2, 0x94, 0x6d, 0x39, 0x60, 0xac, 0xee, 0xe7, 0x81, 0x1a, 0x3b, 0x76, 0x87, 0x5c, 0x05, 0x94, 0x2a, 0x45, 0xb9, 0x80, 0xe9, 0x22, 0xb1, 0x07, 0xcb, 0x40, 0x9e, 0x70, 0x49, 0x6d, 0x12}}}, +{{{0xfd, 0x18, 0x78, 0x84, 0xa8, 0x4c, 0x7d, 0x6e, 0x59, 0xa6, 0xe5, 0x74, 0xf1, 0x19, 0xa6, 0x84, 0x2e, 0x51, 0xc1, 0x29, 0x13, 0xf2, 0x14, 0x6b, 0x5d, 0x53, 0x51, 0xf7, 0xef, 0xbf, 0x01, 0x22}} , + {{0xa4, 0x4b, 0x62, 0x4c, 0xe6, 0xfd, 0x72, 0x07, 0xf2, 0x81, 0xfc, 0xf2, 0xbd, 0x12, 0x7c, 0x68, 0x76, 0x2a, 0xba, 0xf5, 0x65, 0xb1, 0x1f, 0x17, 0x0a, 0x38, 0xb0, 0xbf, 0xc0, 0xf8, 0xf4, 0x2a}}}, +{{{0x55, 0x60, 0x55, 0x5b, 0xe4, 0x1d, 0x71, 0x4c, 0x9d, 0x5b, 0x9f, 0x70, 0xa6, 0x85, 0x9a, 0x2c, 0xa0, 0xe2, 0x32, 0x48, 0xce, 0x9e, 0x2a, 0xa5, 0x07, 0x3b, 0xc7, 0x6c, 0x86, 0x77, 0xde, 0x3c}} , + {{0xf7, 0x18, 0x7a, 0x96, 0x7e, 0x43, 0x57, 0xa9, 0x55, 0xfc, 0x4e, 0xb6, 0x72, 0x00, 0xf2, 0xe4, 0xd7, 0x52, 0xd3, 0xd3, 0xb6, 0x85, 0xf6, 0x71, 0xc7, 0x44, 0x3f, 0x7f, 0xd7, 0xb3, 0xf2, 0x79}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x46, 0xca, 0xa7, 0x55, 0x7b, 0x79, 0xf3, 0xca, 0x5a, 0x65, 0xf6, 0xed, 0x50, 0x14, 0x7b, 0xe4, 0xc4, 0x2a, 0x65, 0x9e, 0xe2, 0xf9, 0xca, 0xa7, 0x22, 0x26, 0x53, 0xcb, 0x21, 0x5b, 0xa7, 0x31}} , + {{0x90, 0xd7, 0xc5, 0x26, 0x08, 0xbd, 0xb0, 0x53, 0x63, 0x58, 0xc3, 0x31, 0x5e, 0x75, 0x46, 0x15, 0x91, 0xa6, 0xf8, 0x2f, 0x1a, 0x08, 0x65, 0x88, 0x2f, 0x98, 0x04, 0xf1, 0x7c, 0x6e, 0x00, 0x77}}}, +{{{0x81, 0x21, 0x61, 0x09, 0xf6, 0x4e, 0xf1, 0x92, 0xee, 0x63, 0x61, 0x73, 0x87, 0xc7, 0x54, 0x0e, 0x42, 0x4b, 0xc9, 0x47, 0xd1, 0xb8, 0x7e, 0x91, 0x75, 0x37, 0x99, 0x28, 0xb8, 0xdd, 0x7f, 0x50}} , + {{0x89, 0x8f, 0xc0, 0xbe, 0x5d, 0xd6, 0x9f, 0xa0, 0xf0, 0x9d, 0x81, 0xce, 0x3a, 0x7b, 0x98, 0x58, 0xbb, 0xd7, 0x78, 0xc8, 0x3f, 0x13, 0xf1, 0x74, 0x19, 0xdf, 0xf8, 0x98, 0x89, 0x5d, 0xfa, 0x5f}}}, +{{{0x9e, 0x35, 0x85, 0x94, 0x47, 0x1f, 0x90, 0x15, 0x26, 0xd0, 0x84, 0xed, 0x8a, 0x80, 0xf7, 0x63, 0x42, 0x86, 0x27, 0xd7, 0xf4, 0x75, 0x58, 0xdc, 0x9c, 0xc0, 0x22, 0x7e, 0x20, 0x35, 0xfd, 0x1f}} , + {{0x68, 0x0e, 0x6f, 0x97, 0xba, 0x70, 0xbb, 0xa3, 0x0e, 0xe5, 0x0b, 0x12, 0xf4, 0xa2, 0xdc, 0x47, 0xf8, 0xe6, 0xd0, 0x23, 0x6c, 0x33, 0xa8, 0x99, 0x46, 0x6e, 0x0f, 0x44, 0xba, 0x76, 0x48, 0x0f}}}, +{{{0xa3, 0x2a, 0x61, 0x37, 0xe2, 0x59, 0x12, 0x0e, 0x27, 0xba, 0x64, 0x43, 0xae, 0xc0, 0x42, 0x69, 0x79, 0xa4, 0x1e, 0x29, 0x8b, 0x15, 0xeb, 0xf8, 0xaf, 0xd4, 0xa2, 0x68, 0x33, 0xb5, 0x7a, 0x24}} , + {{0x2c, 0x19, 0x33, 0xdd, 0x1b, 0xab, 0xec, 0x01, 0xb0, 0x23, 0xf8, 0x42, 0x2b, 0x06, 0x88, 0xea, 0x3d, 0x2d, 0x00, 0x2a, 0x78, 0x45, 0x4d, 0x38, 0xed, 0x2e, 0x2e, 0x44, 0x49, 0xed, 0xcb, 0x33}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xa0, 0x68, 0xe8, 0x41, 0x8f, 0x91, 0xf8, 0x11, 0x13, 0x90, 0x2e, 0xa7, 0xab, 0x30, 0xef, 0xad, 0xa0, 0x61, 0x00, 0x88, 0xef, 0xdb, 0xce, 0x5b, 0x5c, 0xbb, 0x62, 0xc8, 0x56, 0xf9, 0x00, 0x73}} , + {{0x3f, 0x60, 0xc1, 0x82, 0x2d, 0xa3, 0x28, 0x58, 0x24, 0x9e, 0x9f, 0xe3, 0x70, 0xcc, 0x09, 0x4e, 0x1a, 0x3f, 0x11, 0x11, 0x15, 0x07, 0x3c, 0xa4, 0x41, 0xe0, 0x65, 0xa3, 0x0a, 0x41, 0x6d, 0x11}}}, +{{{0x31, 0x40, 0x01, 0x52, 0x56, 0x94, 0x5b, 0x28, 0x8a, 0xaa, 0x52, 0xee, 0xd8, 0x0a, 0x05, 0x8d, 0xcd, 0xb5, 0xaa, 0x2e, 0x38, 0xaa, 0xb7, 0x87, 0xf7, 0x2b, 0xfb, 0x04, 0xcb, 0x84, 0x3d, 0x54}} , + {{0x20, 0xef, 0x59, 0xde, 0xa4, 0x2b, 0x93, 0x6e, 0x2e, 0xec, 0x42, 0x9a, 0xd4, 0x2d, 0xf4, 0x46, 0x58, 0x27, 0x2b, 0x18, 0x8f, 0x83, 0x3d, 0x69, 0x9e, 0xd4, 0x3e, 0xb6, 0xc5, 0xfd, 0x58, 0x03}}}, +{{{0x33, 0x89, 0xc9, 0x63, 0x62, 0x1c, 0x17, 0xb4, 0x60, 0xc4, 0x26, 0x68, 0x09, 0xc3, 0x2e, 0x37, 0x0f, 0x7b, 0xb4, 0x9c, 0xb6, 0xf9, 0xfb, 0xd4, 0x51, 0x78, 0xc8, 0x63, 0xea, 0x77, 0x47, 0x07}} , + {{0x32, 0xb4, 0x18, 0x47, 0x79, 0xcb, 0xd4, 0x5a, 0x07, 0x14, 0x0f, 0xa0, 0xd5, 0xac, 0xd0, 0x41, 0x40, 0xab, 0x61, 0x23, 0xe5, 0x2a, 0x2a, 0x6f, 0xf7, 0xa8, 0xd4, 0x76, 0xef, 0xe7, 0x45, 0x6c}}}, +{{{0xa1, 0x5e, 0x60, 0x4f, 0xfb, 0xe1, 0x70, 0x6a, 0x1f, 0x55, 0x4f, 0x09, 0xb4, 0x95, 0x33, 0x36, 0xc6, 0x81, 0x01, 0x18, 0x06, 0x25, 0x27, 0xa4, 0xb4, 0x24, 0xa4, 0x86, 0x03, 0x4c, 0xac, 0x02}} , + {{0x77, 0x38, 0xde, 0xd7, 0x60, 0x48, 0x07, 0xf0, 0x74, 0xa8, 0xff, 0x54, 0xe5, 0x30, 0x43, 0xff, 0x77, 0xfb, 0x21, 0x07, 0xff, 0xb2, 0x07, 0x6b, 0xe4, 0xe5, 0x30, 0xfc, 0x19, 0x6c, 0xa3, 0x01}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x13, 0xc5, 0x2c, 0xac, 0xd3, 0x83, 0x82, 0x7c, 0x29, 0xf7, 0x05, 0xa5, 0x00, 0xb6, 0x1f, 0x86, 0x55, 0xf4, 0xd6, 0x2f, 0x0c, 0x99, 0xd0, 0x65, 0x9b, 0x6b, 0x46, 0x0d, 0x43, 0xf8, 0x16, 0x28}} , + {{0x1e, 0x7f, 0xb4, 0x74, 0x7e, 0xb1, 0x89, 0x4f, 0x18, 0x5a, 0xab, 0x64, 0x06, 0xdf, 0x45, 0x87, 0xe0, 0x6a, 0xc6, 0xf0, 0x0e, 0xc9, 0x24, 0x35, 0x38, 0xea, 0x30, 0x54, 0xb4, 0xc4, 0x52, 0x54}}}, +{{{0xe9, 0x9f, 0xdc, 0x3f, 0xc1, 0x89, 0x44, 0x74, 0x27, 0xe4, 0xc1, 0x90, 0xff, 0x4a, 0xa7, 0x3c, 0xee, 0xcd, 0xf4, 0x1d, 0x25, 0x94, 0x7f, 0x63, 0x16, 0x48, 0xbc, 0x64, 0xfe, 0x95, 0xc4, 0x0c}} , + {{0x8b, 0x19, 0x75, 0x6e, 0x03, 0x06, 0x5e, 0x6a, 0x6f, 0x1a, 0x8c, 0xe3, 0xd3, 0x28, 0xf2, 0xe0, 0xb9, 0x7a, 0x43, 0x69, 0xe6, 0xd3, 0xc0, 0xfe, 0x7e, 0x97, 0xab, 0x6c, 0x7b, 0x8e, 0x13, 0x42}}}, +{{{0xd4, 0xca, 0x70, 0x3d, 0xab, 0xfb, 0x5f, 0x5e, 0x00, 0x0c, 0xcc, 0x77, 0x22, 0xf8, 0x78, 0x55, 0xae, 0x62, 0x35, 0xfb, 0x9a, 0xc6, 0x03, 0xe4, 0x0c, 0xee, 0xab, 0xc7, 0xc0, 0x89, 0x87, 0x54}} , + {{0x32, 0xad, 0xae, 0x85, 0x58, 0x43, 0xb8, 0xb1, 0xe6, 0x3e, 0x00, 0x9c, 0x78, 0x88, 0x56, 0xdb, 0x9c, 0xfc, 0x79, 0xf6, 0xf9, 0x41, 0x5f, 0xb7, 0xbc, 0x11, 0xf9, 0x20, 0x36, 0x1c, 0x53, 0x2b}}}, +{{{0x5a, 0x20, 0x5b, 0xa1, 0xa5, 0x44, 0x91, 0x24, 0x02, 0x63, 0x12, 0x64, 0xb8, 0x55, 0xf6, 0xde, 0x2c, 0xdb, 0x47, 0xb8, 0xc6, 0x0a, 0xc3, 0x00, 0x78, 0x93, 0xd8, 0xf5, 0xf5, 0x18, 0x28, 0x0a}} , + {{0xd6, 0x1b, 0x9a, 0x6c, 0xe5, 0x46, 0xea, 0x70, 0x96, 0x8d, 0x4e, 0x2a, 0x52, 0x21, 0x26, 0x4b, 0xb1, 0xbb, 0x0f, 0x7c, 0xa9, 0x9b, 0x04, 0xbb, 0x51, 0x08, 0xf1, 0x9a, 0xa4, 0x76, 0x7c, 0x18}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xfa, 0x94, 0xf7, 0x40, 0xd0, 0xd7, 0xeb, 0xa9, 0x82, 0x36, 0xd5, 0x15, 0xb9, 0x33, 0x7a, 0xbf, 0x8a, 0xf2, 0x63, 0xaa, 0x37, 0xf5, 0x59, 0xac, 0xbd, 0xbb, 0x32, 0x36, 0xbe, 0x73, 0x99, 0x38}} , + {{0x2c, 0xb3, 0xda, 0x7a, 0xd8, 0x3d, 0x99, 0xca, 0xd2, 0xf4, 0xda, 0x99, 0x8e, 0x4f, 0x98, 0xb7, 0xf4, 0xae, 0x3e, 0x9f, 0x8e, 0x35, 0x60, 0xa4, 0x33, 0x75, 0xa4, 0x04, 0x93, 0xb1, 0x6b, 0x4d}}}, +{{{0x97, 0x9d, 0xa8, 0xcd, 0x97, 0x7b, 0x9d, 0xb9, 0xe7, 0xa5, 0xef, 0xfd, 0xa8, 0x42, 0x6b, 0xc3, 0x62, 0x64, 0x7d, 0xa5, 0x1b, 0xc9, 0x9e, 0xd2, 0x45, 0xb9, 0xee, 0x03, 0xb0, 0xbf, 0xc0, 0x68}} , + {{0xed, 0xb7, 0x84, 0x2c, 0xf6, 0xd3, 0xa1, 0x6b, 0x24, 0x6d, 0x87, 0x56, 0x97, 0x59, 0x79, 0x62, 0x9f, 0xac, 0xed, 0xf3, 0xc9, 0x89, 0x21, 0x2e, 0x04, 0xb3, 0xcc, 0x2f, 0xbe, 0xd6, 0x0a, 0x4b}}}, +{{{0x39, 0x61, 0x05, 0xed, 0x25, 0x89, 0x8b, 0x5d, 0x1b, 0xcb, 0x0c, 0x55, 0xf4, 0x6a, 0x00, 0x8a, 0x46, 0xe8, 0x1e, 0xc6, 0x83, 0xc8, 0x5a, 0x76, 0xdb, 0xcc, 0x19, 0x7a, 0xcc, 0x67, 0x46, 0x0b}} , + {{0x53, 0xcf, 0xc2, 0xa1, 0xad, 0x6a, 0xf3, 0xcd, 0x8f, 0xc9, 0xde, 0x1c, 0xf8, 0x6c, 0x8f, 0xf8, 0x76, 0x42, 0xe7, 0xfe, 0xb2, 0x72, 0x21, 0x0a, 0x66, 0x74, 0x8f, 0xb7, 0xeb, 0xe4, 0x6f, 0x01}}}, +{{{0x22, 0x8c, 0x6b, 0xbe, 0xfc, 0x4d, 0x70, 0x62, 0x6e, 0x52, 0x77, 0x99, 0x88, 0x7e, 0x7b, 0x57, 0x7a, 0x0d, 0xfe, 0xdc, 0x72, 0x92, 0xf1, 0x68, 0x1d, 0x97, 0xd7, 0x7c, 0x8d, 0x53, 0x10, 0x37}} , + {{0x53, 0x88, 0x77, 0x02, 0xca, 0x27, 0xa8, 0xe5, 0x45, 0xe2, 0xa8, 0x48, 0x2a, 0xab, 0x18, 0xca, 0xea, 0x2d, 0x2a, 0x54, 0x17, 0x37, 0x32, 0x09, 0xdc, 0xe0, 0x4a, 0xb7, 0x7d, 0x82, 0x10, 0x7d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x8a, 0x64, 0x1e, 0x14, 0x0a, 0x57, 0xd4, 0xda, 0x5c, 0x96, 0x9b, 0x01, 0x4c, 0x67, 0xbf, 0x8b, 0x30, 0xfe, 0x08, 0xdb, 0x0d, 0xd5, 0xa8, 0xd7, 0x09, 0x11, 0x85, 0xa2, 0xd3, 0x45, 0xfb, 0x7e}} , + {{0xda, 0x8c, 0xc2, 0xd0, 0xac, 0x18, 0xe8, 0x52, 0x36, 0xd4, 0x21, 0xa3, 0xdd, 0x57, 0x22, 0x79, 0xb7, 0xf8, 0x71, 0x9d, 0xc6, 0x91, 0x70, 0x86, 0x56, 0xbf, 0xa1, 0x11, 0x8b, 0x19, 0xe1, 0x0f}}}, +{{{0x18, 0x32, 0x98, 0x2c, 0x8f, 0x91, 0xae, 0x12, 0xf0, 0x8c, 0xea, 0xf3, 0x3c, 0xb9, 0x5d, 0xe4, 0x69, 0xed, 0xb2, 0x47, 0x18, 0xbd, 0xce, 0x16, 0x52, 0x5c, 0x23, 0xe2, 0xa5, 0x25, 0x52, 0x5d}} , + {{0xb9, 0xb1, 0xe7, 0x5d, 0x4e, 0xbc, 0xee, 0xbb, 0x40, 0x81, 0x77, 0x82, 0x19, 0xab, 0xb5, 0xc6, 0xee, 0xab, 0x5b, 0x6b, 0x63, 0x92, 0x8a, 0x34, 0x8d, 0xcd, 0xee, 0x4f, 0x49, 0xe5, 0xc9, 0x7e}}}, +{{{0x21, 0xac, 0x8b, 0x22, 0xcd, 0xc3, 0x9a, 0xe9, 0x5e, 0x78, 0xbd, 0xde, 0xba, 0xad, 0xab, 0xbf, 0x75, 0x41, 0x09, 0xc5, 0x58, 0xa4, 0x7d, 0x92, 0xb0, 0x7f, 0xf2, 0xa1, 0xd1, 0xc0, 0xb3, 0x6d}} , + {{0x62, 0x4f, 0xd0, 0x75, 0x77, 0xba, 0x76, 0x77, 0xd7, 0xb8, 0xd8, 0x92, 0x6f, 0x98, 0x34, 0x3d, 0xd6, 0x4e, 0x1c, 0x0f, 0xf0, 0x8f, 0x2e, 0xf1, 0xb3, 0xbd, 0xb1, 0xb9, 0xec, 0x99, 0xb4, 0x07}}}, +{{{0x60, 0x57, 0x2e, 0x9a, 0x72, 0x1d, 0x6b, 0x6e, 0x58, 0x33, 0x24, 0x8c, 0x48, 0x39, 0x46, 0x8e, 0x89, 0x6a, 0x88, 0x51, 0x23, 0x62, 0xb5, 0x32, 0x09, 0x36, 0xe3, 0x57, 0xf5, 0x98, 0xde, 0x6f}} , + {{0x8b, 0x2c, 0x00, 0x48, 0x4a, 0xf9, 0x5b, 0x87, 0x69, 0x52, 0xe5, 0x5b, 0xd1, 0xb1, 0xe5, 0x25, 0x25, 0xe0, 0x9c, 0xc2, 0x13, 0x44, 0xe8, 0xb9, 0x0a, 0x70, 0xad, 0xbd, 0x0f, 0x51, 0x94, 0x69}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xa2, 0xdc, 0xab, 0xa9, 0x25, 0x2d, 0xac, 0x5f, 0x03, 0x33, 0x08, 0xe7, 0x7e, 0xfe, 0x95, 0x36, 0x3c, 0x5b, 0x3a, 0xd3, 0x05, 0x82, 0x1c, 0x95, 0x2d, 0xd8, 0x77, 0x7e, 0x02, 0xd9, 0x5b, 0x70}} , + {{0xc2, 0xfe, 0x1b, 0x0c, 0x67, 0xcd, 0xd6, 0xe0, 0x51, 0x8e, 0x2c, 0xe0, 0x79, 0x88, 0xf0, 0xcf, 0x41, 0x4a, 0xad, 0x23, 0xd4, 0x46, 0xca, 0x94, 0xa1, 0xc3, 0xeb, 0x28, 0x06, 0xfa, 0x17, 0x14}}}, +{{{0x7b, 0xaa, 0x70, 0x0a, 0x4b, 0xfb, 0xf5, 0xbf, 0x80, 0xc5, 0xcf, 0x08, 0x7a, 0xdd, 0xa1, 0xf4, 0x9d, 0x54, 0x50, 0x53, 0x23, 0x77, 0x23, 0xf5, 0x34, 0xa5, 0x22, 0xd1, 0x0d, 0x96, 0x2e, 0x47}} , + {{0xcc, 0xb7, 0x32, 0x89, 0x57, 0xd0, 0x98, 0x75, 0xe4, 0x37, 0x99, 0xa9, 0xe8, 0xba, 0xed, 0xba, 0xeb, 0xc7, 0x4f, 0x15, 0x76, 0x07, 0x0c, 0x4c, 0xef, 0x9f, 0x52, 0xfc, 0x04, 0x5d, 0x58, 0x10}}}, +{{{0xce, 0x82, 0xf0, 0x8f, 0x79, 0x02, 0xa8, 0xd1, 0xda, 0x14, 0x09, 0x48, 0xee, 0x8a, 0x40, 0x98, 0x76, 0x60, 0x54, 0x5a, 0xde, 0x03, 0x24, 0xf5, 0xe6, 0x2f, 0xe1, 0x03, 0xbf, 0x68, 0x82, 0x7f}} , + {{0x64, 0xe9, 0x28, 0xc7, 0xa4, 0xcf, 0x2a, 0xf9, 0x90, 0x64, 0x72, 0x2c, 0x8b, 0xeb, 0xec, 0xa0, 0xf2, 0x7d, 0x35, 0xb5, 0x90, 0x4d, 0x7f, 0x5b, 0x4a, 0x49, 0xe4, 0xb8, 0x3b, 0xc8, 0xa1, 0x2f}}}, +{{{0x8b, 0xc5, 0xcc, 0x3d, 0x69, 0xa6, 0xa1, 0x18, 0x44, 0xbc, 0x4d, 0x77, 0x37, 0xc7, 0x86, 0xec, 0x0c, 0xc9, 0xd6, 0x44, 0xa9, 0x23, 0x27, 0xb9, 0x03, 0x34, 0xa7, 0x0a, 0xd5, 0xc7, 0x34, 0x37}} , + {{0xf9, 0x7e, 0x3e, 0x66, 0xee, 0xf9, 0x99, 0x28, 0xff, 0xad, 0x11, 0xd8, 0xe2, 0x66, 0xc5, 0xcd, 0x0f, 0x0d, 0x0b, 0x6a, 0xfc, 0x7c, 0x24, 0xa8, 0x4f, 0xa8, 0x5e, 0x80, 0x45, 0x8b, 0x6c, 0x41}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xef, 0x1e, 0xec, 0xf7, 0x8d, 0x77, 0xf2, 0xea, 0xdb, 0x60, 0x03, 0x21, 0xc0, 0xff, 0x5e, 0x67, 0xc3, 0x71, 0x0b, 0x21, 0xb4, 0x41, 0xa0, 0x68, 0x38, 0xc6, 0x01, 0xa3, 0xd3, 0x51, 0x3c, 0x3c}} , + {{0x92, 0xf8, 0xd6, 0x4b, 0xef, 0x42, 0x13, 0xb2, 0x4a, 0xc4, 0x2e, 0x72, 0x3f, 0xc9, 0x11, 0xbd, 0x74, 0x02, 0x0e, 0xf5, 0x13, 0x9d, 0x83, 0x1a, 0x1b, 0xd5, 0x54, 0xde, 0xc4, 0x1e, 0x16, 0x6c}}}, +{{{0x27, 0x52, 0xe4, 0x63, 0xaa, 0x94, 0xe6, 0xc3, 0x28, 0x9c, 0xc6, 0x56, 0xac, 0xfa, 0xb6, 0xbd, 0xe2, 0xcc, 0x76, 0xc6, 0x27, 0x27, 0xa2, 0x8e, 0x78, 0x2b, 0x84, 0x72, 0x10, 0xbd, 0x4e, 0x2a}} , + {{0xea, 0xa7, 0x23, 0xef, 0x04, 0x61, 0x80, 0x50, 0xc9, 0x6e, 0xa5, 0x96, 0xd1, 0xd1, 0xc8, 0xc3, 0x18, 0xd7, 0x2d, 0xfd, 0x26, 0xbd, 0xcb, 0x7b, 0x92, 0x51, 0x0e, 0x4a, 0x65, 0x57, 0xb8, 0x49}}}, +{{{0xab, 0x55, 0x36, 0xc3, 0xec, 0x63, 0x55, 0x11, 0x55, 0xf6, 0xa5, 0xc7, 0x01, 0x5f, 0xfe, 0x79, 0xd8, 0x0a, 0xf7, 0x03, 0xd8, 0x98, 0x99, 0xf5, 0xd0, 0x00, 0x54, 0x6b, 0x66, 0x28, 0xf5, 0x25}} , + {{0x7a, 0x8d, 0xa1, 0x5d, 0x70, 0x5d, 0x51, 0x27, 0xee, 0x30, 0x65, 0x56, 0x95, 0x46, 0xde, 0xbd, 0x03, 0x75, 0xb4, 0x57, 0x59, 0x89, 0xeb, 0x02, 0x9e, 0xcc, 0x89, 0x19, 0xa7, 0xcb, 0x17, 0x67}}}, +{{{0x6a, 0xeb, 0xfc, 0x9a, 0x9a, 0x10, 0xce, 0xdb, 0x3a, 0x1c, 0x3c, 0x6a, 0x9d, 0xea, 0x46, 0xbc, 0x45, 0x49, 0xac, 0xe3, 0x41, 0x12, 0x7c, 0xf0, 0xf7, 0x4f, 0xf9, 0xf7, 0xff, 0x2c, 0x89, 0x04}} , + {{0x30, 0x31, 0x54, 0x1a, 0x46, 0xca, 0xe6, 0xc6, 0xcb, 0xe2, 0xc3, 0xc1, 0x8b, 0x75, 0x81, 0xbe, 0xee, 0xf8, 0xa3, 0x11, 0x1c, 0x25, 0xa3, 0xa7, 0x35, 0x51, 0x55, 0xe2, 0x25, 0xaa, 0xe2, 0x3a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xb4, 0x48, 0x10, 0x9f, 0x8a, 0x09, 0x76, 0xfa, 0xf0, 0x7a, 0xb0, 0x70, 0xf7, 0x83, 0x80, 0x52, 0x84, 0x2b, 0x26, 0xa2, 0xc4, 0x5d, 0x4f, 0xba, 0xb1, 0xc8, 0x40, 0x0d, 0x78, 0x97, 0xc4, 0x60}} , + {{0xd4, 0xb1, 0x6c, 0x08, 0xc7, 0x40, 0x38, 0x73, 0x5f, 0x0b, 0xf3, 0x76, 0x5d, 0xb2, 0xa5, 0x2f, 0x57, 0x57, 0x07, 0xed, 0x08, 0xa2, 0x6c, 0x4f, 0x08, 0x02, 0xb5, 0x0e, 0xee, 0x44, 0xfa, 0x22}}}, +{{{0x0f, 0x00, 0x3f, 0xa6, 0x04, 0x19, 0x56, 0x65, 0x31, 0x7f, 0x8b, 0xeb, 0x0d, 0xe1, 0x47, 0x89, 0x97, 0x16, 0x53, 0xfa, 0x81, 0xa7, 0xaa, 0xb2, 0xbf, 0x67, 0xeb, 0x72, 0x60, 0x81, 0x0d, 0x48}} , + {{0x7e, 0x13, 0x33, 0xcd, 0xa8, 0x84, 0x56, 0x1e, 0x67, 0xaf, 0x6b, 0x43, 0xac, 0x17, 0xaf, 0x16, 0xc0, 0x52, 0x99, 0x49, 0x5b, 0x87, 0x73, 0x7e, 0xb5, 0x43, 0xda, 0x6b, 0x1d, 0x0f, 0x2d, 0x55}}}, +{{{0xe9, 0x58, 0x1f, 0xff, 0x84, 0x3f, 0x93, 0x1c, 0xcb, 0xe1, 0x30, 0x69, 0xa5, 0x75, 0x19, 0x7e, 0x14, 0x5f, 0xf8, 0xfc, 0x09, 0xdd, 0xa8, 0x78, 0x9d, 0xca, 0x59, 0x8b, 0xd1, 0x30, 0x01, 0x13}} , + {{0xff, 0x76, 0x03, 0xc5, 0x4b, 0x89, 0x99, 0x70, 0x00, 0x59, 0x70, 0x9c, 0xd5, 0xd9, 0x11, 0x89, 0x5a, 0x46, 0xfe, 0xef, 0xdc, 0xd9, 0x55, 0x2b, 0x45, 0xa7, 0xb0, 0x2d, 0xfb, 0x24, 0xc2, 0x29}}}, +{{{0x38, 0x06, 0xf8, 0x0b, 0xac, 0x82, 0xc4, 0x97, 0x2b, 0x90, 0xe0, 0xf7, 0xa8, 0xab, 0x6c, 0x08, 0x80, 0x66, 0x90, 0x46, 0xf7, 0x26, 0x2d, 0xf8, 0xf1, 0xc4, 0x6b, 0x4a, 0x82, 0x98, 0x8e, 0x37}} , + {{0x8e, 0xb4, 0xee, 0xb8, 0xd4, 0x3f, 0xb2, 0x1b, 0xe0, 0x0a, 0x3d, 0x75, 0x34, 0x28, 0xa2, 0x8e, 0xc4, 0x92, 0x7b, 0xfe, 0x60, 0x6e, 0x6d, 0xb8, 0x31, 0x1d, 0x62, 0x0d, 0x78, 0x14, 0x42, 0x11}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x5e, 0xa8, 0xd8, 0x04, 0x9b, 0x73, 0xc9, 0xc9, 0xdc, 0x0d, 0x73, 0xbf, 0x0a, 0x0a, 0x73, 0xff, 0x18, 0x1f, 0x9c, 0x51, 0xaa, 0xc6, 0xf1, 0x83, 0x25, 0xfd, 0xab, 0xa3, 0x11, 0xd3, 0x01, 0x24}} , + {{0x4d, 0xe3, 0x7e, 0x38, 0x62, 0x5e, 0x64, 0xbb, 0x2b, 0x53, 0xb5, 0x03, 0x68, 0xc4, 0xf2, 0x2b, 0x5a, 0x03, 0x32, 0x99, 0x4a, 0x41, 0x9a, 0xe1, 0x1a, 0xae, 0x8c, 0x48, 0xf3, 0x24, 0x32, 0x65}}}, +{{{0xe8, 0xdd, 0xad, 0x3a, 0x8c, 0xea, 0xf4, 0xb3, 0xb2, 0xe5, 0x73, 0xf2, 0xed, 0x8b, 0xbf, 0xed, 0xb1, 0x0c, 0x0c, 0xfb, 0x2b, 0xf1, 0x01, 0x48, 0xe8, 0x26, 0x03, 0x8e, 0x27, 0x4d, 0x96, 0x72}} , + {{0xc8, 0x09, 0x3b, 0x60, 0xc9, 0x26, 0x4d, 0x7c, 0xf2, 0x9c, 0xd4, 0xa1, 0x3b, 0x26, 0xc2, 0x04, 0x33, 0x44, 0x76, 0x3c, 0x02, 0xbb, 0x11, 0x42, 0x0c, 0x22, 0xb7, 0xc6, 0xe1, 0xac, 0xb4, 0x0e}}}, +{{{0x6f, 0x85, 0xe7, 0xef, 0xde, 0x67, 0x30, 0xfc, 0xbf, 0x5a, 0xe0, 0x7b, 0x7a, 0x2a, 0x54, 0x6b, 0x5d, 0x62, 0x85, 0xa1, 0xf8, 0x16, 0x88, 0xec, 0x61, 0xb9, 0x96, 0xb5, 0xef, 0x2d, 0x43, 0x4d}} , + {{0x7c, 0x31, 0x33, 0xcc, 0xe4, 0xcf, 0x6c, 0xff, 0x80, 0x47, 0x77, 0xd1, 0xd8, 0xe9, 0x69, 0x97, 0x98, 0x7f, 0x20, 0x57, 0x1d, 0x1d, 0x4f, 0x08, 0x27, 0xc8, 0x35, 0x57, 0x40, 0xc6, 0x21, 0x0c}}}, +{{{0xd2, 0x8e, 0x9b, 0xfa, 0x42, 0x8e, 0xdf, 0x8f, 0xc7, 0x86, 0xf9, 0xa4, 0xca, 0x70, 0x00, 0x9d, 0x21, 0xbf, 0xec, 0x57, 0x62, 0x30, 0x58, 0x8c, 0x0d, 0x35, 0xdb, 0x5d, 0x8b, 0x6a, 0xa0, 0x5a}} , + {{0xc1, 0x58, 0x7c, 0x0d, 0x20, 0xdd, 0x11, 0x26, 0x5f, 0x89, 0x3b, 0x97, 0x58, 0xf8, 0x8b, 0xe3, 0xdf, 0x32, 0xe2, 0xfc, 0xd8, 0x67, 0xf2, 0xa5, 0x37, 0x1e, 0x6d, 0xec, 0x7c, 0x27, 0x20, 0x79}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xd0, 0xe9, 0xc0, 0xfa, 0x95, 0x45, 0x23, 0x96, 0xf1, 0x2c, 0x79, 0x25, 0x14, 0xce, 0x40, 0x14, 0x44, 0x2c, 0x36, 0x50, 0xd9, 0x63, 0x56, 0xb7, 0x56, 0x3b, 0x9e, 0xa7, 0xef, 0x89, 0xbb, 0x0e}} , + {{0xce, 0x7f, 0xdc, 0x0a, 0xcc, 0x82, 0x1c, 0x0a, 0x78, 0x71, 0xe8, 0x74, 0x8d, 0x01, 0x30, 0x0f, 0xa7, 0x11, 0x4c, 0xdf, 0x38, 0xd7, 0xa7, 0x0d, 0xf8, 0x48, 0x52, 0x00, 0x80, 0x7b, 0x5f, 0x0e}}}, +{{{0x25, 0x83, 0xe6, 0x94, 0x7b, 0x81, 0xb2, 0x91, 0xae, 0x0e, 0x05, 0xc9, 0xa3, 0x68, 0x2d, 0xd9, 0x88, 0x25, 0x19, 0x2a, 0x61, 0x61, 0x21, 0x97, 0x15, 0xa1, 0x35, 0xa5, 0x46, 0xc8, 0xa2, 0x0e}} , + {{0x1b, 0x03, 0x0d, 0x8b, 0x5a, 0x1b, 0x97, 0x4b, 0xf2, 0x16, 0x31, 0x3d, 0x1f, 0x33, 0xa0, 0x50, 0x3a, 0x18, 0xbe, 0x13, 0xa1, 0x76, 0xc1, 0xba, 0x1b, 0xf1, 0x05, 0x7b, 0x33, 0xa8, 0x82, 0x3b}}}, +{{{0xba, 0x36, 0x7b, 0x6d, 0xa9, 0xea, 0x14, 0x12, 0xc5, 0xfa, 0x91, 0x00, 0xba, 0x9b, 0x99, 0xcc, 0x56, 0x02, 0xe9, 0xa0, 0x26, 0x40, 0x66, 0x8c, 0xc4, 0xf8, 0x85, 0x33, 0x68, 0xe7, 0x03, 0x20}} , + {{0x50, 0x5b, 0xff, 0xa9, 0xb2, 0xf1, 0xf1, 0x78, 0xcf, 0x14, 0xa4, 0xa9, 0xfc, 0x09, 0x46, 0x94, 0x54, 0x65, 0x0d, 0x9c, 0x5f, 0x72, 0x21, 0xe2, 0x97, 0xa5, 0x2d, 0x81, 0xce, 0x4a, 0x5f, 0x79}}}, +{{{0x3d, 0x5f, 0x5c, 0xd2, 0xbc, 0x7d, 0x77, 0x0e, 0x2a, 0x6d, 0x22, 0x45, 0x84, 0x06, 0xc4, 0xdd, 0xc6, 0xa6, 0xc6, 0xd7, 0x49, 0xad, 0x6d, 0x87, 0x91, 0x0e, 0x3a, 0x67, 0x1d, 0x2c, 0x1d, 0x56}} , + {{0xfe, 0x7a, 0x74, 0xcf, 0xd4, 0xd2, 0xe5, 0x19, 0xde, 0xd0, 0xdb, 0x70, 0x23, 0x69, 0xe6, 0x6d, 0xec, 0xec, 0xcc, 0x09, 0x33, 0x6a, 0x77, 0xdc, 0x6b, 0x22, 0x76, 0x5d, 0x92, 0x09, 0xac, 0x2d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x23, 0x15, 0x17, 0xeb, 0xd3, 0xdb, 0x12, 0x5e, 0x01, 0xf0, 0x91, 0xab, 0x2c, 0x41, 0xce, 0xac, 0xed, 0x1b, 0x4b, 0x2d, 0xbc, 0xdb, 0x17, 0x66, 0x89, 0x46, 0xad, 0x4b, 0x1e, 0x6f, 0x0b, 0x14}} , + {{0x11, 0xce, 0xbf, 0xb6, 0x77, 0x2d, 0x48, 0x22, 0x18, 0x4f, 0xa3, 0x5d, 0x4a, 0xb0, 0x70, 0x12, 0x3e, 0x54, 0xd7, 0xd8, 0x0e, 0x2b, 0x27, 0xdc, 0x53, 0xff, 0xca, 0x8c, 0x59, 0xb3, 0x4e, 0x44}}}, +{{{0x07, 0x76, 0x61, 0x0f, 0x66, 0xb2, 0x21, 0x39, 0x7e, 0xc0, 0xec, 0x45, 0x28, 0x82, 0xa1, 0x29, 0x32, 0x44, 0x35, 0x13, 0x5e, 0x61, 0x5e, 0x54, 0xcb, 0x7c, 0xef, 0xf6, 0x41, 0xcf, 0x9f, 0x0a}} , + {{0xdd, 0xf9, 0xda, 0x84, 0xc3, 0xe6, 0x8a, 0x9f, 0x24, 0xd2, 0x96, 0x5d, 0x39, 0x6f, 0x58, 0x8c, 0xc1, 0x56, 0x93, 0xab, 0xb5, 0x79, 0x3b, 0xd2, 0xa8, 0x73, 0x16, 0xed, 0xfa, 0xb4, 0x2f, 0x73}}}, +{{{0x8b, 0xb1, 0x95, 0xe5, 0x92, 0x50, 0x35, 0x11, 0x76, 0xac, 0xf4, 0x4d, 0x24, 0xc3, 0x32, 0xe6, 0xeb, 0xfe, 0x2c, 0x87, 0xc4, 0xf1, 0x56, 0xc4, 0x75, 0x24, 0x7a, 0x56, 0x85, 0x5a, 0x3a, 0x13}} , + {{0x0d, 0x16, 0xac, 0x3c, 0x4a, 0x58, 0x86, 0x3a, 0x46, 0x7f, 0x6c, 0xa3, 0x52, 0x6e, 0x37, 0xe4, 0x96, 0x9c, 0xe9, 0x5c, 0x66, 0x41, 0x67, 0xe4, 0xfb, 0x79, 0x0c, 0x05, 0xf6, 0x64, 0xd5, 0x7c}}}, +{{{0x28, 0xc1, 0xe1, 0x54, 0x73, 0xf2, 0xbf, 0x76, 0x74, 0x19, 0x19, 0x1b, 0xe4, 0xb9, 0xa8, 0x46, 0x65, 0x73, 0xf3, 0x77, 0x9b, 0x29, 0x74, 0x5b, 0xc6, 0x89, 0x6c, 0x2c, 0x7c, 0xf8, 0xb3, 0x0f}} , + {{0xf7, 0xd5, 0xe9, 0x74, 0x5d, 0xb8, 0x25, 0x16, 0xb5, 0x30, 0xbc, 0x84, 0xc5, 0xf0, 0xad, 0xca, 0x12, 0x28, 0xbc, 0x9d, 0xd4, 0xfa, 0x82, 0xe6, 0xe3, 0xbf, 0xa2, 0x15, 0x2c, 0xd4, 0x34, 0x10}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x61, 0xb1, 0x46, 0xba, 0x0e, 0x31, 0xa5, 0x67, 0x6c, 0x7f, 0xd6, 0xd9, 0x27, 0x85, 0x0f, 0x79, 0x14, 0xc8, 0x6c, 0x2f, 0x5f, 0x5b, 0x9c, 0x35, 0x3d, 0x38, 0x86, 0x77, 0x65, 0x55, 0x6a, 0x7b}} , + {{0xd3, 0xb0, 0x3a, 0x66, 0x60, 0x1b, 0x43, 0xf1, 0x26, 0x58, 0x99, 0x09, 0x8f, 0x2d, 0xa3, 0x14, 0x71, 0x85, 0xdb, 0xed, 0xf6, 0x26, 0xd5, 0x61, 0x9a, 0x73, 0xac, 0x0e, 0xea, 0xac, 0xb7, 0x0c}}}, +{{{0x5e, 0xf4, 0xe5, 0x17, 0x0e, 0x10, 0x9f, 0xe7, 0x43, 0x5f, 0x67, 0x5c, 0xac, 0x4b, 0xe5, 0x14, 0x41, 0xd2, 0xbf, 0x48, 0xf5, 0x14, 0xb0, 0x71, 0xc6, 0x61, 0xc1, 0xb2, 0x70, 0x58, 0xd2, 0x5a}} , + {{0x2d, 0xba, 0x16, 0x07, 0x92, 0x94, 0xdc, 0xbd, 0x50, 0x2b, 0xc9, 0x7f, 0x42, 0x00, 0xba, 0x61, 0xed, 0xf8, 0x43, 0xed, 0xf5, 0xf9, 0x40, 0x60, 0xb2, 0xb0, 0x82, 0xcb, 0xed, 0x75, 0xc7, 0x65}}}, +{{{0x80, 0xba, 0x0d, 0x09, 0x40, 0xa7, 0x39, 0xa6, 0x67, 0x34, 0x7e, 0x66, 0xbe, 0x56, 0xfb, 0x53, 0x78, 0xc4, 0x46, 0xe8, 0xed, 0x68, 0x6c, 0x7f, 0xce, 0xe8, 0x9f, 0xce, 0xa2, 0x64, 0x58, 0x53}} , + {{0xe8, 0xc1, 0xa9, 0xc2, 0x7b, 0x59, 0x21, 0x33, 0xe2, 0x43, 0x73, 0x2b, 0xac, 0x2d, 0xc1, 0x89, 0x3b, 0x15, 0xe2, 0xd5, 0xc0, 0x97, 0x8a, 0xfd, 0x6f, 0x36, 0x33, 0xb7, 0xb9, 0xc3, 0x88, 0x09}}}, +{{{0xd0, 0xb6, 0x56, 0x30, 0x5c, 0xae, 0xb3, 0x75, 0x44, 0xa4, 0x83, 0x51, 0x6e, 0x01, 0x65, 0xef, 0x45, 0x76, 0xe6, 0xf5, 0xa2, 0x0d, 0xd4, 0x16, 0x3b, 0x58, 0x2f, 0xf2, 0x2f, 0x36, 0x18, 0x3f}} , + {{0xfd, 0x2f, 0xe0, 0x9b, 0x1e, 0x8c, 0xc5, 0x18, 0xa9, 0xca, 0xd4, 0x2b, 0x35, 0xb6, 0x95, 0x0a, 0x9f, 0x7e, 0xfb, 0xc4, 0xef, 0x88, 0x7b, 0x23, 0x43, 0xec, 0x2f, 0x0d, 0x0f, 0x7a, 0xfc, 0x5c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x8d, 0xd2, 0xda, 0xc7, 0x44, 0xd6, 0x7a, 0xdb, 0x26, 0x7d, 0x1d, 0xb8, 0xe1, 0xde, 0x9d, 0x7a, 0x7d, 0x17, 0x7e, 0x1c, 0x37, 0x04, 0x8d, 0x2d, 0x7c, 0x5e, 0x18, 0x38, 0x1e, 0xaf, 0xc7, 0x1b}} , + {{0x33, 0x48, 0x31, 0x00, 0x59, 0xf6, 0xf2, 0xca, 0x0f, 0x27, 0x1b, 0x63, 0x12, 0x7e, 0x02, 0x1d, 0x49, 0xc0, 0x5d, 0x79, 0x87, 0xef, 0x5e, 0x7a, 0x2f, 0x1f, 0x66, 0x55, 0xd8, 0x09, 0xd9, 0x61}}}, +{{{0x54, 0x83, 0x02, 0x18, 0x82, 0x93, 0x99, 0x07, 0xd0, 0xa7, 0xda, 0xd8, 0x75, 0x89, 0xfa, 0xf2, 0xd9, 0xa3, 0xb8, 0x6b, 0x5a, 0x35, 0x28, 0xd2, 0x6b, 0x59, 0xc2, 0xf8, 0x45, 0xe2, 0xbc, 0x06}} , + {{0x65, 0xc0, 0xa3, 0x88, 0x51, 0x95, 0xfc, 0x96, 0x94, 0x78, 0xe8, 0x0d, 0x8b, 0x41, 0xc9, 0xc2, 0x58, 0x48, 0x75, 0x10, 0x2f, 0xcd, 0x2a, 0xc9, 0xa0, 0x6d, 0x0f, 0xdd, 0x9c, 0x98, 0x26, 0x3d}}}, +{{{0x2f, 0x66, 0x29, 0x1b, 0x04, 0x89, 0xbd, 0x7e, 0xee, 0x6e, 0xdd, 0xb7, 0x0e, 0xef, 0xb0, 0x0c, 0xb4, 0xfc, 0x7f, 0xc2, 0xc9, 0x3a, 0x3c, 0x64, 0xef, 0x45, 0x44, 0xaf, 0x8a, 0x90, 0x65, 0x76}} , + {{0xa1, 0x4c, 0x70, 0x4b, 0x0e, 0xa0, 0x83, 0x70, 0x13, 0xa4, 0xaf, 0xb8, 0x38, 0x19, 0x22, 0x65, 0x09, 0xb4, 0x02, 0x4f, 0x06, 0xf8, 0x17, 0xce, 0x46, 0x45, 0xda, 0x50, 0x7c, 0x8a, 0xd1, 0x4e}}}, +{{{0xf7, 0xd4, 0x16, 0x6c, 0x4e, 0x95, 0x9d, 0x5d, 0x0f, 0x91, 0x2b, 0x52, 0xfe, 0x5c, 0x34, 0xe5, 0x30, 0xe6, 0xa4, 0x3b, 0xf3, 0xf3, 0x34, 0x08, 0xa9, 0x4a, 0xa0, 0xb5, 0x6e, 0xb3, 0x09, 0x0a}} , + {{0x26, 0xd9, 0x5e, 0xa3, 0x0f, 0xeb, 0xa2, 0xf3, 0x20, 0x3b, 0x37, 0xd4, 0xe4, 0x9e, 0xce, 0x06, 0x3d, 0x53, 0xed, 0xae, 0x2b, 0xeb, 0xb6, 0x24, 0x0a, 0x11, 0xa3, 0x0f, 0xd6, 0x7f, 0xa4, 0x3a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xdb, 0x9f, 0x2c, 0xfc, 0xd6, 0xb2, 0x1e, 0x2e, 0x52, 0x7a, 0x06, 0x87, 0x2d, 0x86, 0x72, 0x2b, 0x6d, 0x90, 0x77, 0x46, 0x43, 0xb5, 0x7a, 0xf8, 0x60, 0x7d, 0x91, 0x60, 0x5b, 0x9d, 0x9e, 0x07}} , + {{0x97, 0x87, 0xc7, 0x04, 0x1c, 0x38, 0x01, 0x39, 0x58, 0xc7, 0x85, 0xa3, 0xfc, 0x64, 0x00, 0x64, 0x25, 0xa2, 0xbf, 0x50, 0x94, 0xca, 0x26, 0x31, 0x45, 0x0a, 0x24, 0xd2, 0x51, 0x29, 0x51, 0x16}}}, +{{{0x4d, 0x4a, 0xd7, 0x98, 0x71, 0x57, 0xac, 0x7d, 0x8b, 0x37, 0xbd, 0x63, 0xff, 0x87, 0xb1, 0x49, 0x95, 0x20, 0x7c, 0xcf, 0x7c, 0x59, 0xc4, 0x91, 0x9c, 0xef, 0xd0, 0xdb, 0x60, 0x09, 0x9d, 0x46}} , + {{0xcb, 0x78, 0x94, 0x90, 0xe4, 0x45, 0xb3, 0xf6, 0xd9, 0xf6, 0x57, 0x74, 0xd5, 0xf8, 0x83, 0x4f, 0x39, 0xc9, 0xbd, 0x88, 0xc2, 0x57, 0x21, 0x1f, 0x24, 0x32, 0x68, 0xf8, 0xc7, 0x21, 0x5f, 0x0b}}}, +{{{0x2a, 0x36, 0x68, 0xfc, 0x5f, 0xb6, 0x4f, 0xa5, 0xe3, 0x9d, 0x24, 0x2f, 0xc0, 0x93, 0x61, 0xcf, 0xf8, 0x0a, 0xed, 0xe1, 0xdb, 0x27, 0xec, 0x0e, 0x14, 0x32, 0x5f, 0x8e, 0xa1, 0x62, 0x41, 0x16}} , + {{0x95, 0x21, 0x01, 0xce, 0x95, 0x5b, 0x0e, 0x57, 0xc7, 0xb9, 0x62, 0xb5, 0x28, 0xca, 0x11, 0xec, 0xb4, 0x46, 0x06, 0x73, 0x26, 0xff, 0xfb, 0x66, 0x7d, 0xee, 0x5f, 0xb2, 0x56, 0xfd, 0x2a, 0x08}}}, +{{{0x92, 0x67, 0x77, 0x56, 0xa1, 0xff, 0xc4, 0xc5, 0x95, 0xf0, 0xe3, 0x3a, 0x0a, 0xca, 0x94, 0x4d, 0x9e, 0x7e, 0x3d, 0xb9, 0x6e, 0xb6, 0xb0, 0xce, 0xa4, 0x30, 0x89, 0x99, 0xe9, 0xad, 0x11, 0x59}} , + {{0xf6, 0x48, 0x95, 0xa1, 0x6f, 0x5f, 0xb7, 0xa5, 0xbb, 0x30, 0x00, 0x1c, 0xd2, 0x8a, 0xd6, 0x25, 0x26, 0x1b, 0xb2, 0x0d, 0x37, 0x6a, 0x05, 0xf4, 0x9d, 0x3e, 0x17, 0x2a, 0x43, 0xd2, 0x3a, 0x06}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x32, 0x99, 0x93, 0xd1, 0x9a, 0x72, 0xf3, 0xa9, 0x16, 0xbd, 0xb4, 0x4c, 0xdd, 0xf9, 0xd4, 0xb2, 0x64, 0x9a, 0xd3, 0x05, 0xe4, 0xa3, 0x73, 0x1c, 0xcb, 0x7e, 0x57, 0x67, 0xff, 0x04, 0xb3, 0x10}} , + {{0xb9, 0x4b, 0xa4, 0xad, 0xd0, 0x6d, 0x61, 0x23, 0xb4, 0xaf, 0x34, 0xa9, 0xaa, 0x65, 0xec, 0xd9, 0x69, 0xe3, 0x85, 0xcd, 0xcc, 0xe7, 0xb0, 0x9b, 0x41, 0xc1, 0x1c, 0xf9, 0xa0, 0xfa, 0xb7, 0x13}}}, +{{{0x04, 0xfd, 0x88, 0x3c, 0x0c, 0xd0, 0x09, 0x52, 0x51, 0x4f, 0x06, 0x19, 0xcc, 0xc3, 0xbb, 0xde, 0x80, 0xc5, 0x33, 0xbc, 0xf9, 0xf3, 0x17, 0x36, 0xdd, 0xc6, 0xde, 0xe8, 0x9b, 0x5d, 0x79, 0x1b}} , + {{0x65, 0x0a, 0xbe, 0x51, 0x57, 0xad, 0x50, 0x79, 0x08, 0x71, 0x9b, 0x07, 0x95, 0x8f, 0xfb, 0xae, 0x4b, 0x38, 0xba, 0xcf, 0x53, 0x2a, 0x86, 0x1e, 0xc0, 0x50, 0x5c, 0x67, 0x1b, 0xf6, 0x87, 0x6c}}}, +{{{0x4f, 0x00, 0xb2, 0x66, 0x55, 0xed, 0x4a, 0xed, 0x8d, 0xe1, 0x66, 0x18, 0xb2, 0x14, 0x74, 0x8d, 0xfd, 0x1a, 0x36, 0x0f, 0x26, 0x5c, 0x8b, 0x89, 0xf3, 0xab, 0xf2, 0xf3, 0x24, 0x67, 0xfd, 0x70}} , + {{0xfd, 0x4e, 0x2a, 0xc1, 0x3a, 0xca, 0x8f, 0x00, 0xd8, 0xec, 0x74, 0x67, 0xef, 0x61, 0xe0, 0x28, 0xd0, 0x96, 0xf4, 0x48, 0xde, 0x81, 0xe3, 0xef, 0xdc, 0xaa, 0x7d, 0xf3, 0xb6, 0x55, 0xa6, 0x65}}}, +{{{0xeb, 0xcb, 0xc5, 0x70, 0x91, 0x31, 0x10, 0x93, 0x0d, 0xc8, 0xd0, 0xef, 0x62, 0xe8, 0x6f, 0x82, 0xe3, 0x69, 0x3d, 0x91, 0x7f, 0x31, 0xe1, 0x26, 0x35, 0x3c, 0x4a, 0x2f, 0xab, 0xc4, 0x9a, 0x5e}} , + {{0xab, 0x1b, 0xb5, 0xe5, 0x2b, 0xc3, 0x0e, 0x29, 0xb0, 0xd0, 0x73, 0xe6, 0x4f, 0x64, 0xf2, 0xbc, 0xe4, 0xe4, 0xe1, 0x9a, 0x52, 0x33, 0x2f, 0xbd, 0xcc, 0x03, 0xee, 0x8a, 0xfa, 0x00, 0x5f, 0x50}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xf6, 0xdb, 0x0d, 0x22, 0x3d, 0xb5, 0x14, 0x75, 0x31, 0xf0, 0x81, 0xe2, 0xb9, 0x37, 0xa2, 0xa9, 0x84, 0x11, 0x9a, 0x07, 0xb5, 0x53, 0x89, 0x78, 0xa9, 0x30, 0x27, 0xa1, 0xf1, 0x4e, 0x5c, 0x2e}} , + {{0x8b, 0x00, 0x54, 0xfb, 0x4d, 0xdc, 0xcb, 0x17, 0x35, 0x40, 0xff, 0xb7, 0x8c, 0xfe, 0x4a, 0xe4, 0x4e, 0x99, 0x4e, 0xa8, 0x74, 0x54, 0x5d, 0x5c, 0x96, 0xa3, 0x12, 0x55, 0x36, 0x31, 0x17, 0x5c}}}, +{{{0xce, 0x24, 0xef, 0x7b, 0x86, 0xf2, 0x0f, 0x77, 0xe8, 0x5c, 0x7d, 0x87, 0x38, 0x2d, 0xef, 0xaf, 0xf2, 0x8c, 0x72, 0x2e, 0xeb, 0xb6, 0x55, 0x4b, 0x6e, 0xf1, 0x4e, 0x8a, 0x0e, 0x9a, 0x6c, 0x4c}} , + {{0x25, 0xea, 0x86, 0xc2, 0xd1, 0x4f, 0xb7, 0x3e, 0xa8, 0x5c, 0x8d, 0x66, 0x81, 0x25, 0xed, 0xc5, 0x4c, 0x05, 0xb9, 0xd8, 0xd6, 0x70, 0xbe, 0x73, 0x82, 0xe8, 0xa1, 0xe5, 0x1e, 0x71, 0xd5, 0x26}}}, +{{{0x4e, 0x6d, 0xc3, 0xa7, 0x4f, 0x22, 0x45, 0x26, 0xa2, 0x7e, 0x16, 0xf7, 0xf7, 0x63, 0xdc, 0x86, 0x01, 0x2a, 0x71, 0x38, 0x5c, 0x33, 0xc3, 0xce, 0x30, 0xff, 0xf9, 0x2c, 0x91, 0x71, 0x8a, 0x72}} , + {{0x8c, 0x44, 0x09, 0x28, 0xd5, 0x23, 0xc9, 0x8f, 0xf3, 0x84, 0x45, 0xc6, 0x9a, 0x5e, 0xff, 0xd2, 0xc7, 0x57, 0x93, 0xa3, 0xc1, 0x69, 0xdd, 0x62, 0x0f, 0xda, 0x5c, 0x30, 0x59, 0x5d, 0xe9, 0x4c}}}, +{{{0x92, 0x7e, 0x50, 0x27, 0x72, 0xd7, 0x0c, 0xd6, 0x69, 0x96, 0x81, 0x35, 0x84, 0x94, 0x35, 0x8b, 0x6c, 0xaa, 0x62, 0x86, 0x6e, 0x1c, 0x15, 0xf3, 0x6c, 0xb3, 0xff, 0x65, 0x1b, 0xa2, 0x9b, 0x59}} , + {{0xe2, 0xa9, 0x65, 0x88, 0xc4, 0x50, 0xfa, 0xbb, 0x3b, 0x6e, 0x5f, 0x44, 0x01, 0xca, 0x97, 0xd4, 0xdd, 0xf6, 0xcd, 0x3f, 0x3f, 0xe5, 0x97, 0x67, 0x2b, 0x8c, 0x66, 0x0f, 0x35, 0x9b, 0xf5, 0x07}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xf1, 0x59, 0x27, 0xd8, 0xdb, 0x5a, 0x11, 0x5e, 0x82, 0xf3, 0x38, 0xff, 0x1c, 0xed, 0xfe, 0x3f, 0x64, 0x54, 0x3f, 0x7f, 0xd1, 0x81, 0xed, 0xef, 0x65, 0xc5, 0xcb, 0xfd, 0xe1, 0x80, 0xcd, 0x11}} , + {{0xe0, 0xdb, 0x22, 0x28, 0xe6, 0xff, 0x61, 0x9d, 0x41, 0x14, 0x2d, 0x3b, 0x26, 0x22, 0xdf, 0xf1, 0x34, 0x81, 0xe9, 0x45, 0xee, 0x0f, 0x98, 0x8b, 0xa6, 0x3f, 0xef, 0xf7, 0x43, 0x19, 0xf1, 0x43}}}, +{{{0xee, 0xf3, 0x00, 0xa1, 0x50, 0xde, 0xc0, 0xb6, 0x01, 0xe3, 0x8c, 0x3c, 0x4d, 0x31, 0xd2, 0xb0, 0x58, 0xcd, 0xed, 0x10, 0x4a, 0x7a, 0xef, 0x80, 0xa9, 0x19, 0x32, 0xf3, 0xd8, 0x33, 0x8c, 0x06}} , + {{0xcb, 0x7d, 0x4f, 0xff, 0x30, 0xd8, 0x12, 0x3b, 0x39, 0x1c, 0x06, 0xf9, 0x4c, 0x34, 0x35, 0x71, 0xb5, 0x16, 0x94, 0x67, 0xdf, 0xee, 0x11, 0xde, 0xa4, 0x1d, 0x88, 0x93, 0x35, 0xa9, 0x32, 0x10}}}, +{{{0xe9, 0xc3, 0xbc, 0x7b, 0x5c, 0xfc, 0xb2, 0xf9, 0xc9, 0x2f, 0xe5, 0xba, 0x3a, 0x0b, 0xab, 0x64, 0x38, 0x6f, 0x5b, 0x4b, 0x93, 0xda, 0x64, 0xec, 0x4d, 0x3d, 0xa0, 0xf5, 0xbb, 0xba, 0x47, 0x48}} , + {{0x60, 0xbc, 0x45, 0x1f, 0x23, 0xa2, 0x3b, 0x70, 0x76, 0xe6, 0x97, 0x99, 0x4f, 0x77, 0x54, 0x67, 0x30, 0x9a, 0xe7, 0x66, 0xd6, 0xcd, 0x2e, 0x51, 0x24, 0x2c, 0x42, 0x4a, 0x11, 0xfe, 0x6f, 0x7e}}}, +{{{0x87, 0xc0, 0xb1, 0xf0, 0xa3, 0x6f, 0x0c, 0x93, 0xa9, 0x0a, 0x72, 0xef, 0x5c, 0xbe, 0x65, 0x35, 0xa7, 0x6a, 0x4e, 0x2c, 0xbf, 0x21, 0x23, 0xe8, 0x2f, 0x97, 0xc7, 0x3e, 0xc8, 0x17, 0xac, 0x1e}} , + {{0x7b, 0xef, 0x21, 0xe5, 0x40, 0xcc, 0x1e, 0xdc, 0xd6, 0xbd, 0x97, 0x7a, 0x7c, 0x75, 0x86, 0x7a, 0x25, 0x5a, 0x6e, 0x7c, 0xe5, 0x51, 0x3c, 0x1b, 0x5b, 0x82, 0x9a, 0x07, 0x60, 0xa1, 0x19, 0x04}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x96, 0x88, 0xa6, 0xab, 0x8f, 0xe3, 0x3a, 0x49, 0xf8, 0xfe, 0x34, 0xe7, 0x6a, 0xb2, 0xfe, 0x40, 0x26, 0x74, 0x57, 0x4c, 0xf6, 0xd4, 0x99, 0xce, 0x5d, 0x7b, 0x2f, 0x67, 0xd6, 0x5a, 0xe4, 0x4e}} , + {{0x5c, 0x82, 0xb3, 0xbd, 0x55, 0x25, 0xf6, 0x6a, 0x93, 0xa4, 0x02, 0xc6, 0x7d, 0x5c, 0xb1, 0x2b, 0x5b, 0xff, 0xfb, 0x56, 0xf8, 0x01, 0x41, 0x90, 0xc6, 0xb6, 0xac, 0x4f, 0xfe, 0xa7, 0x41, 0x70}}}, +{{{0xdb, 0xfa, 0x9b, 0x2c, 0xd4, 0x23, 0x67, 0x2c, 0x8a, 0x63, 0x6c, 0x07, 0x26, 0x48, 0x4f, 0xc2, 0x03, 0xd2, 0x53, 0x20, 0x28, 0xed, 0x65, 0x71, 0x47, 0xa9, 0x16, 0x16, 0x12, 0xbc, 0x28, 0x33}} , + {{0x39, 0xc0, 0xfa, 0xfa, 0xcd, 0x33, 0x43, 0xc7, 0x97, 0x76, 0x9b, 0x93, 0x91, 0x72, 0xeb, 0xc5, 0x18, 0x67, 0x4c, 0x11, 0xf0, 0xf4, 0xe5, 0x73, 0xb2, 0x5c, 0x1b, 0xc2, 0x26, 0x3f, 0xbf, 0x2b}}}, +{{{0x86, 0xe6, 0x8c, 0x1d, 0xdf, 0xca, 0xfc, 0xd5, 0xf8, 0x3a, 0xc3, 0x44, 0x72, 0xe6, 0x78, 0x9d, 0x2b, 0x97, 0xf8, 0x28, 0x45, 0xb4, 0x20, 0xc9, 0x2a, 0x8c, 0x67, 0xaa, 0x11, 0xc5, 0x5b, 0x2f}} , + {{0x17, 0x0f, 0x86, 0x52, 0xd7, 0x9d, 0xc3, 0x44, 0x51, 0x76, 0x32, 0x65, 0xb4, 0x37, 0x81, 0x99, 0x46, 0x37, 0x62, 0xed, 0xcf, 0x64, 0x9d, 0x72, 0x40, 0x7a, 0x4c, 0x0b, 0x76, 0x2a, 0xfb, 0x56}}}, +{{{0x33, 0xa7, 0x90, 0x7c, 0xc3, 0x6f, 0x17, 0xa5, 0xa0, 0x67, 0x72, 0x17, 0xea, 0x7e, 0x63, 0x14, 0x83, 0xde, 0xc1, 0x71, 0x2d, 0x41, 0x32, 0x7a, 0xf3, 0xd1, 0x2b, 0xd8, 0x2a, 0xa6, 0x46, 0x36}} , + {{0xac, 0xcc, 0x6b, 0x7c, 0xf9, 0xb8, 0x8b, 0x08, 0x5c, 0xd0, 0x7d, 0x8f, 0x73, 0xea, 0x20, 0xda, 0x86, 0xca, 0x00, 0xc7, 0xad, 0x73, 0x4d, 0xe9, 0xe8, 0xa9, 0xda, 0x1f, 0x03, 0x06, 0xdd, 0x24}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x9c, 0xb2, 0x61, 0x0a, 0x98, 0x2a, 0xa5, 0xd7, 0xee, 0xa9, 0xac, 0x65, 0xcb, 0x0a, 0x1e, 0xe2, 0xbe, 0xdc, 0x85, 0x59, 0x0f, 0x9c, 0xa6, 0x57, 0x34, 0xa5, 0x87, 0xeb, 0x7b, 0x1e, 0x0c, 0x3c}} , + {{0x2f, 0xbd, 0x84, 0x63, 0x0d, 0xb5, 0xa0, 0xf0, 0x4b, 0x9e, 0x93, 0xc6, 0x34, 0x9a, 0x34, 0xff, 0x73, 0x19, 0x2f, 0x6e, 0x54, 0x45, 0x2c, 0x92, 0x31, 0x76, 0x34, 0xf1, 0xb2, 0x26, 0xe8, 0x74}}}, +{{{0x0a, 0x67, 0x90, 0x6d, 0x0c, 0x4c, 0xcc, 0xc0, 0xe6, 0xbd, 0xa7, 0x5e, 0x55, 0x8c, 0xcd, 0x58, 0x9b, 0x11, 0xa2, 0xbb, 0x4b, 0xb1, 0x43, 0x04, 0x3c, 0x55, 0xed, 0x23, 0xfe, 0xcd, 0xb1, 0x53}} , + {{0x05, 0xfb, 0x75, 0xf5, 0x01, 0xaf, 0x38, 0x72, 0x58, 0xfc, 0x04, 0x29, 0x34, 0x7a, 0x67, 0xa2, 0x08, 0x50, 0x6e, 0xd0, 0x2b, 0x73, 0xd5, 0xb8, 0xe4, 0x30, 0x96, 0xad, 0x45, 0xdf, 0xa6, 0x5c}}}, +{{{0x0d, 0x88, 0x1a, 0x90, 0x7e, 0xdc, 0xd8, 0xfe, 0xc1, 0x2f, 0x5d, 0x67, 0xee, 0x67, 0x2f, 0xed, 0x6f, 0x55, 0x43, 0x5f, 0x87, 0x14, 0x35, 0x42, 0xd3, 0x75, 0xae, 0xd5, 0xd3, 0x85, 0x1a, 0x76}} , + {{0x87, 0xc8, 0xa0, 0x6e, 0xe1, 0xb0, 0xad, 0x6a, 0x4a, 0x34, 0x71, 0xed, 0x7c, 0xd6, 0x44, 0x03, 0x65, 0x4a, 0x5c, 0x5c, 0x04, 0xf5, 0x24, 0x3f, 0xb0, 0x16, 0x5e, 0x8c, 0xb2, 0xd2, 0xc5, 0x20}}}, +{{{0x98, 0x83, 0xc2, 0x37, 0xa0, 0x41, 0xa8, 0x48, 0x5c, 0x5f, 0xbf, 0xc8, 0xfa, 0x24, 0xe0, 0x59, 0x2c, 0xbd, 0xf6, 0x81, 0x7e, 0x88, 0xe6, 0xca, 0x04, 0xd8, 0x5d, 0x60, 0xbb, 0x74, 0xa7, 0x0b}} , + {{0x21, 0x13, 0x91, 0xbf, 0x77, 0x7a, 0x33, 0xbc, 0xe9, 0x07, 0x39, 0x0a, 0xdd, 0x7d, 0x06, 0x10, 0x9a, 0xee, 0x47, 0x73, 0x1b, 0x15, 0x5a, 0xfb, 0xcd, 0x4d, 0xd0, 0xd2, 0x3a, 0x01, 0xba, 0x54}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x48, 0xd5, 0x39, 0x4a, 0x0b, 0x20, 0x6a, 0x43, 0xa0, 0x07, 0x82, 0x5e, 0x49, 0x7c, 0xc9, 0x47, 0xf1, 0x7c, 0x37, 0xb9, 0x23, 0xef, 0x6b, 0x46, 0x45, 0x8c, 0x45, 0x76, 0xdf, 0x14, 0x6b, 0x6e}} , + {{0x42, 0xc9, 0xca, 0x29, 0x4c, 0x76, 0x37, 0xda, 0x8a, 0x2d, 0x7c, 0x3a, 0x58, 0xf2, 0x03, 0xb4, 0xb5, 0xb9, 0x1a, 0x13, 0x2d, 0xde, 0x5f, 0x6b, 0x9d, 0xba, 0x52, 0xc9, 0x5d, 0xb3, 0xf3, 0x30}}}, +{{{0x4c, 0x6f, 0xfe, 0x6b, 0x0c, 0x62, 0xd7, 0x48, 0x71, 0xef, 0xb1, 0x85, 0x79, 0xc0, 0xed, 0x24, 0xb1, 0x08, 0x93, 0x76, 0x8e, 0xf7, 0x38, 0x8e, 0xeb, 0xfe, 0x80, 0x40, 0xaf, 0x90, 0x64, 0x49}} , + {{0x4a, 0x88, 0xda, 0xc1, 0x98, 0x44, 0x3c, 0x53, 0x4e, 0xdb, 0x4b, 0xb9, 0x12, 0x5f, 0xcd, 0x08, 0x04, 0xef, 0x75, 0xe7, 0xb1, 0x3a, 0xe5, 0x07, 0xfa, 0xca, 0x65, 0x7b, 0x72, 0x10, 0x64, 0x7f}}}, +{{{0x3d, 0x81, 0xf0, 0xeb, 0x16, 0xfd, 0x58, 0x33, 0x8d, 0x7c, 0x1a, 0xfb, 0x20, 0x2c, 0x8a, 0xee, 0x90, 0xbb, 0x33, 0x6d, 0x45, 0xe9, 0x8e, 0x99, 0x85, 0xe1, 0x08, 0x1f, 0xc5, 0xf1, 0xb5, 0x46}} , + {{0xe4, 0xe7, 0x43, 0x4b, 0xa0, 0x3f, 0x2b, 0x06, 0xba, 0x17, 0xae, 0x3d, 0xe6, 0xce, 0xbd, 0xb8, 0xed, 0x74, 0x11, 0x35, 0xec, 0x96, 0xfe, 0x31, 0xe3, 0x0e, 0x7a, 0x4e, 0xc9, 0x1d, 0xcb, 0x20}}}, +{{{0xe0, 0x67, 0xe9, 0x7b, 0xdb, 0x96, 0x5c, 0xb0, 0x32, 0xd0, 0x59, 0x31, 0x90, 0xdc, 0x92, 0x97, 0xac, 0x09, 0x38, 0x31, 0x0f, 0x7e, 0xd6, 0x5d, 0xd0, 0x06, 0xb6, 0x1f, 0xea, 0xf0, 0x5b, 0x07}} , + {{0x81, 0x9f, 0xc7, 0xde, 0x6b, 0x41, 0x22, 0x35, 0x14, 0x67, 0x77, 0x3e, 0x90, 0x81, 0xb0, 0xd9, 0x85, 0x4c, 0xca, 0x9b, 0x3f, 0x04, 0x59, 0xd6, 0xaa, 0x17, 0xc3, 0x88, 0x34, 0x37, 0xba, 0x43}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x4c, 0xb6, 0x69, 0xc8, 0x81, 0x95, 0x94, 0x33, 0x92, 0x34, 0xe9, 0x3c, 0x84, 0x0d, 0x3d, 0x5a, 0x37, 0x9c, 0x22, 0xa0, 0xaa, 0x65, 0xce, 0xb4, 0xc2, 0x2d, 0x66, 0x67, 0x02, 0xff, 0x74, 0x10}} , + {{0x22, 0xb0, 0xd5, 0xe6, 0xc7, 0xef, 0xb1, 0xa7, 0x13, 0xda, 0x60, 0xb4, 0x80, 0xc1, 0x42, 0x7d, 0x10, 0x70, 0x97, 0x04, 0x4d, 0xda, 0x23, 0x89, 0xc2, 0x0e, 0x68, 0xcb, 0xde, 0xe0, 0x9b, 0x29}}}, +{{{0x33, 0xfe, 0x42, 0x2a, 0x36, 0x2b, 0x2e, 0x36, 0x64, 0x5c, 0x8b, 0xcc, 0x81, 0x6a, 0x15, 0x08, 0xa1, 0x27, 0xe8, 0x57, 0xe5, 0x78, 0x8e, 0xf2, 0x58, 0x19, 0x12, 0x42, 0xae, 0xc4, 0x63, 0x3e}} , + {{0x78, 0x96, 0x9c, 0xa7, 0xca, 0x80, 0xae, 0x02, 0x85, 0xb1, 0x7c, 0x04, 0x5c, 0xc1, 0x5b, 0x26, 0xc1, 0xba, 0xed, 0xa5, 0x59, 0x70, 0x85, 0x8c, 0x8c, 0xe8, 0x87, 0xac, 0x6a, 0x28, 0x99, 0x35}}}, +{{{0x9f, 0x04, 0x08, 0x28, 0xbe, 0x87, 0xda, 0x80, 0x28, 0x38, 0xde, 0x9f, 0xcd, 0xe4, 0xe3, 0x62, 0xfb, 0x2e, 0x46, 0x8d, 0x01, 0xb3, 0x06, 0x51, 0xd4, 0x19, 0x3b, 0x11, 0xfa, 0xe2, 0xad, 0x1e}} , + {{0xa0, 0x20, 0x99, 0x69, 0x0a, 0xae, 0xa3, 0x70, 0x4e, 0x64, 0x80, 0xb7, 0x85, 0x9c, 0x87, 0x54, 0x43, 0x43, 0x55, 0x80, 0x6d, 0x8d, 0x7c, 0xa9, 0x64, 0xca, 0x6c, 0x2e, 0x21, 0xd8, 0xc8, 0x6c}}}, +{{{0x91, 0x4a, 0x07, 0xad, 0x08, 0x75, 0xc1, 0x4f, 0xa4, 0xb2, 0xc3, 0x6f, 0x46, 0x3e, 0xb1, 0xce, 0x52, 0xab, 0x67, 0x09, 0x54, 0x48, 0x6b, 0x6c, 0xd7, 0x1d, 0x71, 0x76, 0xcb, 0xff, 0xdd, 0x31}} , + {{0x36, 0x88, 0xfa, 0xfd, 0xf0, 0x36, 0x6f, 0x07, 0x74, 0x88, 0x50, 0xd0, 0x95, 0x38, 0x4a, 0x48, 0x2e, 0x07, 0x64, 0x97, 0x11, 0x76, 0x01, 0x1a, 0x27, 0x4d, 0x8e, 0x25, 0x9a, 0x9b, 0x1c, 0x22}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xbe, 0x57, 0xbd, 0x0e, 0x0f, 0xac, 0x5e, 0x76, 0xa3, 0x71, 0xad, 0x2b, 0x10, 0x45, 0x02, 0xec, 0x59, 0xd5, 0x5d, 0xa9, 0x44, 0xcc, 0x25, 0x4c, 0xb3, 0x3c, 0x5b, 0x69, 0x07, 0x55, 0x26, 0x6b}} , + {{0x30, 0x6b, 0xd4, 0xa7, 0x51, 0x29, 0xe3, 0xf9, 0x7a, 0x75, 0x2a, 0x82, 0x2f, 0xd6, 0x1d, 0x99, 0x2b, 0x80, 0xd5, 0x67, 0x1e, 0x15, 0x9d, 0xca, 0xfd, 0xeb, 0xac, 0x97, 0x35, 0x09, 0x7f, 0x3f}}}, +{{{0x35, 0x0d, 0x34, 0x0a, 0xb8, 0x67, 0x56, 0x29, 0x20, 0xf3, 0x19, 0x5f, 0xe2, 0x83, 0x42, 0x73, 0x53, 0xa8, 0xc5, 0x02, 0x19, 0x33, 0xb4, 0x64, 0xbd, 0xc3, 0x87, 0x8c, 0xd7, 0x76, 0xed, 0x25}} , + {{0x47, 0x39, 0x37, 0x76, 0x0d, 0x1d, 0x0c, 0xf5, 0x5a, 0x6d, 0x43, 0x88, 0x99, 0x15, 0xb4, 0x52, 0x0f, 0x2a, 0xb3, 0xb0, 0x3f, 0xa6, 0xb3, 0x26, 0xb3, 0xc7, 0x45, 0xf5, 0x92, 0x5f, 0x9b, 0x17}}}, +{{{0x9d, 0x23, 0xbd, 0x15, 0xfe, 0x52, 0x52, 0x15, 0x26, 0x79, 0x86, 0xba, 0x06, 0x56, 0x66, 0xbb, 0x8c, 0x2e, 0x10, 0x11, 0xd5, 0x4a, 0x18, 0x52, 0xda, 0x84, 0x44, 0xf0, 0x3e, 0xe9, 0x8c, 0x35}} , + {{0xad, 0xa0, 0x41, 0xec, 0xc8, 0x4d, 0xb9, 0xd2, 0x6e, 0x96, 0x4e, 0x5b, 0xc5, 0xc2, 0xa0, 0x1b, 0xcf, 0x0c, 0xbf, 0x17, 0x66, 0x57, 0xc1, 0x17, 0x90, 0x45, 0x71, 0xc2, 0xe1, 0x24, 0xeb, 0x27}}}, +{{{0x2c, 0xb9, 0x42, 0xa4, 0xaf, 0x3b, 0x42, 0x0e, 0xc2, 0x0f, 0xf2, 0xea, 0x83, 0xaf, 0x9a, 0x13, 0x17, 0xb0, 0xbd, 0x89, 0x17, 0xe3, 0x72, 0xcb, 0x0e, 0x76, 0x7e, 0x41, 0x63, 0x04, 0x88, 0x71}} , + {{0x75, 0x78, 0x38, 0x86, 0x57, 0xdd, 0x9f, 0xee, 0x54, 0x70, 0x65, 0xbf, 0xf1, 0x2c, 0xe0, 0x39, 0x0d, 0xe3, 0x89, 0xfd, 0x8e, 0x93, 0x4f, 0x43, 0xdc, 0xd5, 0x5b, 0xde, 0xf9, 0x98, 0xe5, 0x7b}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xe7, 0x3b, 0x65, 0x11, 0xdf, 0xb2, 0xf2, 0x63, 0x94, 0x12, 0x6f, 0x5c, 0x9e, 0x77, 0xc1, 0xb6, 0xd8, 0xab, 0x58, 0x7a, 0x1d, 0x95, 0x73, 0xdd, 0xe7, 0xe3, 0x6f, 0xf2, 0x03, 0x1d, 0xdb, 0x76}} , + {{0xae, 0x06, 0x4e, 0x2c, 0x52, 0x1b, 0xbc, 0x5a, 0x5a, 0xa5, 0xbe, 0x27, 0xbd, 0xeb, 0xe1, 0x14, 0x17, 0x68, 0x26, 0x07, 0x03, 0xd1, 0x18, 0x0b, 0xdf, 0xf1, 0x06, 0x5c, 0xa6, 0x1b, 0xb9, 0x24}}}, +{{{0xc5, 0x66, 0x80, 0x13, 0x0e, 0x48, 0x8c, 0x87, 0x31, 0x84, 0xb4, 0x60, 0xed, 0xc5, 0xec, 0xb6, 0xc5, 0x05, 0x33, 0x5f, 0x2f, 0x7d, 0x40, 0xb6, 0x32, 0x1d, 0x38, 0x74, 0x1b, 0xf1, 0x09, 0x3d}} , + {{0xd4, 0x69, 0x82, 0xbc, 0x8d, 0xf8, 0x34, 0x36, 0x75, 0x55, 0x18, 0x55, 0x58, 0x3c, 0x79, 0xaf, 0x26, 0x80, 0xab, 0x9b, 0x95, 0x00, 0xf1, 0xcb, 0xda, 0xc1, 0x9f, 0xf6, 0x2f, 0xa2, 0xf4, 0x45}}}, +{{{0x17, 0xbe, 0xeb, 0x85, 0xed, 0x9e, 0xcd, 0x56, 0xf5, 0x17, 0x45, 0x42, 0xb4, 0x1f, 0x44, 0x4c, 0x05, 0x74, 0x15, 0x47, 0x00, 0xc6, 0x6a, 0x3d, 0x24, 0x09, 0x0d, 0x58, 0xb1, 0x42, 0xd7, 0x04}} , + {{0x8d, 0xbd, 0xa3, 0xc4, 0x06, 0x9b, 0x1f, 0x90, 0x58, 0x60, 0x74, 0xb2, 0x00, 0x3b, 0x3c, 0xd2, 0xda, 0x82, 0xbb, 0x10, 0x90, 0x69, 0x92, 0xa9, 0xb4, 0x30, 0x81, 0xe3, 0x7c, 0xa8, 0x89, 0x45}}}, +{{{0x3f, 0xdc, 0x05, 0xcb, 0x41, 0x3c, 0xc8, 0x23, 0x04, 0x2c, 0x38, 0x99, 0xe3, 0x68, 0x55, 0xf9, 0xd3, 0x32, 0xc7, 0xbf, 0xfa, 0xd4, 0x1b, 0x5d, 0xde, 0xdc, 0x10, 0x42, 0xc0, 0x42, 0xd9, 0x75}} , + {{0x2d, 0xab, 0x35, 0x4e, 0x87, 0xc4, 0x65, 0x97, 0x67, 0x24, 0xa4, 0x47, 0xad, 0x3f, 0x8e, 0xf3, 0xcb, 0x31, 0x17, 0x77, 0xc5, 0xe2, 0xd7, 0x8f, 0x3c, 0xc1, 0xcd, 0x56, 0x48, 0xc1, 0x6c, 0x69}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x14, 0xae, 0x5f, 0x88, 0x7b, 0xa5, 0x90, 0xdf, 0x10, 0xb2, 0x8b, 0x5e, 0x24, 0x17, 0xc3, 0xa3, 0xd4, 0x0f, 0x92, 0x61, 0x1a, 0x19, 0x5a, 0xad, 0x76, 0xbd, 0xd8, 0x1c, 0xdd, 0xe0, 0x12, 0x6d}} , + {{0x8e, 0xbd, 0x70, 0x8f, 0x02, 0xa3, 0x24, 0x4d, 0x5a, 0x67, 0xc4, 0xda, 0xf7, 0x20, 0x0f, 0x81, 0x5b, 0x7a, 0x05, 0x24, 0x67, 0x83, 0x0b, 0x2a, 0x80, 0xe7, 0xfd, 0x74, 0x4b, 0x9e, 0x5c, 0x0d}}}, +{{{0x94, 0xd5, 0x5f, 0x1f, 0xa2, 0xfb, 0xeb, 0xe1, 0x07, 0x34, 0xf8, 0x20, 0xad, 0x81, 0x30, 0x06, 0x2d, 0xa1, 0x81, 0x95, 0x36, 0xcf, 0x11, 0x0b, 0xaf, 0xc1, 0x2b, 0x9a, 0x6c, 0x55, 0xc1, 0x16}} , + {{0x36, 0x4f, 0xf1, 0x5e, 0x74, 0x35, 0x13, 0x28, 0xd7, 0x11, 0xcf, 0xb8, 0xde, 0x93, 0xb3, 0x05, 0xb8, 0xb5, 0x73, 0xe9, 0xeb, 0xad, 0x19, 0x1e, 0x89, 0x0f, 0x8b, 0x15, 0xd5, 0x8c, 0xe3, 0x23}}}, +{{{0x33, 0x79, 0xe7, 0x18, 0xe6, 0x0f, 0x57, 0x93, 0x15, 0xa0, 0xa7, 0xaa, 0xc4, 0xbf, 0x4f, 0x30, 0x74, 0x95, 0x5e, 0x69, 0x4a, 0x5b, 0x45, 0xe4, 0x00, 0xeb, 0x23, 0x74, 0x4c, 0xdf, 0x6b, 0x45}} , + {{0x97, 0x29, 0x6c, 0xc4, 0x42, 0x0b, 0xdd, 0xc0, 0x29, 0x5c, 0x9b, 0x34, 0x97, 0xd0, 0xc7, 0x79, 0x80, 0x63, 0x74, 0xe4, 0x8e, 0x37, 0xb0, 0x2b, 0x7c, 0xe8, 0x68, 0x6c, 0xc3, 0x82, 0x97, 0x57}}}, +{{{0x22, 0xbe, 0x83, 0xb6, 0x4b, 0x80, 0x6b, 0x43, 0x24, 0x5e, 0xef, 0x99, 0x9b, 0xa8, 0xfc, 0x25, 0x8d, 0x3b, 0x03, 0x94, 0x2b, 0x3e, 0xe7, 0x95, 0x76, 0x9b, 0xcc, 0x15, 0xdb, 0x32, 0xe6, 0x66}} , + {{0x84, 0xf0, 0x4a, 0x13, 0xa6, 0xd6, 0xfa, 0x93, 0x46, 0x07, 0xf6, 0x7e, 0x5c, 0x6d, 0x5e, 0xf6, 0xa6, 0xe7, 0x48, 0xf0, 0x06, 0xea, 0xff, 0x90, 0xc1, 0xcc, 0x4c, 0x19, 0x9c, 0x3c, 0x4e, 0x53}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x2a, 0x50, 0xe3, 0x07, 0x15, 0x59, 0xf2, 0x8b, 0x81, 0xf2, 0xf3, 0xd3, 0x6c, 0x99, 0x8c, 0x70, 0x67, 0xec, 0xcc, 0xee, 0x9e, 0x59, 0x45, 0x59, 0x7d, 0x47, 0x75, 0x69, 0xf5, 0x24, 0x93, 0x5d}} , + {{0x6a, 0x4f, 0x1b, 0xbe, 0x6b, 0x30, 0xcf, 0x75, 0x46, 0xe3, 0x7b, 0x9d, 0xfc, 0xcd, 0xd8, 0x5c, 0x1f, 0xb4, 0xc8, 0xe2, 0x24, 0xec, 0x1a, 0x28, 0x05, 0x32, 0x57, 0xfd, 0x3c, 0x5a, 0x98, 0x10}}}, +{{{0xa3, 0xdb, 0xf7, 0x30, 0xd8, 0xc2, 0x9a, 0xe1, 0xd3, 0xce, 0x22, 0xe5, 0x80, 0x1e, 0xd9, 0xe4, 0x1f, 0xab, 0xc0, 0x71, 0x1a, 0x86, 0x0e, 0x27, 0x99, 0x5b, 0xfa, 0x76, 0x99, 0xb0, 0x08, 0x3c}} , + {{0x2a, 0x93, 0xd2, 0x85, 0x1b, 0x6a, 0x5d, 0xa6, 0xee, 0xd1, 0xd1, 0x33, 0xbd, 0x6a, 0x36, 0x73, 0x37, 0x3a, 0x44, 0xb4, 0xec, 0xa9, 0x7a, 0xde, 0x83, 0x40, 0xd7, 0xdf, 0x28, 0xba, 0xa2, 0x30}}}, +{{{0xd3, 0xb5, 0x6d, 0x05, 0x3f, 0x9f, 0xf3, 0x15, 0x8d, 0x7c, 0xca, 0xc9, 0xfc, 0x8a, 0x7c, 0x94, 0xb0, 0x63, 0x36, 0x9b, 0x78, 0xd1, 0x91, 0x1f, 0x93, 0xd8, 0x57, 0x43, 0xde, 0x76, 0xa3, 0x43}} , + {{0x9b, 0x35, 0xe2, 0xa9, 0x3d, 0x32, 0x1e, 0xbb, 0x16, 0x28, 0x70, 0xe9, 0x45, 0x2f, 0x8f, 0x70, 0x7f, 0x08, 0x7e, 0x53, 0xc4, 0x7a, 0xbf, 0xf7, 0xe1, 0xa4, 0x6a, 0xd8, 0xac, 0x64, 0x1b, 0x11}}}, +{{{0xb2, 0xeb, 0x47, 0x46, 0x18, 0x3e, 0x1f, 0x99, 0x0c, 0xcc, 0xf1, 0x2c, 0xe0, 0xe7, 0x8f, 0xe0, 0x01, 0x7e, 0x65, 0xb8, 0x0c, 0xd0, 0xfb, 0xc8, 0xb9, 0x90, 0x98, 0x33, 0x61, 0x3b, 0xd8, 0x27}} , + {{0xa0, 0xbe, 0x72, 0x3a, 0x50, 0x4b, 0x74, 0xab, 0x01, 0xc8, 0x93, 0xc5, 0xe4, 0xc7, 0x08, 0x6c, 0xb4, 0xca, 0xee, 0xeb, 0x8e, 0xd7, 0x4e, 0x26, 0xc6, 0x1d, 0xe2, 0x71, 0xaf, 0x89, 0xa0, 0x2a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x98, 0x0b, 0xe4, 0xde, 0xdb, 0xa8, 0xfa, 0x82, 0x74, 0x06, 0x52, 0x6d, 0x08, 0x52, 0x8a, 0xff, 0x62, 0xc5, 0x6a, 0x44, 0x0f, 0x51, 0x8c, 0x1f, 0x6e, 0xb6, 0xc6, 0x2c, 0x81, 0xd3, 0x76, 0x46}} , + {{0xf4, 0x29, 0x74, 0x2e, 0x80, 0xa7, 0x1a, 0x8f, 0xf6, 0xbd, 0xd6, 0x8e, 0xbf, 0xc1, 0x95, 0x2a, 0xeb, 0xa0, 0x7f, 0x45, 0xa0, 0x50, 0x14, 0x05, 0xb1, 0x57, 0x4c, 0x74, 0xb7, 0xe2, 0x89, 0x7d}}}, +{{{0x07, 0xee, 0xa7, 0xad, 0xb7, 0x09, 0x0b, 0x49, 0x4e, 0xbf, 0xca, 0xe5, 0x21, 0xe6, 0xe6, 0xaf, 0xd5, 0x67, 0xf3, 0xce, 0x7e, 0x7c, 0x93, 0x7b, 0x5a, 0x10, 0x12, 0x0e, 0x6c, 0x06, 0x11, 0x75}} , + {{0xd5, 0xfc, 0x86, 0xa3, 0x3b, 0xa3, 0x3e, 0x0a, 0xfb, 0x0b, 0xf7, 0x36, 0xb1, 0x5b, 0xda, 0x70, 0xb7, 0x00, 0xa7, 0xda, 0x88, 0x8f, 0x84, 0xa8, 0xbc, 0x1c, 0x39, 0xb8, 0x65, 0xf3, 0x4d, 0x60}}}, +{{{0x96, 0x9d, 0x31, 0xf4, 0xa2, 0xbe, 0x81, 0xb9, 0xa5, 0x59, 0x9e, 0xba, 0x07, 0xbe, 0x74, 0x58, 0xd8, 0xeb, 0xc5, 0x9f, 0x3d, 0xd1, 0xf4, 0xae, 0xce, 0x53, 0xdf, 0x4f, 0xc7, 0x2a, 0x89, 0x4d}} , + {{0x29, 0xd8, 0xf2, 0xaa, 0xe9, 0x0e, 0xf7, 0x2e, 0x5f, 0x9d, 0x8a, 0x5b, 0x09, 0xed, 0xc9, 0x24, 0x22, 0xf4, 0x0f, 0x25, 0x8f, 0x1c, 0x84, 0x6e, 0x34, 0x14, 0x6c, 0xea, 0xb3, 0x86, 0x5d, 0x04}}}, +{{{0x07, 0x98, 0x61, 0xe8, 0x6a, 0xd2, 0x81, 0x49, 0x25, 0xd5, 0x5b, 0x18, 0xc7, 0x35, 0x52, 0x51, 0xa4, 0x46, 0xad, 0x18, 0x0d, 0xc9, 0x5f, 0x18, 0x91, 0x3b, 0xb4, 0xc0, 0x60, 0x59, 0x8d, 0x66}} , + {{0x03, 0x1b, 0x79, 0x53, 0x6e, 0x24, 0xae, 0x57, 0xd9, 0x58, 0x09, 0x85, 0x48, 0xa2, 0xd3, 0xb5, 0xe2, 0x4d, 0x11, 0x82, 0xe6, 0x86, 0x3c, 0xe9, 0xb1, 0x00, 0x19, 0xc2, 0x57, 0xf7, 0x66, 0x7a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x0f, 0xe3, 0x89, 0x03, 0xd7, 0x22, 0x95, 0x9f, 0xca, 0xb4, 0x8d, 0x9e, 0x6d, 0x97, 0xff, 0x8d, 0x21, 0x59, 0x07, 0xef, 0x03, 0x2d, 0x5e, 0xf8, 0x44, 0x46, 0xe7, 0x85, 0x80, 0xc5, 0x89, 0x50}} , + {{0x8b, 0xd8, 0x53, 0x86, 0x24, 0x86, 0x29, 0x52, 0x01, 0xfa, 0x20, 0xc3, 0x4e, 0x95, 0xcb, 0xad, 0x7b, 0x34, 0x94, 0x30, 0xb7, 0x7a, 0xfa, 0x96, 0x41, 0x60, 0x2b, 0xcb, 0x59, 0xb9, 0xca, 0x50}}}, +{{{0xc2, 0x5b, 0x9b, 0x78, 0x23, 0x1b, 0x3a, 0x88, 0x94, 0x5f, 0x0a, 0x9b, 0x98, 0x2b, 0x6e, 0x53, 0x11, 0xf6, 0xff, 0xc6, 0x7d, 0x42, 0xcc, 0x02, 0x80, 0x40, 0x0d, 0x1e, 0xfb, 0xaf, 0x61, 0x07}} , + {{0xb0, 0xe6, 0x2f, 0x81, 0x70, 0xa1, 0x2e, 0x39, 0x04, 0x7c, 0xc4, 0x2c, 0x87, 0x45, 0x4a, 0x5b, 0x69, 0x97, 0xac, 0x6d, 0x2c, 0x10, 0x42, 0x7c, 0x3b, 0x15, 0x70, 0x60, 0x0e, 0x11, 0x6d, 0x3a}}}, +{{{0x9b, 0x18, 0x80, 0x5e, 0xdb, 0x05, 0xbd, 0xc6, 0xb7, 0x3c, 0xc2, 0x40, 0x4d, 0x5d, 0xce, 0x97, 0x8a, 0x34, 0x15, 0xab, 0x28, 0x5d, 0x10, 0xf0, 0x37, 0x0c, 0xcc, 0x16, 0xfa, 0x1f, 0x33, 0x0d}} , + {{0x19, 0xf9, 0x35, 0xaa, 0x59, 0x1a, 0x0c, 0x5c, 0x06, 0xfc, 0x6a, 0x0b, 0x97, 0x53, 0x36, 0xfc, 0x2a, 0xa5, 0x5a, 0x9b, 0x30, 0xef, 0x23, 0xaf, 0x39, 0x5d, 0x9a, 0x6b, 0x75, 0x57, 0x48, 0x0b}}}, +{{{0x26, 0xdc, 0x76, 0x3b, 0xfc, 0xf9, 0x9c, 0x3f, 0x89, 0x0b, 0x62, 0x53, 0xaf, 0x83, 0x01, 0x2e, 0xbc, 0x6a, 0xc6, 0x03, 0x0d, 0x75, 0x2a, 0x0d, 0xe6, 0x94, 0x54, 0xcf, 0xb3, 0xe5, 0x96, 0x25}} , + {{0xfe, 0x82, 0xb1, 0x74, 0x31, 0x8a, 0xa7, 0x6f, 0x56, 0xbd, 0x8d, 0xf4, 0xe0, 0x94, 0x51, 0x59, 0xde, 0x2c, 0x5a, 0xf4, 0x84, 0x6b, 0x4a, 0x88, 0x93, 0xc0, 0x0c, 0x9a, 0xac, 0xa7, 0xa0, 0x68}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x25, 0x0d, 0xd6, 0xc7, 0x23, 0x47, 0x10, 0xad, 0xc7, 0x08, 0x5c, 0x87, 0x87, 0x93, 0x98, 0x18, 0xb8, 0xd3, 0x9c, 0xac, 0x5a, 0x3d, 0xc5, 0x75, 0xf8, 0x49, 0x32, 0x14, 0xcc, 0x51, 0x96, 0x24}} , + {{0x65, 0x9c, 0x5d, 0xf0, 0x37, 0x04, 0xf0, 0x34, 0x69, 0x2a, 0xf0, 0xa5, 0x64, 0xca, 0xde, 0x2b, 0x5b, 0x15, 0x10, 0xd2, 0xab, 0x06, 0xdd, 0xc4, 0xb0, 0xb6, 0x5b, 0xc1, 0x17, 0xdf, 0x8f, 0x02}}}, +{{{0xbd, 0x59, 0x3d, 0xbf, 0x5c, 0x31, 0x44, 0x2c, 0x32, 0x94, 0x04, 0x60, 0x84, 0x0f, 0xad, 0x00, 0xb6, 0x8f, 0xc9, 0x1d, 0xcc, 0x5c, 0xa2, 0x49, 0x0e, 0x50, 0x91, 0x08, 0x9a, 0x43, 0x55, 0x05}} , + {{0x5d, 0x93, 0x55, 0xdf, 0x9b, 0x12, 0x19, 0xec, 0x93, 0x85, 0x42, 0x9e, 0x66, 0x0f, 0x9d, 0xaf, 0x99, 0xaf, 0x26, 0x89, 0xbc, 0x61, 0xfd, 0xff, 0xce, 0x4b, 0xf4, 0x33, 0x95, 0xc9, 0x35, 0x58}}}, +{{{0x12, 0x55, 0xf9, 0xda, 0xcb, 0x44, 0xa7, 0xdc, 0x57, 0xe2, 0xf9, 0x9a, 0xe6, 0x07, 0x23, 0x60, 0x54, 0xa7, 0x39, 0xa5, 0x9b, 0x84, 0x56, 0x6e, 0xaa, 0x8b, 0x8f, 0xb0, 0x2c, 0x87, 0xaf, 0x67}} , + {{0x00, 0xa9, 0x4c, 0xb2, 0x12, 0xf8, 0x32, 0xa8, 0x7a, 0x00, 0x4b, 0x49, 0x32, 0xba, 0x1f, 0x5d, 0x44, 0x8e, 0x44, 0x7a, 0xdc, 0x11, 0xfb, 0x39, 0x08, 0x57, 0x87, 0xa5, 0x12, 0x42, 0x93, 0x0e}}}, +{{{0x17, 0xb4, 0xae, 0x72, 0x59, 0xd0, 0xaa, 0xa8, 0x16, 0x8b, 0x63, 0x11, 0xb3, 0x43, 0x04, 0xda, 0x0c, 0xa8, 0xb7, 0x68, 0xdd, 0x4e, 0x54, 0xe7, 0xaf, 0x5d, 0x5d, 0x05, 0x76, 0x36, 0xec, 0x0d}} , + {{0x6d, 0x7c, 0x82, 0x32, 0x38, 0x55, 0x57, 0x74, 0x5b, 0x7d, 0xc3, 0xc4, 0xfb, 0x06, 0x29, 0xf0, 0x13, 0x55, 0x54, 0xc6, 0xa7, 0xdc, 0x4c, 0x9f, 0x98, 0x49, 0x20, 0xa8, 0xc3, 0x8d, 0xfa, 0x48}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x87, 0x47, 0x9d, 0xe9, 0x25, 0xd5, 0xe3, 0x47, 0x78, 0xdf, 0x85, 0xa7, 0x85, 0x5e, 0x7a, 0x4c, 0x5f, 0x79, 0x1a, 0xf3, 0xa2, 0xb2, 0x28, 0xa0, 0x9c, 0xdd, 0x30, 0x40, 0xd4, 0x38, 0xbd, 0x28}} , + {{0xfc, 0xbb, 0xd5, 0x78, 0x6d, 0x1d, 0xd4, 0x99, 0xb4, 0xaa, 0x44, 0x44, 0x7a, 0x1b, 0xd8, 0xfe, 0xb4, 0x99, 0xb9, 0xcc, 0xe7, 0xc4, 0xd3, 0x3a, 0x73, 0x83, 0x41, 0x5c, 0x40, 0xd7, 0x2d, 0x55}}}, +{{{0x26, 0xe1, 0x7b, 0x5f, 0xe5, 0xdc, 0x3f, 0x7d, 0xa1, 0xa7, 0x26, 0x44, 0x22, 0x23, 0xc0, 0x8f, 0x7d, 0xf1, 0xb5, 0x11, 0x47, 0x7b, 0x19, 0xd4, 0x75, 0x6f, 0x1e, 0xa5, 0x27, 0xfe, 0xc8, 0x0e}} , + {{0xd3, 0x11, 0x3d, 0xab, 0xef, 0x2c, 0xed, 0xb1, 0x3d, 0x7c, 0x32, 0x81, 0x6b, 0xfe, 0xf8, 0x1c, 0x3c, 0x7b, 0xc0, 0x61, 0xdf, 0xb8, 0x75, 0x76, 0x7f, 0xaa, 0xd8, 0x93, 0xaf, 0x3d, 0xe8, 0x3d}}}, +{{{0xfd, 0x5b, 0x4e, 0x8d, 0xb6, 0x7e, 0x82, 0x9b, 0xef, 0xce, 0x04, 0x69, 0x51, 0x52, 0xff, 0xef, 0xa0, 0x52, 0xb5, 0x79, 0x17, 0x5e, 0x2f, 0xde, 0xd6, 0x3c, 0x2d, 0xa0, 0x43, 0xb4, 0x0b, 0x19}} , + {{0xc0, 0x61, 0x48, 0x48, 0x17, 0xf4, 0x9e, 0x18, 0x51, 0x2d, 0xea, 0x2f, 0xf2, 0xf2, 0xe0, 0xa3, 0x14, 0xb7, 0x8b, 0x3a, 0x30, 0xf5, 0x81, 0xc1, 0x5d, 0x71, 0x39, 0x62, 0x55, 0x1f, 0x60, 0x5a}}}, +{{{0xe5, 0x89, 0x8a, 0x76, 0x6c, 0xdb, 0x4d, 0x0a, 0x5b, 0x72, 0x9d, 0x59, 0x6e, 0x63, 0x63, 0x18, 0x7c, 0xe3, 0xfa, 0xe2, 0xdb, 0xa1, 0x8d, 0xf4, 0xa5, 0xd7, 0x16, 0xb2, 0xd0, 0xb3, 0x3f, 0x39}} , + {{0xce, 0x60, 0x09, 0x6c, 0xf5, 0x76, 0x17, 0x24, 0x80, 0x3a, 0x96, 0xc7, 0x94, 0x2e, 0xf7, 0x6b, 0xef, 0xb5, 0x05, 0x96, 0xef, 0xd3, 0x7b, 0x51, 0xda, 0x05, 0x44, 0x67, 0xbc, 0x07, 0x21, 0x4e}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xe9, 0x73, 0x6f, 0x21, 0xb9, 0xde, 0x22, 0x7d, 0xeb, 0x97, 0x31, 0x10, 0xa3, 0xea, 0xe1, 0xc6, 0x37, 0xeb, 0x8f, 0x43, 0x58, 0xde, 0x41, 0x64, 0x0e, 0x3e, 0x07, 0x99, 0x3d, 0xf1, 0xdf, 0x1e}} , + {{0xf8, 0xad, 0x43, 0xc2, 0x17, 0x06, 0xe2, 0xe4, 0xa9, 0x86, 0xcd, 0x18, 0xd7, 0x78, 0xc8, 0x74, 0x66, 0xd2, 0x09, 0x18, 0xa5, 0xf1, 0xca, 0xa6, 0x62, 0x92, 0xc1, 0xcb, 0x00, 0xeb, 0x42, 0x2e}}}, +{{{0x7b, 0x34, 0x24, 0x4c, 0xcf, 0x38, 0xe5, 0x6c, 0x0a, 0x01, 0x2c, 0x22, 0x0b, 0x24, 0x38, 0xad, 0x24, 0x7e, 0x19, 0xf0, 0x6c, 0xf9, 0x31, 0xf4, 0x35, 0x11, 0xf6, 0x46, 0x33, 0x3a, 0x23, 0x59}} , + {{0x20, 0x0b, 0xa1, 0x08, 0x19, 0xad, 0x39, 0x54, 0xea, 0x3e, 0x23, 0x09, 0xb6, 0xe2, 0xd2, 0xbc, 0x4d, 0xfc, 0x9c, 0xf0, 0x13, 0x16, 0x22, 0x3f, 0xb9, 0xd2, 0x11, 0x86, 0x90, 0x55, 0xce, 0x3c}}}, +{{{0xc4, 0x0b, 0x4b, 0x62, 0x99, 0x37, 0x84, 0x3f, 0x74, 0xa2, 0xf9, 0xce, 0xe2, 0x0b, 0x0f, 0x2a, 0x3d, 0xa3, 0xe3, 0xdb, 0x5a, 0x9d, 0x93, 0xcc, 0xa5, 0xef, 0x82, 0x91, 0x1d, 0xe6, 0x6c, 0x68}} , + {{0xa3, 0x64, 0x17, 0x9b, 0x8b, 0xc8, 0x3a, 0x61, 0xe6, 0x9d, 0xc6, 0xed, 0x7b, 0x03, 0x52, 0x26, 0x9d, 0x3a, 0xb3, 0x13, 0xcc, 0x8a, 0xfd, 0x2c, 0x1a, 0x1d, 0xed, 0x13, 0xd0, 0x55, 0x57, 0x0e}}}, +{{{0x1a, 0xea, 0xbf, 0xfd, 0x4a, 0x3c, 0x8e, 0xec, 0x29, 0x7e, 0x77, 0x77, 0x12, 0x99, 0xd7, 0x84, 0xf9, 0x55, 0x7f, 0xf1, 0x8b, 0xb4, 0xd2, 0x95, 0xa3, 0x8d, 0xf0, 0x8a, 0xa7, 0xeb, 0x82, 0x4b}} , + {{0x2c, 0x28, 0xf4, 0x3a, 0xf6, 0xde, 0x0a, 0xe0, 0x41, 0x44, 0x23, 0xf8, 0x3f, 0x03, 0x64, 0x9f, 0xc3, 0x55, 0x4c, 0xc6, 0xc1, 0x94, 0x1c, 0x24, 0x5d, 0x5f, 0x92, 0x45, 0x96, 0x57, 0x37, 0x14}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xc1, 0xcd, 0x90, 0x66, 0xb9, 0x76, 0xa0, 0x5b, 0xa5, 0x85, 0x75, 0x23, 0xf9, 0x89, 0xa5, 0x82, 0xb2, 0x6f, 0xb1, 0xeb, 0xc4, 0x69, 0x6f, 0x18, 0x5a, 0xed, 0x94, 0x3d, 0x9d, 0xd9, 0x2c, 0x1a}} , + {{0x35, 0xb0, 0xe6, 0x73, 0x06, 0xb7, 0x37, 0xe0, 0xf8, 0xb0, 0x22, 0xe8, 0xd2, 0xed, 0x0b, 0xef, 0xe6, 0xc6, 0x5a, 0x99, 0x9e, 0x1a, 0x9f, 0x04, 0x97, 0xe4, 0x4d, 0x0b, 0xbe, 0xba, 0x44, 0x40}}}, +{{{0xc1, 0x56, 0x96, 0x91, 0x5f, 0x1f, 0xbb, 0x54, 0x6f, 0x88, 0x89, 0x0a, 0xb2, 0xd6, 0x41, 0x42, 0x6a, 0x82, 0xee, 0x14, 0xaa, 0x76, 0x30, 0x65, 0x0f, 0x67, 0x39, 0xa6, 0x51, 0x7c, 0x49, 0x24}} , + {{0x35, 0xa3, 0x78, 0xd1, 0x11, 0x0f, 0x75, 0xd3, 0x70, 0x46, 0xdb, 0x20, 0x51, 0xcb, 0x92, 0x80, 0x54, 0x10, 0x74, 0x36, 0x86, 0xa9, 0xd7, 0xa3, 0x08, 0x78, 0xf1, 0x01, 0x29, 0xf8, 0x80, 0x3b}}}, +{{{0xdb, 0xa7, 0x9d, 0x9d, 0xbf, 0xa0, 0xcc, 0xed, 0x53, 0xa2, 0xa2, 0x19, 0x39, 0x48, 0x83, 0x19, 0x37, 0x58, 0xd1, 0x04, 0x28, 0x40, 0xf7, 0x8a, 0xc2, 0x08, 0xb7, 0xa5, 0x42, 0xcf, 0x53, 0x4c}} , + {{0xa7, 0xbb, 0xf6, 0x8e, 0xad, 0xdd, 0xf7, 0x90, 0xdd, 0x5f, 0x93, 0x89, 0xae, 0x04, 0x37, 0xe6, 0x9a, 0xb7, 0xe8, 0xc0, 0xdf, 0x16, 0x2a, 0xbf, 0xc4, 0x3a, 0x3c, 0x41, 0xd5, 0x89, 0x72, 0x5a}}}, +{{{0x1f, 0x96, 0xff, 0x34, 0x2c, 0x13, 0x21, 0xcb, 0x0a, 0x89, 0x85, 0xbe, 0xb3, 0x70, 0x9e, 0x1e, 0xde, 0x97, 0xaf, 0x96, 0x30, 0xf7, 0x48, 0x89, 0x40, 0x8d, 0x07, 0xf1, 0x25, 0xf0, 0x30, 0x58}} , + {{0x1e, 0xd4, 0x93, 0x57, 0xe2, 0x17, 0xe7, 0x9d, 0xab, 0x3c, 0x55, 0x03, 0x82, 0x2f, 0x2b, 0xdb, 0x56, 0x1e, 0x30, 0x2e, 0x24, 0x47, 0x6e, 0xe6, 0xff, 0x33, 0x24, 0x2c, 0x75, 0x51, 0xd4, 0x67}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x2b, 0x06, 0xd9, 0xa1, 0x5d, 0xe1, 0xf4, 0xd1, 0x1e, 0x3c, 0x9a, 0xc6, 0x29, 0x2b, 0x13, 0x13, 0x78, 0xc0, 0xd8, 0x16, 0x17, 0x2d, 0x9e, 0xa9, 0xc9, 0x79, 0x57, 0xab, 0x24, 0x91, 0x92, 0x19}} , + {{0x69, 0xfb, 0xa1, 0x9c, 0xa6, 0x75, 0x49, 0x7d, 0x60, 0x73, 0x40, 0x42, 0xc4, 0x13, 0x0a, 0x95, 0x79, 0x1e, 0x04, 0x83, 0x94, 0x99, 0x9b, 0x1e, 0x0c, 0xe8, 0x1f, 0x54, 0xef, 0xcb, 0xc0, 0x52}}}, +{{{0x14, 0x89, 0x73, 0xa1, 0x37, 0x87, 0x6a, 0x7a, 0xcf, 0x1d, 0xd9, 0x2e, 0x1a, 0x67, 0xed, 0x74, 0xc0, 0xf0, 0x9c, 0x33, 0xdd, 0xdf, 0x08, 0xbf, 0x7b, 0xd1, 0x66, 0xda, 0xe6, 0xc9, 0x49, 0x08}} , + {{0xe9, 0xdd, 0x5e, 0x55, 0xb0, 0x0a, 0xde, 0x21, 0x4c, 0x5a, 0x2e, 0xd4, 0x80, 0x3a, 0x57, 0x92, 0x7a, 0xf1, 0xc4, 0x2c, 0x40, 0xaf, 0x2f, 0xc9, 0x92, 0x03, 0xe5, 0x5a, 0xbc, 0xdc, 0xf4, 0x09}}}, +{{{0xf3, 0xe1, 0x2b, 0x7c, 0x05, 0x86, 0x80, 0x93, 0x4a, 0xad, 0xb4, 0x8f, 0x7e, 0x99, 0x0c, 0xfd, 0xcd, 0xef, 0xd1, 0xff, 0x2c, 0x69, 0x34, 0x13, 0x41, 0x64, 0xcf, 0x3b, 0xd0, 0x90, 0x09, 0x1e}} , + {{0x9d, 0x45, 0xd6, 0x80, 0xe6, 0x45, 0xaa, 0xf4, 0x15, 0xaa, 0x5c, 0x34, 0x87, 0x99, 0xa2, 0x8c, 0x26, 0x84, 0x62, 0x7d, 0xb6, 0x29, 0xc0, 0x52, 0xea, 0xf5, 0x81, 0x18, 0x0f, 0x35, 0xa9, 0x0e}}}, +{{{0xe7, 0x20, 0x72, 0x7c, 0x6d, 0x94, 0x5f, 0x52, 0x44, 0x54, 0xe3, 0xf1, 0xb2, 0xb0, 0x36, 0x46, 0x0f, 0xae, 0x92, 0xe8, 0x70, 0x9d, 0x6e, 0x79, 0xb1, 0xad, 0x37, 0xa9, 0x5f, 0xc0, 0xde, 0x03}} , + {{0x15, 0x55, 0x37, 0xc6, 0x1c, 0x27, 0x1c, 0x6d, 0x14, 0x4f, 0xca, 0xa4, 0xc4, 0x88, 0x25, 0x46, 0x39, 0xfc, 0x5a, 0xe5, 0xfe, 0x29, 0x11, 0x69, 0xf5, 0x72, 0x84, 0x4d, 0x78, 0x9f, 0x94, 0x15}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xec, 0xd3, 0xff, 0x57, 0x0b, 0xb0, 0xb2, 0xdc, 0xf8, 0x4f, 0xe2, 0x12, 0xd5, 0x36, 0xbe, 0x6b, 0x09, 0x43, 0x6d, 0xa3, 0x4d, 0x90, 0x2d, 0xb8, 0x74, 0xe8, 0x71, 0x45, 0x19, 0x8b, 0x0c, 0x6a}} , + {{0xb8, 0x42, 0x1c, 0x03, 0xad, 0x2c, 0x03, 0x8e, 0xac, 0xd7, 0x98, 0x29, 0x13, 0xc6, 0x02, 0x29, 0xb5, 0xd4, 0xe7, 0xcf, 0xcc, 0x8b, 0x83, 0xec, 0x35, 0xc7, 0x9c, 0x74, 0xb7, 0xad, 0x85, 0x5f}}}, +{{{0x78, 0x84, 0xe1, 0x56, 0x45, 0x69, 0x68, 0x5a, 0x4f, 0xb8, 0xb1, 0x29, 0xff, 0x33, 0x03, 0x31, 0xb7, 0xcb, 0x96, 0x25, 0xe6, 0xe6, 0x41, 0x98, 0x1a, 0xbb, 0x03, 0x56, 0xf2, 0xb2, 0x91, 0x34}} , + {{0x2c, 0x6c, 0xf7, 0x66, 0xa4, 0x62, 0x6b, 0x39, 0xb3, 0xba, 0x65, 0xd3, 0x1c, 0xf8, 0x11, 0xaa, 0xbe, 0xdc, 0x80, 0x59, 0x87, 0xf5, 0x7b, 0xe5, 0xe3, 0xb3, 0x3e, 0x39, 0xda, 0xbe, 0x88, 0x09}}}, +{{{0x8b, 0xf1, 0xa0, 0xf5, 0xdc, 0x29, 0xb4, 0xe2, 0x07, 0xc6, 0x7a, 0x00, 0xd0, 0x89, 0x17, 0x51, 0xd4, 0xbb, 0xd4, 0x22, 0xea, 0x7e, 0x7d, 0x7c, 0x24, 0xea, 0xf2, 0xe8, 0x22, 0x12, 0x95, 0x06}} , + {{0xda, 0x7c, 0xa4, 0x0c, 0xf4, 0xba, 0x6e, 0xe1, 0x89, 0xb5, 0x59, 0xca, 0xf1, 0xc0, 0x29, 0x36, 0x09, 0x44, 0xe2, 0x7f, 0xd1, 0x63, 0x15, 0x99, 0xea, 0x25, 0xcf, 0x0c, 0x9d, 0xc0, 0x44, 0x6f}}}, +{{{0x1d, 0x86, 0x4e, 0xcf, 0xf7, 0x37, 0x10, 0x25, 0x8f, 0x12, 0xfb, 0x19, 0xfb, 0xe0, 0xed, 0x10, 0xc8, 0xe2, 0xf5, 0x75, 0xb1, 0x33, 0xc0, 0x96, 0x0d, 0xfb, 0x15, 0x6c, 0x0d, 0x07, 0x5f, 0x05}} , + {{0x69, 0x3e, 0x47, 0x97, 0x2c, 0xaf, 0x52, 0x7c, 0x78, 0x83, 0xad, 0x1b, 0x39, 0x82, 0x2f, 0x02, 0x6f, 0x47, 0xdb, 0x2a, 0xb0, 0xe1, 0x91, 0x99, 0x55, 0xb8, 0x99, 0x3a, 0xa0, 0x44, 0x11, 0x51}}} diff --git a/src/external/poly1305.c b/src/external/poly1305.c new file mode 100644 index 0000000..916dd62 --- /dev/null +++ b/src/external/poly1305.c @@ -0,0 +1,156 @@ +/* + * Public Domain poly1305 from Andrew Moon + * poly1305-donna-unrolled.c from https://github.com/floodyberry/poly1305-donna + */ + +#include "config.h" + +#include +#include + +#include "libssh/poly1305.h" + +#define mul32x32_64(a,b) ((uint64_t)(a) * (b)) + +#define U8TO32_LE(p) \ + (((uint32_t)((p)[0])) | \ + ((uint32_t)((p)[1]) << 8) | \ + ((uint32_t)((p)[2]) << 16) | \ + ((uint32_t)((p)[3]) << 24)) + +#define U32TO8_LE(p, v) \ + do { \ + (p)[0] = (uint8_t)((v)); \ + (p)[1] = (uint8_t)((v) >> 8); \ + (p)[2] = (uint8_t)((v) >> 16); \ + (p)[3] = (uint8_t)((v) >> 24); \ + } while (0) + +void +poly1305_auth(unsigned char out[POLY1305_TAGLEN], const unsigned char *m, size_t inlen, const unsigned char key[POLY1305_KEYLEN]) { + uint32_t t0,t1,t2,t3; + uint32_t h0,h1,h2,h3,h4; + uint32_t r0,r1,r2,r3,r4; + uint32_t s1,s2,s3,s4; + uint32_t b, nb; + size_t j; + uint64_t t[5]; + uint64_t f0,f1,f2,f3; + uint32_t g0,g1,g2,g3,g4; + uint64_t c; + unsigned char mp[16]; + + /* clamp key */ + t0 = U8TO32_LE(key+0); + t1 = U8TO32_LE(key+4); + t2 = U8TO32_LE(key+8); + t3 = U8TO32_LE(key+12); + + /* precompute multipliers */ + r0 = t0 & 0x3ffffff; t0 >>= 26; t0 |= t1 << 6; + r1 = t0 & 0x3ffff03; t1 >>= 20; t1 |= t2 << 12; + r2 = t1 & 0x3ffc0ff; t2 >>= 14; t2 |= t3 << 18; + r3 = t2 & 0x3f03fff; t3 >>= 8; + r4 = t3 & 0x00fffff; + + s1 = r1 * 5; + s2 = r2 * 5; + s3 = r3 * 5; + s4 = r4 * 5; + + /* init state */ + h0 = 0; + h1 = 0; + h2 = 0; + h3 = 0; + h4 = 0; + + /* full blocks */ + if (inlen < 16) goto poly1305_donna_atmost15bytes; +poly1305_donna_16bytes: + m += 16; + inlen -= 16; + + t0 = U8TO32_LE(m-16); + t1 = U8TO32_LE(m-12); + t2 = U8TO32_LE(m-8); + t3 = U8TO32_LE(m-4); + + h0 += t0 & 0x3ffffff; + h1 += ((((uint64_t)t1 << 32) | t0) >> 26) & 0x3ffffff; + h2 += ((((uint64_t)t2 << 32) | t1) >> 20) & 0x3ffffff; + h3 += ((((uint64_t)t3 << 32) | t2) >> 14) & 0x3ffffff; + h4 += (t3 >> 8) | (1 << 24); + + +poly1305_donna_mul: + t[0] = mul32x32_64(h0,r0) + mul32x32_64(h1,s4) + mul32x32_64(h2,s3) + mul32x32_64(h3,s2) + mul32x32_64(h4,s1); + t[1] = mul32x32_64(h0,r1) + mul32x32_64(h1,r0) + mul32x32_64(h2,s4) + mul32x32_64(h3,s3) + mul32x32_64(h4,s2); + t[2] = mul32x32_64(h0,r2) + mul32x32_64(h1,r1) + mul32x32_64(h2,r0) + mul32x32_64(h3,s4) + mul32x32_64(h4,s3); + t[3] = mul32x32_64(h0,r3) + mul32x32_64(h1,r2) + mul32x32_64(h2,r1) + mul32x32_64(h3,r0) + mul32x32_64(h4,s4); + t[4] = mul32x32_64(h0,r4) + mul32x32_64(h1,r3) + mul32x32_64(h2,r2) + mul32x32_64(h3,r1) + mul32x32_64(h4,r0); + + h0 = (uint32_t)t[0] & 0x3ffffff; c = (t[0] >> 26); + t[1] += c; h1 = (uint32_t)t[1] & 0x3ffffff; b = (uint32_t)(t[1] >> 26); + t[2] += b; h2 = (uint32_t)t[2] & 0x3ffffff; b = (uint32_t)(t[2] >> 26); + t[3] += b; h3 = (uint32_t)t[3] & 0x3ffffff; b = (uint32_t)(t[3] >> 26); + t[4] += b; h4 = (uint32_t)t[4] & 0x3ffffff; b = (uint32_t)(t[4] >> 26); + h0 += b * 5; + + if (inlen >= 16) goto poly1305_donna_16bytes; + + /* final bytes */ +poly1305_donna_atmost15bytes: + if (!inlen) goto poly1305_donna_finish; + + for (j = 0; j < inlen; j++) mp[j] = m[j]; + mp[j++] = 1; + for (; j < 16; j++) mp[j] = 0; + inlen = 0; + + t0 = U8TO32_LE(mp+0); + t1 = U8TO32_LE(mp+4); + t2 = U8TO32_LE(mp+8); + t3 = U8TO32_LE(mp+12); + + h0 += t0 & 0x3ffffff; + h1 += ((((uint64_t)t1 << 32) | t0) >> 26) & 0x3ffffff; + h2 += ((((uint64_t)t2 << 32) | t1) >> 20) & 0x3ffffff; + h3 += ((((uint64_t)t3 << 32) | t2) >> 14) & 0x3ffffff; + h4 += (t3 >> 8); + + goto poly1305_donna_mul; + +poly1305_donna_finish: + b = h0 >> 26; h0 = h0 & 0x3ffffff; + h1 += b; b = h1 >> 26; h1 = h1 & 0x3ffffff; + h2 += b; b = h2 >> 26; h2 = h2 & 0x3ffffff; + h3 += b; b = h3 >> 26; h3 = h3 & 0x3ffffff; + h4 += b; b = h4 >> 26; h4 = h4 & 0x3ffffff; + h0 += b * 5; b = h0 >> 26; h0 = h0 & 0x3ffffff; + h1 += b; + + g0 = h0 + 5; b = g0 >> 26; g0 &= 0x3ffffff; + g1 = h1 + b; b = g1 >> 26; g1 &= 0x3ffffff; + g2 = h2 + b; b = g2 >> 26; g2 &= 0x3ffffff; + g3 = h3 + b; b = g3 >> 26; g3 &= 0x3ffffff; + g4 = h4 + b - (1 << 26); + + b = (g4 >> 31) - 1; + nb = ~b; + h0 = (h0 & nb) | (g0 & b); + h1 = (h1 & nb) | (g1 & b); + h2 = (h2 & nb) | (g2 & b); + h3 = (h3 & nb) | (g3 & b); + h4 = (h4 & nb) | (g4 & b); + + f0 = ((h0 ) | (h1 << 26)) + (uint64_t)U8TO32_LE(&key[16]); + f1 = ((h1 >> 6) | (h2 << 20)) + (uint64_t)U8TO32_LE(&key[20]); + f2 = ((h2 >> 12) | (h3 << 14)) + (uint64_t)U8TO32_LE(&key[24]); + f3 = ((h3 >> 18) | (h4 << 8)) + (uint64_t)U8TO32_LE(&key[28]); + + U32TO8_LE(&out[ 0], f0); f1 += (f0 >> 32); + U32TO8_LE(&out[ 4], f1); f2 += (f1 >> 32); + U32TO8_LE(&out[ 8], f2); f3 += (f2 >> 32); + U32TO8_LE(&out[12], f3); +} diff --git a/src/external/sc25519.c b/src/external/sc25519.c new file mode 100644 index 0000000..5da91f7 --- /dev/null +++ b/src/external/sc25519.c @@ -0,0 +1,375 @@ +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/sc25519.c + */ + +#include "config.h" + +#include "libssh/priv.h" +#include "libssh/sc25519.h" + +/*Arithmetic modulo the group order m = 2^252 + 27742317777372353535851937790883648493 = 7237005577332262213973186563042994240857116359379907606001950938285454250989 */ + +static const uint32_t m[32] = { + 0xED, 0xD3, 0xF5, 0x5C, 0x1A, 0x63, 0x12, 0x58, + 0xD6, 0x9C, 0xF7, 0xA2, 0xDE, 0xF9, 0xDE, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 +}; + +static const uint32_t mu[33] = { + 0x1B, 0x13, 0x2C, 0x0A, 0xA3, 0xE5, 0x9C, 0xED, + 0xA7, 0x29, 0x63, 0x08, 0x5D, 0x21, 0x06, 0x21, + 0xEB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F +}; + +static uint32_t lt(uint32_t a,uint32_t b) /* 16-bit inputs */ +{ + unsigned int x = a; + + x -= (unsigned int) b; /* 0..65535: no; 4294901761..4294967295: yes */ + x >>= 31; /* 0: no; 1: yes */ + + return x; +} + +/* Reduce coefficients of r before calling reduce_add_sub */ +static void reduce_add_sub(sc25519 *r) +{ + uint32_t pb = 0; + uint32_t b; + uint32_t mask; + int i; + unsigned char t[32]; + + for (i = 0; i < 32; i++) { + pb += m[i]; + b = lt(r->v[i],pb); + t[i] = r->v[i]-pb+(b<<8); + pb = b; + } + mask = b - 1; + for (i = 0; i < 32; i++) { + r->v[i] ^= mask & (r->v[i] ^ t[i]); + } +} + +/* Reduce coefficients of x before calling barrett_reduce */ +static void barrett_reduce(sc25519 *r, const uint32_t x[64]) +{ + /* See HAC, Alg. 14.42 */ + int i,j; + uint32_t q2[66]; + uint32_t *q3 = q2 + 33; + uint32_t r1[33]; + uint32_t r2[33]; + uint32_t carry; + uint32_t pb = 0; + uint32_t b; + + for (i = 0; i < 66; i++) { + q2[i] = 0; + } + for (i = 0; i < 33; i++) { + r2[i] = 0; + } + + for (i = 0; i < 33; i++) { + for (j = 0; j < 33; j++) { + if (i + j >= 31) { + q2[i+j] += mu[i]*x[j+31]; + } + } + } + + carry = q2[31] >> 8; + q2[32] += carry; + carry = q2[32] >> 8; + q2[33] += carry; + + for (i = 0; i < 33; i++) { + r1[i] = x[i]; + } + + for (i = 0; i < 32; i++) { + for (j = 0; j < 33; j++) { + if (i + j < 33) { + r2[i+j] += m[i]*q3[j]; + } + } + } + + for (i = 0; i < 32; i++) { + carry = r2[i] >> 8; + r2[i+1] += carry; + r2[i] &= 0xff; + } + + for (i = 0; i < 32; i++) { + pb += r2[i]; + b = lt(r1[i],pb); + r->v[i] = r1[i]-pb+(b<<8); + pb = b; + } + + /* XXX: Can it really happen that r<0?, See HAC, Alg 14.42, Step 3 + * If so: Handle it here! + */ + + reduce_add_sub(r); + reduce_add_sub(r); +} + +void sc25519_from32bytes(sc25519 *r, const unsigned char x[32]) +{ + int i; + uint32_t t[64]; + + for (i = 0; i < 32; i++) { + t[i] = x[i]; + } + for (i = 32; i < 64; i++) { + t[i] = 0; + } + + barrett_reduce(r, t); +} + +void shortsc25519_from16bytes(shortsc25519 *r, const unsigned char x[16]) +{ + int i; + + for (i = 0; i < 16; i++) { + r->v[i] = x[i]; + } +} + +void sc25519_from64bytes(sc25519 *r, const unsigned char x[64]) +{ + int i; + uint32_t t[64]; + + for (i = 0; i < 64; i++) { + t[i] = x[i]; + } + + barrett_reduce(r, t); +} + +void sc25519_from_shortsc(sc25519 *r, const shortsc25519 *x) +{ + int i; + + for (i = 0; i < 16; i++) { + r->v[i] = x->v[i]; + } + for (i = 0; i < 16; i++) { + r->v[16+i] = 0; + } +} + +void sc25519_to32bytes(unsigned char r[32], const sc25519 *x) +{ + int i; + + for (i = 0; i < 32; i++) { + r[i] = x->v[i]; + } +} + +int sc25519_iszero_vartime(const sc25519 *x) +{ + int i; + + for (i = 0; i < 32; i++) { + if(x->v[i] != 0) { + return 0; + } + } + + return 1; +} + +int sc25519_isshort_vartime(const sc25519 *x) +{ + int i; + + for (i = 31; i > 15; i--) { + if (x->v[i] != 0) { + return 0; + } + } + + return 1; +} + +int sc25519_lt_vartime(const sc25519 *x, const sc25519 *y) +{ + int i; + + for (i = 31; i >= 0; i--) { + if (x->v[i] < y->v[i]) { + return 1; + } + if (x->v[i] > y->v[i]) { + return 0; + } + } + + return 0; +} + +void sc25519_add(sc25519 *r, const sc25519 *x, const sc25519 *y) +{ + uint32_t i, carry; + + for (i = 0; i < 32; i++) { + r->v[i] = x->v[i] + y->v[i]; + } + + for (i = 0;i < 31; i++) { + carry = r->v[i] >> 8; + r->v[i+1] += carry; + r->v[i] &= 0xff; + } + + reduce_add_sub(r); +} + +void sc25519_sub_nored(sc25519 *r, const sc25519 *x, const sc25519 *y) +{ + uint32_t b = 0; + uint32_t t; + int i; + + for (i = 0; i < 32; i++) { + t = x->v[i] - y->v[i] - b; + r->v[i] = t & 255; + b = (t >> 8) & 1; + } +} + +void sc25519_mul(sc25519 *r, const sc25519 *x, const sc25519 *y) +{ + uint32_t i,j,carry; + uint32_t t[64]; + + for (i = 0; i < 64; i++) { + t[i] = 0; + } + + for (i = 0; i < 32; i++) { + for (j = 0; j < 32; j++) { + t[i+j] += x->v[i] * y->v[j]; + } + } + + /* Reduce coefficients */ + for (i = 0; i < 63; i++) { + carry = t[i] >> 8; + t[i+1] += carry; + t[i] &= 0xff; + } + + barrett_reduce(r, t); +} + +void sc25519_mul_shortsc(sc25519 *r, const sc25519 *x, const shortsc25519 *y) +{ + sc25519 t; + sc25519_from_shortsc(&t, y); + sc25519_mul(r, x, &t); +} + +void sc25519_window3(signed char r[85], const sc25519 *s) +{ + char carry; + int i; + + for (i = 0; i < 10; i++) { + r[8*i+0] = s->v[3*i+0] & 7; + r[8*i+1] = (s->v[3*i+0] >> 3) & 7; + r[8*i+2] = (s->v[3*i+0] >> 6) & 7; + r[8*i+2] ^= (s->v[3*i+1] << 2) & 7; + r[8*i+3] = (s->v[3*i+1] >> 1) & 7; + r[8*i+4] = (s->v[3*i+1] >> 4) & 7; + r[8*i+5] = (s->v[3*i+1] >> 7) & 7; + r[8*i+5] ^= (s->v[3*i+2] << 1) & 7; + r[8*i+6] = (s->v[3*i+2] >> 2) & 7; + r[8*i+7] = (s->v[3*i+2] >> 5) & 7; + } + r[8*i+0] = s->v[3*i+0] & 7; + r[8*i+1] = (s->v[3*i+0] >> 3) & 7; + r[8*i+2] = (s->v[3*i+0] >> 6) & 7; + r[8*i+2] ^= (s->v[3*i+1] << 2) & 7; + r[8*i+3] = (s->v[3*i+1] >> 1) & 7; + r[8*i+4] = (s->v[3*i+1] >> 4) & 7; + + /* Making it signed */ + carry = 0; + for (i = 0; i < 84; i++) { + r[i] += carry; + r[i+1] += r[i] >> 3; + r[i] &= 7; + carry = r[i] >> 2; + r[i] -= carry<<3; + } + + r[84] += carry; +} + +void sc25519_window5(signed char r[51], const sc25519 *s) +{ + char carry; + int i; + + for (i = 0; i < 6; i++) { + r[8*i+0] = s->v[5*i+0] & 31; + r[8*i+1] = (s->v[5*i+0] >> 5) & 31; + r[8*i+1] ^= (s->v[5*i+1] << 3) & 31; + r[8*i+2] = (s->v[5*i+1] >> 2) & 31; + r[8*i+3] = (s->v[5*i+1] >> 7) & 31; + r[8*i+3] ^= (s->v[5*i+2] << 1) & 31; + r[8*i+4] = (s->v[5*i+2] >> 4) & 31; + r[8*i+4] ^= (s->v[5*i+3] << 4) & 31; + r[8*i+5] = (s->v[5*i+3] >> 1) & 31; + r[8*i+6] = (s->v[5*i+3] >> 6) & 31; + r[8*i+6] ^= (s->v[5*i+4] << 2) & 31; + r[8*i+7] = (s->v[5*i+4] >> 3) & 31; + } + r[8*i+0] = s->v[5*i+0] & 31; + r[8*i+1] = (s->v[5*i+0] >> 5) & 31; + r[8*i+1] ^= (s->v[5*i+1] << 3) & 31; + r[8*i+2] = (s->v[5*i+1] >> 2) & 31; + + /* Making it signed */ + carry = 0; + for (i = 0; i < 50; i++) { + r[i] += carry; + r[i+1] += r[i] >> 5; + r[i] &= 31; + carry = r[i] >> 4; + r[i] -= carry<<5; + } + + r[50] += carry; +} + +void sc25519_2interleave2(unsigned char r[127], + const sc25519 *s1, + const sc25519 *s2) +{ + int i; + + for (i = 0; i < 31; i++) { + r[4*i] = ( s1->v[i] & 3) ^ (( s2->v[i] & 3) << 2); + r[4*i+1] = ((s1->v[i] >> 2) & 3) ^ (((s2->v[i] >> 2) & 3) << 2); + r[4*i+2] = ((s1->v[i] >> 4) & 3) ^ (((s2->v[i] >> 4) & 3) << 2); + r[4*i+3] = ((s1->v[i] >> 6) & 3) ^ (((s2->v[i] >> 6) & 3) << 2); + } + r[124] = ( s1->v[31] & 3) ^ (( s2->v[31] & 3) << 2); + r[125] = ((s1->v[31] >> 2) & 3) ^ (((s2->v[31] >> 2) & 3) << 2); + r[126] = ((s1->v[31] >> 4) & 3) ^ (((s2->v[31] >> 4) & 3) << 2); +} diff --git a/src/gcrypt_missing.c b/src/gcrypt_missing.c new file mode 100644 index 0000000..e931ec5 --- /dev/null +++ b/src/gcrypt_missing.c @@ -0,0 +1,124 @@ +/* + * gcrypt_missing.c - routines that are in OpenSSL but not in libgcrypt. + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2006 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 + +#include "libssh/priv.h" +#include "libssh/libgcrypt.h" + +#ifdef HAVE_LIBGCRYPT +int ssh_gcry_dec2bn(bignum *bn, const char *data) { + int count; + + *bn = bignum_new(); + if (*bn == NULL) { + return 0; + } + gcry_mpi_set_ui(*bn, 0); + for (count = 0; data[count]; count++) { + gcry_mpi_mul_ui(*bn, *bn, 10); + gcry_mpi_add_ui(*bn, *bn, data[count] - '0'); + } + + return count; +} + +char *ssh_gcry_bn2dec(bignum bn) { + bignum bndup, num, ten; + char *ret; + int count, count2; + int size, rsize; + char decnum; + + size = gcry_mpi_get_nbits(bn) * 3; + rsize = size / 10 + size / 1000 + 2; + + ret = malloc(rsize + 1); + if (ret == NULL) { + return NULL; + } + + if (!gcry_mpi_cmp_ui(bn, 0)) { + strcpy(ret, "0"); + } else { + ten = bignum_new(); + if (ten == NULL) { + SAFE_FREE(ret); + return NULL; + } + + num = bignum_new(); + if (num == NULL) { + SAFE_FREE(ret); + bignum_safe_free(ten); + return NULL; + } + + for (bndup = gcry_mpi_copy(bn), bignum_set_word(ten, 10), count = rsize; + count; count--) { + gcry_mpi_div(bndup, num, bndup, ten, 0); + for (decnum = 0, count2 = gcry_mpi_get_nbits(num); count2; + decnum *= 2, decnum += (gcry_mpi_test_bit(num, count2 - 1) ? 1 : 0), + count2--) + ; + ret[count - 1] = decnum + '0'; + } + for (count = 0; count < rsize && ret[count] == '0'; count++) + ; + for (count2 = 0; count2 < rsize - count; ++count2) { + ret[count2] = ret[count2 + count]; + } + ret[count2] = 0; + bignum_safe_free(num); + bignum_safe_free(bndup); + bignum_safe_free(ten); + } + + return ret; +} + +/** @brief generates a random integer between 0 and max + * @returns 1 in case of success, 0 otherwise + */ +int ssh_gcry_rand_range(bignum dest, bignum max) +{ + size_t bits; + bignum rnd; + int rc; + + bits = bignum_num_bits(max) + 64; + rnd = bignum_new(); + if (rnd == NULL) { + return 0; + } + rc = bignum_rand(rnd, bits); + if (rc != 1) { + return rc; + } + gcry_mpi_mod(dest, rnd, max); + bignum_safe_free(rnd); + return 1; +} +#endif diff --git a/src/getpass.c b/src/getpass.c new file mode 100644 index 0000000..9962766 --- /dev/null +++ b/src/getpass.c @@ -0,0 +1,284 @@ +/* + * getpass.c - platform independent getpass function. + * + * This file is part of the SSH Library + * + * Copyright (c) 2011-2013 by Andreas Schneider + * + * 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 +#include +#include + +#include + +/** + * @internal + * + * @brief Get the password from the console. + * + * @param[in] prompt The prompt to display. + * + * @param[in] buf The buffer to fill. + * + * @param[in] len The length of the buffer. + * + * @param[in] verify Should the password be verified? + * + * @return 1 on success, 0 on error. + */ +static int ssh_gets(const char *prompt, char *buf, size_t len, int verify) { + char *tmp; + char *ptr = NULL; + int ok = 0; + + tmp = calloc(1, len); + if (tmp == NULL) { + return 0; + } + + /* read the password */ + while (!ok) { + if (buf[0] != '\0') { + fprintf(stdout, "%s[%s] ", prompt, buf); + } else { + fprintf(stdout, "%s", prompt); + } + fflush(stdout); + if (fgets(tmp, len, stdin) == NULL) { + free(tmp); + return 0; + } + + if ((ptr = strchr(tmp, '\n'))) { + *ptr = '\0'; + } + fprintf(stdout, "\n"); + + if (*tmp) { + strncpy(buf, tmp, len); + } + + if (verify) { + char *key_string; + + key_string = calloc(1, len); + if (key_string == NULL) { + break; + } + + fprintf(stdout, "\nVerifying, please re-enter. %s", prompt); + fflush(stdout); + if (! fgets(key_string, len, stdin)) { + explicit_bzero(key_string, len); + SAFE_FREE(key_string); + clearerr(stdin); + continue; + } + if ((ptr = strchr(key_string, '\n'))) { + *ptr = '\0'; + } + fprintf(stdout, "\n"); + if (strcmp(buf, key_string)) { + printf("\n\07\07Mismatch - try again\n"); + explicit_bzero(key_string, len); + SAFE_FREE(key_string); + fflush(stdout); + continue; + } + explicit_bzero(key_string, len); + SAFE_FREE(key_string); + } + ok = 1; + } + explicit_bzero(tmp, len); + free(tmp); + + return ok; +} + +#ifdef _WIN32 +#include + +int ssh_getpass(const char *prompt, + char *buf, + size_t len, + int echo, + int verify) { + HANDLE h; + DWORD mode = 0; + int ok; + + /* fgets needs at least len - 1 */ + if (prompt == NULL || buf == NULL || len < 2) { + return -1; + } + + /* get stdin and mode */ + h = GetStdHandle(STD_INPUT_HANDLE); + if (!GetConsoleMode(h, &mode)) { + return -1; + } + + /* disable echo */ + if (!echo) { + if (!SetConsoleMode(h, mode & ~ENABLE_ECHO_INPUT)) { + return -1; + } + } + + ok = ssh_gets(prompt, buf, len, verify); + + /* reset echo */ + SetConsoleMode(h, mode); + + if (!ok) { + explicit_bzero(buf, len); + return -1; + } + + /* force termination */ + buf[len - 1] = '\0'; + + return 0; +} + +#else + +#include +#ifdef HAVE_TERMIOS_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +/** + * @ingroup libssh_misc + * + * @brief Get a password from the console. + * + * You should make sure that the buffer is an empty string! + * + * You can also use this function to ask for a username. Then you can fill the + * buffer with the username and it is shows to the users. If the users just + * presses enter the buffer will be untouched. + * + * @code + * char username[128]; + * + * snprintf(username, sizeof(username), "john"); + * + * ssh_getpass("Username:", username, sizeof(username), 1, 0); + * @endcode + * + * The prompt will look like this: + * + * Username: [john] + * + * If you press enter then john is used as the username, or you can type it in + * to change it. + * + * @param[in] prompt The prompt to show to ask for the password. + * + * @param[out] buf The buffer the password should be stored. It NEEDS to be + * empty or filled out. + * + * @param[in] len The length of the buffer. + * + * @param[in] echo Should we echo what you type. + * + * @param[in] verify Should we ask for the password twice. + * + * @return 0 on success, -1 on error. + */ +int ssh_getpass(const char *prompt, + char *buf, + size_t len, + int echo, + int verify) { + struct termios attr; + struct termios old_attr; + int ok = 0; + int fd = -1; + + /* fgets needs at least len - 1 */ + if (prompt == NULL || buf == NULL || len < 2) { + return -1; + } + + if (isatty(STDIN_FILENO)) { + ZERO_STRUCT(attr); + ZERO_STRUCT(old_attr); + + /* get local terminal attributes */ + if (tcgetattr(STDIN_FILENO, &attr) < 0) { + perror("tcgetattr"); + return -1; + } + + /* save terminal attributes */ + memcpy(&old_attr, &attr, sizeof(attr)); + if((fd = fcntl(0, F_GETFL, 0)) < 0) { + perror("fcntl"); + return -1; + } + + /* disable echo */ + if (!echo) { + attr.c_lflag &= ~(ECHO); + } + + /* write attributes to terminal */ + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &attr) < 0) { + perror("tcsetattr"); + return -1; + } + } + + /* disable nonblocking I/O */ + if (fd & O_NDELAY) { + fcntl(0, F_SETFL, fd & ~O_NDELAY); + } + + ok = ssh_gets(prompt, buf, len, verify); + + if (isatty(STDIN_FILENO)) { + /* reset terminal */ + tcsetattr(STDIN_FILENO, TCSANOW, &old_attr); + } + + /* close fd */ + if (fd & O_NDELAY) { + fcntl(0, F_SETFL, fd); + } + + if (!ok) { + explicit_bzero(buf, len); + return -1; + } + + /* force termination */ + buf[len - 1] = '\0'; + + return 0; +} + +#endif diff --git a/src/gssapi.c b/src/gssapi.c new file mode 100644 index 0000000..488df58 --- /dev/null +++ b/src/gssapi.c @@ -0,0 +1,1020 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 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 +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/** current state of an GSSAPI authentication */ +enum ssh_gssapi_state_e { + SSH_GSSAPI_STATE_NONE, /* no status */ + SSH_GSSAPI_STATE_RCV_TOKEN, /* Expecting a token */ + SSH_GSSAPI_STATE_RCV_MIC, /* Expecting a MIC */ +}; + +struct ssh_gssapi_struct{ + enum ssh_gssapi_state_e state; /* current state */ + struct gss_OID_desc_struct mech; /* mechanism being elected for auth */ + gss_cred_id_t server_creds; /* credentials of server */ + gss_cred_id_t client_creds; /* creds delegated by the client */ + gss_ctx_id_t ctx; /* the authentication context */ + gss_name_t client_name; /* Identity of the client */ + char *user; /* username of client */ + char *canonic_user; /* canonic form of the client's username */ + char *service; /* name of the service */ + struct { + gss_name_t server_name; /* identity of server */ + OM_uint32 flags; /* flags used for init context */ + gss_OID oid; /* mech being used for authentication */ + gss_cred_id_t creds; /* creds used to initialize context */ + gss_cred_id_t client_deleg_creds; /* delegated creds (const, not freeable) */ + } client; +}; + + +/** @internal + * @initializes a gssapi context for authentication + */ +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){ + 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; + session->gssapi->state = SSH_GSSAPI_STATE_NONE; + return SSH_OK; +} + +/** @internal + * @frees a gssapi context + */ +static void ssh_gssapi_free(ssh_session session){ + OM_uint32 min; + if (session->gssapi == NULL) + return; + SAFE_FREE(session->gssapi->user); + SAFE_FREE(session->gssapi->mech.elements); + gss_release_cred(&min,&session->gssapi->server_creds); + if (session->gssapi->client.creds != + session->gssapi->client.client_deleg_creds) { + gss_release_cred(&min, &session->gssapi->client.creds); + } + SAFE_FREE(session->gssapi); +} + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token){ +#ifdef WITH_SERVER + if(session->server) + return ssh_packet_userauth_gssapi_token_server(session, type, packet, user); +#endif + return ssh_packet_userauth_gssapi_token_client(session, type, packet, user); +} +#ifdef WITH_SERVER + +/** @internal + * @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){ + 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); + return SSH_ERROR; + } + session->auth.state = SSH_AUTH_STATE_GSSAPI_TOKEN; + + ssh_packet_send(session); + SSH_LOG(SSH_LOG_PACKET, + "Sent SSH_MSG_USERAUTH_GSSAPI_RESPONSE"); + return SSH_OK; +} + +#endif /* WITH_SERVER */ + +static void ssh_gssapi_log_error(int verb, + const char *msg, + int maj_stat, + int min_stat) +{ + gss_buffer_desc msg_maj = { + .length = 0, + }; + gss_buffer_desc msg_min = { + .length = 0, + }; + OM_uint32 dummy_maj, dummy_min; + OM_uint32 message_context = 0; + + dummy_maj = gss_display_status(&dummy_min, + maj_stat, + GSS_C_GSS_CODE, + GSS_C_NO_OID, + &message_context, + &msg_maj); + if (dummy_maj != 0) { + goto out; + } + + dummy_maj = gss_display_status(&dummy_min, + min_stat, + GSS_C_MECH_CODE, + GSS_C_NO_OID, + &message_context, + &msg_min); + if (dummy_maj != 0) { + goto out; + } + + SSH_LOG(verb, + "GSSAPI(%s): %s - %s", + msg, + (const char *)msg_maj.value, + (const char *)msg_min.value); + +out: + if (msg_maj.value) { + gss_release_buffer(&dummy_min, &msg_maj); + } + if (msg_min.value) { + gss_release_buffer(&dummy_min, &msg_min); + } +} + +#ifdef WITH_SERVER + +/** @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"; + gss_buffer_desc name_buf; + gss_name_t server_name; /* local server fqdn */ + OM_uint32 maj_stat, min_stat; + size_t i; + char *ptr; + gss_OID_set supported; /* oids supported by server */ + gss_OID_set both_supported; /* oids supported by both client and server */ + gss_OID_set selected; /* oid selected for authentication */ + int present=0; + size_t oid_count=0; + struct gss_OID_desc_struct oid; + int rc; + + if (ssh_callbacks_exists(session->server_callbacks, gssapi_select_oid_function)){ + ssh_string oid_s = session->server_callbacks->gssapi_select_oid_function(session, + user, n_oid, oids, + session->server_callbacks->userdata); + if (oid_s != NULL){ + if (ssh_gssapi_init(session) == SSH_ERROR) + return SSH_ERROR; + session->gssapi->state = SSH_GSSAPI_STATE_RCV_TOKEN; + rc = ssh_gssapi_send_response(session, oid_s); + SSH_STRING_FREE(oid_s); + return rc; + } else { + return ssh_auth_reply_default(session,0); + } + } + gss_create_empty_oid_set(&min_stat, &both_supported); + + 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, + "indicate mechs", + maj_stat, + min_stat); + return SSH_ERROR; + } + + for (i=0; i < supported->count; ++i){ + ptr = ssh_get_hexa(supported->elements[i].elements, supported->elements[i].length); + SSH_LOG(SSH_LOG_DEBUG, "Supported mech %zu: %s", i, ptr); + free(ptr); + } + + for (i=0 ; i< n_oid ; ++i){ + unsigned char *oid_s = (unsigned char *) ssh_string_data(oids[i]); + size_t len = ssh_string_len(oids[i]); + + if (oid_s == NULL) { + 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"); + continue; + } + oid.elements = &oid_s[2]; + oid.length = len - 2; + gss_test_oid_set_member(&min_stat,&oid,supported,&present); + if(present){ + gss_add_oid_set_member(&min_stat,&oid,&both_supported); + oid_count++; + } + } + gss_release_oid_set(&min_stat, &supported); + if (oid_count == 0){ + SSH_LOG(SSH_LOG_PROTOCOL,"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) + 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, + "importing name", + maj_stat, + min_stat); + return -1; + } + + maj_stat = gss_acquire_cred(&min_stat, server_name, 0, + both_supported, GSS_C_ACCEPT, + &session->gssapi->server_creds, &selected, NULL); + gss_release_name(&min_stat, &server_name); + 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, + "acquiring creds", + maj_stat, + min_stat); + ssh_auth_reply_default(session,0); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "acquiring credentials %d, %d", maj_stat, min_stat); + + /* finding which OID from client we selected */ + for (i=0 ; i< n_oid ; ++i){ + unsigned char *oid_s = (unsigned char *) ssh_string_data(oids[i]); + size_t len = ssh_string_len(oids[i]); + + if (oid_s == NULL) { + 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"); + continue; + } + oid.elements = &oid_s[2]; + oid.length = len - 2; + gss_test_oid_set_member(&min_stat,&oid,selected,&present); + if(present){ + SSH_LOG(SSH_LOG_PACKET, "Selected oid %zu", i); + break; + } + } + session->gssapi->mech.length = oid.length; + session->gssapi->mech.elements = malloc(oid.length); + if (session->gssapi->mech.elements == NULL){ + ssh_set_error_oom(session); + return SSH_ERROR; + } + memcpy(session->gssapi->mech.elements, oid.elements, oid.length); + gss_release_oid_set(&min_stat, &selected); + session->gssapi->user = strdup(user); + session->gssapi->service = service_name; + session->gssapi->state = SSH_GSSAPI_STATE_RCV_TOKEN; + return ssh_gssapi_send_response(session, oids[i]); +} + +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, + "converting name", + maj_stat, + min_stat); + ptr = malloc(buffer.length + 1); + if (ptr == NULL) { + return NULL; + } + memcpy(ptr, buffer.value, buffer.length); + ptr[buffer.length] = '\0'; + gss_release_buffer(&min_stat, &buffer); + return ptr; + +} + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server){ + ssh_string token; + char *hexa; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token = GSS_C_EMPTY_BUFFER; + gss_name_t client_name = GSS_C_NO_NAME; + OM_uint32 ret_flags=0; + gss_channel_bindings_t input_bindings=GSS_C_NO_CHANNEL_BINDINGS; + int rc; + + (void)user; + (void)type; + + SSH_LOG(SSH_LOG_PACKET,"Received SSH_MSG_USERAUTH_GSSAPI_TOKEN"); + if (!session->gssapi || session->gssapi->state != SSH_GSSAPI_STATE_RCV_TOKEN){ + ssh_set_error(session, SSH_FATAL, "Received SSH_MSG_USERAUTH_GSSAPI_TOKEN in invalid state"); + return SSH_PACKET_USED; + } + token = ssh_buffer_get_ssh_string(packet); + + if (token == NULL){ + ssh_set_error(session, SSH_REQUEST_DENIED, "ssh_packet_userauth_gssapi_token: invalid packet"); + return SSH_PACKET_USED; + } + + if (ssh_callbacks_exists(session->server_callbacks, gssapi_accept_sec_ctx_function)){ + ssh_string out_token=NULL; + rc = session->server_callbacks->gssapi_accept_sec_ctx_function(session, + token, &out_token, session->server_callbacks->userdata); + if (rc == SSH_ERROR){ + ssh_auth_reply_default(session, 0); + ssh_gssapi_free(session); + session->gssapi=NULL; + return SSH_PACKET_USED; + } + if (ssh_string_len(out_token) != 0){ + rc = ssh_buffer_pack(session->out_buffer, + "bS", + SSH2_MSG_USERAUTH_GSSAPI_TOKEN, + out_token); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_PACKET_USED; + } + ssh_packet_send(session); + SSH_STRING_FREE(out_token); + } else { + session->gssapi->state = SSH_GSSAPI_STATE_RCV_MIC; + } + return SSH_PACKET_USED; + } + hexa = ssh_get_hexa(ssh_string_data(token),ssh_string_len(token)); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI Token : %s",hexa); + SAFE_FREE(hexa); + input_token.length = ssh_string_len(token); + input_token.value = ssh_string_data(token); + + 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, + "accepting token", + maj_stat, + min_stat); + SSH_STRING_FREE(token); + if (client_name != GSS_C_NO_NAME){ + session->gssapi->client_name = client_name; + session->gssapi->canonic_user = ssh_gssapi_name_to_char(client_name); + } + if (GSS_ERROR(maj_stat)){ + ssh_gssapi_log_error(SSH_LOG_WARNING, + "Gssapi error", + maj_stat, + min_stat); + ssh_auth_reply_default(session,0); + ssh_gssapi_free(session); + session->gssapi=NULL; + return SSH_PACKET_USED; + } + + if (output_token.length != 0){ + 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); + ssh_packet_send(session); + } + if(maj_stat == GSS_S_COMPLETE){ + session->gssapi->state = SSH_GSSAPI_STATE_RCV_MIC; + } + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ + +static ssh_buffer ssh_gssapi_build_mic(ssh_session session) +{ + struct ssh_crypto_struct *crypto = NULL; + ssh_buffer mic_buffer = NULL; + int rc; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_BOTH); + if (crypto == NULL) { + return NULL; + } + + mic_buffer = ssh_buffer_new(); + if (mic_buffer == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + rc = ssh_buffer_pack(mic_buffer, + "dPbsss", + crypto->digest_len, + (size_t)crypto->digest_len, crypto->session_id, + SSH2_MSG_USERAUTH_REQUEST, + session->gssapi->user, + "ssh-connection", + "gssapi-with-mic"); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(mic_buffer); + return NULL; + } + + return mic_buffer; +} + +#ifdef WITH_SERVER + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic) +{ + ssh_string mic_token; + 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; + ssh_buffer mic_buffer = NULL; + + (void)user; + (void)type; + + SSH_LOG(SSH_LOG_PACKET,"Received SSH_MSG_USERAUTH_GSSAPI_MIC"); + mic_token = ssh_buffer_get_ssh_string(packet); + if (mic_token == NULL) { + ssh_set_error(session, SSH_FATAL, "Missing MIC in packet"); + goto error; + } + if (session->gssapi == NULL + || session->gssapi->state != SSH_GSSAPI_STATE_RCV_MIC) { + ssh_set_error(session, SSH_FATAL, "Received SSH_MSG_USERAUTH_GSSAPI_MIC in invalid state"); + goto error; + } + + mic_buffer = ssh_gssapi_build_mic(session); + if (mic_buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + if (ssh_callbacks_exists(session->server_callbacks, gssapi_verify_mic_function)){ + int rc = session->server_callbacks->gssapi_verify_mic_function(session, mic_token, + ssh_buffer_get(mic_buffer), ssh_buffer_get_len(mic_buffer), + session->server_callbacks->userdata); + if (rc != SSH_OK) { + goto error; + } + } else { + mic_buf.length = ssh_buffer_get_len(mic_buffer); + mic_buf.value = ssh_buffer_get(mic_buffer); + mic_token_buf.length = ssh_string_len(mic_token); + 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, + "verifying MIC", + maj_stat, + min_stat); + if (maj_stat == GSS_S_DEFECTIVE_TOKEN || GSS_ERROR(maj_stat)) { + goto error; + } + } + + if (ssh_callbacks_exists(session->server_callbacks, auth_gssapi_mic_function)){ + switch(session->server_callbacks->auth_gssapi_mic_function(session, + session->gssapi->user, session->gssapi->canonic_user, + session->server_callbacks->userdata)){ + case SSH_AUTH_SUCCESS: + ssh_auth_reply_success(session, 0); + break; + case SSH_AUTH_PARTIAL: + ssh_auth_reply_success(session, 1); + break; + default: + ssh_auth_reply_default(session, 0); + break; + } + } + + goto end; + +error: + ssh_auth_reply_default(session,0); + +end: + ssh_gssapi_free(session); + if (mic_buffer != NULL) { + SSH_BUFFER_FREE(mic_buffer); + } + if (mic_token != NULL) { + SSH_STRING_FREE(mic_token); + } + + return SSH_PACKET_USED; +} + +/** @brief returns the client credentials of the connected client. + * If the client has given a forwardable token, the SSH server will + * retrieve it. + * @returns gssapi credentials handle. + * @returns NULL if no forwardable token is available. + */ +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; +} + +#endif /* SERVER */ + +/** + * @brief Set the forwadable 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] creds gssapi credentials handle. + */ +void ssh_gssapi_set_creds(ssh_session session, const ssh_gssapi_creds creds) +{ + if (session == NULL) { + return; + } + if (session->gssapi == NULL) { + ssh_gssapi_init(session); + if (session->gssapi == NULL) { + return; + } + } + + 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){ + int rc; + int i; + + rc = ssh_buffer_pack(session->out_buffer, + "bsssd", + SSH2_MSG_USERAUTH_REQUEST, + session->opts.username, + "ssh-connection", + "gssapi-with-mic", + n_oid); + + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto fail; + } + + for (i=0; iout_buffer, oid_set[i]); + if (rc < 0) { + goto fail; + } + } + + session->auth.state = SSH_AUTH_STATE_GSSAPI_REQUEST_SENT; + return ssh_packet_send(session); +fail: + ssh_buffer_reinit(session->out_buffer); + return SSH_ERROR; +} + +/** @brief returns the OIDs of the mechs that have usable credentials + */ +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_buffer_desc namebuf; + gss_name_t client_id = GSS_C_NO_NAME; + gss_OID oid; + unsigned int i; + char *ptr; + int ret; + + if (session->gssapi->client.client_deleg_creds == NULL) { + if (session->opts.gss_client_identity != NULL) { + namebuf.value = (void *)session->opts.gss_client_identity; + namebuf.length = strlen(session->opts.gss_client_identity); + + maj_stat = gss_import_name(&min_stat, &namebuf, + GSS_C_NT_USER_NAME, &client_id); + if (GSS_ERROR(maj_stat)) { + ret = SSH_ERROR; + goto end; + } + } + + maj_stat = gss_acquire_cred(&min_stat, client_id, GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, GSS_C_INITIATE, + &session->gssapi->client.creds, + &actual_mechs, NULL); + if (GSS_ERROR(maj_stat)) { + ret = SSH_ERROR; + goto end; + } + } else { + session->gssapi->client.creds = + session->gssapi->client.client_deleg_creds; + + maj_stat = gss_inquire_cred(&min_stat, session->gssapi->client.creds, + &client_id, NULL, NULL, &actual_mechs); + if (GSS_ERROR(maj_stat)) { + ret = SSH_ERROR; + goto end; + } + } + + gss_create_empty_oid_set(&min_stat, valid_oids); + + /* double check each single cred */ + for (i = 0; i < actual_mechs->count; i++) { + /* check lifetime is not 0 or skip */ + lifetime = 0; + oid = &actual_mechs->elements[i]; + maj_stat = gss_inquire_cred_by_mech(&min_stat, + session->gssapi->client.creds, + oid, NULL, &lifetime, NULL, NULL); + if (maj_stat == GSS_S_COMPLETE && lifetime > 0) { + gss_add_oid_set_member(&min_stat, oid, valid_oids); + ptr = ssh_get_hexa(oid->elements, oid->length); + SSH_LOG(SSH_LOG_DEBUG, "GSSAPI valid oid %d : %s", i, ptr); + SAFE_FREE(ptr); + } + } + + ret = SSH_OK; + +end: + gss_release_name(&min_stat, &client_id); + return ret; +} + +/** + * @brief launches a gssapi-with-mic auth request + * @returns SSH_AUTH_ERROR: A serious error happened\n + * SSH_AUTH_DENIED: Authentication failed : use another method\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + */ +int ssh_gssapi_auth_mic(ssh_session session){ + size_t i; + gss_OID_set selected; /* oid selected for authentication */ + ssh_string *oids = NULL; + int rc; + size_t n_oids = 0; + OM_uint32 maj_stat, min_stat; + char name_buf[256] = {0}; + gss_buffer_desc hostname; + const char *gss_host = session->opts.host; + + rc = ssh_gssapi_init(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + if (session->opts.gss_server_identity != NULL) { + gss_host = session->opts.gss_server_identity; + } + /* import target host name */ + snprintf(name_buf, sizeof(name_buf), "host@%s", gss_host); + + hostname.value = name_buf; + hostname.length = strlen(name_buf) + 1; + maj_stat = gss_import_name(&min_stat, &hostname, + (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, + "importing name", + maj_stat, + min_stat); + return SSH_AUTH_DENIED; + } + + /* copy username */ + session->gssapi->user = strdup(session->opts.username); + if (session->gssapi->user == NULL) { + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "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) { + return SSH_AUTH_DENIED; + } + + n_oids = selected->count; + SSH_LOG(SSH_LOG_PROTOCOL, "Sending %zu oids", n_oids); + + oids = calloc(n_oids, sizeof(ssh_string)); + if (oids == NULL) { + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + + for (i=0; ielements[i].length + 2); + if (oids[i] == NULL) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto out; + } + ((unsigned char *)oids[i]->data)[0] = SSH_OID_TAG; + ((unsigned char *)oids[i]->data)[1] = selected->elements[i].length; + memcpy((unsigned char *)oids[i]->data + 2, selected->elements[i].elements, + selected->elements[i].length); + } + + rc = ssh_gssapi_send_auth_mic(session, oids, n_oids); + +out: + for (i = 0; i < n_oids; i++) { + SSH_STRING_FREE(oids[i]); + } + free(oids); + if (rc != SSH_ERROR) { + return SSH_AUTH_AGAIN; + } + + return SSH_AUTH_ERROR; +} + +static gss_OID ssh_gssapi_oid_from_string(ssh_string oid_s) +{ + gss_OID ret = NULL; + unsigned char *data = ssh_string_data(oid_s); + size_t len = ssh_string_len(oid_s); + + if (data == NULL) { + return NULL; + } + + if (len > 256 || len <= 2) { + SAFE_FREE(ret); + return NULL; + } + + if (data[0] != SSH_OID_TAG || data[1] != len - 2) { + SAFE_FREE(ret); + return NULL; + } + + ret = malloc(sizeof(gss_OID_desc)); + if (ret == NULL) { + return NULL; + } + + ret->elements = malloc(len - 2); + if (ret->elements == NULL) { + SAFE_FREE(ret); + return NULL; + } + memcpy(ret->elements, &data[2], len-2); + ret->length = len-2; + + return ret; +} + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response){ + ssh_string oid_s; + gss_uint32 maj_stat, min_stat; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + char *hexa; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_PACKET, "Received SSH_USERAUTH_GSSAPI_RESPONSE"); + if (session->auth.state != SSH_AUTH_STATE_GSSAPI_REQUEST_SENT){ + ssh_set_error(session, SSH_FATAL, "Invalid state in ssh_packet_userauth_gssapi_response"); + goto error; + } + + oid_s = ssh_buffer_get_ssh_string(packet); + if (!oid_s){ + ssh_set_error(session, SSH_FATAL, "Missing OID"); + goto error; + } + session->gssapi->client.oid = ssh_gssapi_oid_from_string(oid_s); + SSH_STRING_FREE(oid_s); + if (!session->gssapi->client.oid) { + ssh_set_error(session, SSH_FATAL, "Invalid OID"); + goto error; + } + + session->gssapi->client.flags = GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG; + if (session->opts.gss_delegate_creds) { + session->gssapi->client.flags |= GSS_C_DELEG_FLAG; + } + + /* prepare the first TOKEN response */ + maj_stat = gss_init_sec_context(&min_stat, + session->gssapi->client.creds, + &session->gssapi->ctx, + session->gssapi->client.server_name, + session->gssapi->client.oid, + session->gssapi->client.flags, + 0, NULL, &input_token, NULL, + &output_token, NULL, NULL); + if(GSS_ERROR(maj_stat)){ + ssh_gssapi_log_error(SSH_LOG_WARNING, + "Initializing gssapi context", + maj_stat, + min_stat); + goto error; + } + if (output_token.length != 0){ + 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); + ssh_packet_send(session); + session->auth.state = SSH_AUTH_STATE_GSSAPI_TOKEN; + } + return SSH_PACKET_USED; + +error: + session->auth.state = SSH_AUTH_STATE_ERROR; + ssh_gssapi_free(session); + session->gssapi = NULL; + return SSH_PACKET_USED; +} + +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; + ssh_buffer mic_buffer; + int rc; + + SSH_LOG(SSH_LOG_PACKET,"Sending SSH_MSG_USERAUTH_GSSAPI_MIC"); + + mic_buffer = ssh_gssapi_build_mic(session); + if (mic_buffer == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + mic_buf.length = ssh_buffer_get_len(mic_buffer); + mic_buf.value = ssh_buffer_get(mic_buffer); + + maj_stat = gss_get_mic(&min_stat,session->gssapi->ctx, GSS_C_QOP_DEFAULT, + &mic_buf, &mic_token_buf); + if (GSS_ERROR(maj_stat)){ + SSH_BUFFER_FREE(mic_buffer); + ssh_gssapi_log_error(SSH_LOG_PROTOCOL, + "generating MIC", + maj_stat, + min_stat); + return SSH_ERROR; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_USERAUTH_GSSAPI_MIC, + mic_token_buf.length, + (size_t)mic_token_buf.length, mic_token_buf.value); + if (rc != SSH_OK) { + SSH_BUFFER_FREE(mic_buffer); + ssh_set_error_oom(session); + return SSH_ERROR; + } + + return ssh_packet_send(session); +} + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client){ + ssh_string token; + char *hexa; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token = GSS_C_EMPTY_BUFFER; + (void)user; + (void)type; + + SSH_LOG(SSH_LOG_PACKET,"Received SSH_MSG_USERAUTH_GSSAPI_TOKEN"); + if (!session->gssapi || session->auth.state != SSH_AUTH_STATE_GSSAPI_TOKEN) { + ssh_set_error(session, SSH_FATAL, + "Received SSH_MSG_USERAUTH_GSSAPI_TOKEN in invalid state"); + goto error; + } + token = ssh_buffer_get_ssh_string(packet); + + if (token == NULL){ + ssh_set_error(session, SSH_REQUEST_DENIED, + "ssh_packet_userauth_gssapi_token: invalid packet"); + goto error; + } + + hexa = ssh_get_hexa(ssh_string_data(token),ssh_string_len(token)); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI Token : %s",hexa); + SAFE_FREE(hexa); + input_token.length = ssh_string_len(token); + input_token.value = ssh_string_data(token); + maj_stat = gss_init_sec_context(&min_stat, + session->gssapi->client.creds, + &session->gssapi->ctx, + session->gssapi->client.server_name, + session->gssapi->client.oid, + session->gssapi->client.flags, + 0, NULL, &input_token, NULL, + &output_token, NULL, NULL); + + ssh_gssapi_log_error(SSH_LOG_PROTOCOL, + "accepting token", + maj_stat, + min_stat); + SSH_STRING_FREE(token); + if (GSS_ERROR(maj_stat)){ + ssh_gssapi_log_error(SSH_LOG_PROTOCOL, + "Gssapi error", + maj_stat, + min_stat); + goto error; + } + + if (output_token.length != 0) { + 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); + ssh_packet_send(session); + } + + if (maj_stat == GSS_S_COMPLETE) { + ssh_gssapi_send_mic(session); + session->auth.state = SSH_AUTH_STATE_GSSAPI_MIC_SENT; + } + + return SSH_PACKET_USED; + +error: + session->auth.state = SSH_AUTH_STATE_ERROR; + ssh_gssapi_free(session); + session->gssapi = NULL; + return SSH_PACKET_USED; +} diff --git a/src/gzip.c b/src/gzip.c new file mode 100644 index 0000000..f981de9 --- /dev/null +++ b/src/gzip.c @@ -0,0 +1,232 @@ +/* + * gzip.c - hooks for compression of packets + * + * This file is part of the SSH Library + * + * Copyright (c) 2003 by Aris Adamantiadis + * Copyright (c) 2009 by Andreas Schneider + * + * 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 +#include +#include + +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/session.h" + +#define BLOCKSIZE 4092 + +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; + } + + 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; + } + + return stream; +} + +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); + 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; + } + 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; + + 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_add_data(buf, ssh_buffer_get(dest), ssh_buffer_get_len(dest)) < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } + + SSH_BUFFER_FREE(dest); + return 0; +} + +/* decompression */ + +static z_stream *initdecompress(ssh_session session) { + z_stream *stream = NULL; + int status; + + 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; + } + + return stream; +} + +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; + } + } + + 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; + } + + 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; + + 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_add_data(buf, ssh_buffer_get(dest), ssh_buffer_get_len(dest)) < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } + + SSH_BUFFER_FREE(dest); + return 0; +} diff --git a/src/init.c b/src/init.c new file mode 100644 index 0000000..9e70bf6 --- /dev/null +++ b/src/init.c @@ -0,0 +1,253 @@ +/* + * init.c - initialization and finalization of the library + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-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/priv.h" +#include "libssh/socket.h" +#include "libssh/dh.h" +#include "libssh/poll.h" +#include "libssh/threads.h" + +#ifdef _WIN32 +#include +#endif + +#ifdef HAVE_CONSTRUCTOR_ATTRIBUTE +#define CONSTRUCTOR_ATTRIBUTE __attribute__((constructor)) +#else +#define CONSTRUCTOR_ATTRIBUTE +#endif /* HAVE_CONSTRUCTOR_ATTRIBUTE */ + +#ifdef HAVE_DESTRUCTOR_ATTRIBUTE +#define DESTRUCTOR_ATTRIBUTE __attribute__((destructor)) +#else +#define DESTRUCTOR_ATTRIBUTE +#endif /* HAVE_DESTRUCTOR_ATTRIBUTE */ + +/* Declare static mutex */ +static SSH_MUTEX ssh_init_mutex = SSH_MUTEX_STATIC_INIT; + +/* Counter for initializations */ +static int _ssh_initialized = 0; + +/* Cache the returned value */ +static int _ssh_init_ret = 0; + +void libssh_constructor(void) CONSTRUCTOR_ATTRIBUTE; +void libssh_destructor(void) DESTRUCTOR_ATTRIBUTE; + +static int _ssh_init(unsigned constructor) { + + int rc = 0; + + if (!constructor) { + ssh_mutex_lock(&ssh_init_mutex); + } + + _ssh_initialized++; + + if (_ssh_initialized > 1) { + rc = _ssh_init_ret; + goto _ret; + } + + rc = ssh_threads_init(); + if (rc) { + goto _ret; + } + + rc = ssh_crypto_init(); + if (rc) { + goto _ret; + } + + rc = ssh_dh_init(); + if (rc) { + goto _ret; + } + + rc = ssh_socket_init(); + if (rc) { + goto _ret; + } + +_ret: + _ssh_init_ret = rc; + + if (!constructor) { + ssh_mutex_unlock(&ssh_init_mutex); + } + + return rc; +} + +/** + * @brief Initialize global cryptographic data structures. + * + * This functions is automatically called when the library is loaded. + * + */ +void libssh_constructor(void) +{ + + int rc; + + rc = _ssh_init(1); + + if (rc < 0) { + fprintf(stderr, "Error in auto_init()\n"); + } + + return; +} + +/** + * @defgroup libssh The libssh API + * + * 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. + * + * @{ + */ + +/** + * @brief Initialize global cryptographic data structures. + * + * Since version 0.8.0, it is not necessary to call this function on systems + * which are fully supported with regards to threading (that is, system with + * pthreads available). + * + * If the library is already initialized, increments the _ssh_initialized + * counter and return the error code cached in _ssh_init_ret. + * + * @returns SSH_OK on success, SSH_ERROR if an error occurred. + */ +int ssh_init(void) { + return _ssh_init(0); +} + +static int _ssh_finalize(unsigned destructor) { + + if (!destructor) { + ssh_mutex_lock(&ssh_init_mutex); + + if (_ssh_initialized > 1) { + _ssh_initialized--; + goto _ret; + } + + if (_ssh_initialized == 1) { + if (_ssh_init_ret < 0) { + goto _ret; + } + } + } + + /* If the counter reaches zero or it is the destructor calling, finalize */ + ssh_dh_finalize(); + ssh_crypto_finalize(); + ssh_socket_cleanup(); + /* It is important to finalize threading after CRYPTO because + * it still depends on it */ + ssh_threads_finalize(); + + _ssh_initialized = 0; + +_ret: + if (!destructor) { + ssh_mutex_unlock(&ssh_init_mutex); + } + return 0; +} + +/** + * @brief Finalize and cleanup all libssh and cryptographic data structures. + * + * This function is automatically called when the library is unloaded. + * + */ +void libssh_destructor(void) +{ + int rc; + + rc = _ssh_finalize(1); + + if (rc < 0) { + fprintf(stderr, "Error in libssh_destructor()\n"); + } +} + +/** + * @brief Finalize and cleanup all libssh and cryptographic data structures. + * + * Since version 0.8.0, it is not necessary to call this function, since it is + * automatically called when the library is unloaded. + * + * If ssh_init() is called explicitly, then ssh_finalize() must be called + * explicitly. + * + * When called, decrements the counter _ssh_initialized. If the counter reaches + * zero, then the libssh and cryptographic data structures are cleaned up. + * + * @returns 0 on succes, -1 if an error occured. + * + @returns 0 otherwise + */ +int ssh_finalize(void) { + return _ssh_finalize(0); +} + +#ifdef _WIN32 + +#if defined(_MSC_VER) && !defined(LIBSSH_STATIC) +/* Library constructor and destructor */ +BOOL WINAPI DllMain(HINSTANCE hinstDLL, + DWORD fdwReason, + LPVOID lpvReserved) +{ + int rc = 0; + + switch(fdwReason) { + case DLL_PROCESS_ATTACH: + rc = _ssh_init(1); + if (rc != 0) { + fprintf(stderr, "DllMain: ssh_init failed!"); + return FALSE; + } + break; + case DLL_PROCESS_DETACH: + _ssh_finalize(1); + break; + default: + break; + } + + return TRUE; +} +#endif /* _MSC_VER && !LIBSSH_STATIC */ + +#endif /* _WIN32 */ + +/** @} */ diff --git a/src/kdf.c b/src/kdf.c new file mode 100644 index 0000000..0e90e18 --- /dev/null +++ b/src/kdf.c @@ -0,0 +1,167 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * Copyrihgt (c) 2018 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 +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/dh.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" + +#include "libssh/string.h" + + +/* The following implements the SSHKDF for crypto backend that + * do not have a native implementations */ +struct ssh_mac_ctx_struct { + enum ssh_kdf_digest digest_type; + union { + SHACTX sha1_ctx; + SHA256CTX sha256_ctx; + SHA384CTX sha384_ctx; + SHA512CTX sha512_ctx; + } ctx; +}; + +static ssh_mac_ctx ssh_mac_ctx_init(enum ssh_kdf_digest type) +{ + ssh_mac_ctx ctx = malloc(sizeof(struct ssh_mac_ctx_struct)); + if (ctx == NULL) { + return NULL; + } + + ctx->digest_type = type; + switch(type){ + case SSH_KDF_SHA1: + ctx->ctx.sha1_ctx = sha1_init(); + return ctx; + case SSH_KDF_SHA256: + ctx->ctx.sha256_ctx = sha256_init(); + return ctx; + case SSH_KDF_SHA384: + ctx->ctx.sha384_ctx = sha384_init(); + return ctx; + case SSH_KDF_SHA512: + ctx->ctx.sha512_ctx = sha512_init(); + return ctx; + default: + SAFE_FREE(ctx); + return NULL; + } +} + +static void ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) +{ + switch(ctx->digest_type){ + case SSH_KDF_SHA1: + sha1_update(ctx->ctx.sha1_ctx, data, len); + break; + case SSH_KDF_SHA256: + sha256_update(ctx->ctx.sha256_ctx, data, len); + break; + case SSH_KDF_SHA384: + sha384_update(ctx->ctx.sha384_ctx, data, len); + break; + case SSH_KDF_SHA512: + sha512_update(ctx->ctx.sha512_ctx, data, len); + break; + } +} + +static void ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) +{ + switch(ctx->digest_type){ + case SSH_KDF_SHA1: + sha1_final(md,ctx->ctx.sha1_ctx); + break; + case SSH_KDF_SHA256: + sha256_final(md,ctx->ctx.sha256_ctx); + break; + case SSH_KDF_SHA384: + sha384_final(md,ctx->ctx.sha384_ctx); + break; + case SSH_KDF_SHA512: + sha512_final(md,ctx->ctx.sha512_ctx); + break; + } + SAFE_FREE(ctx); +} + +int sshkdf_derive_key(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + int 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; + + if (DIGEST_MAX_LEN < crypto->digest_len) { + return -1; + } + + ctx = ssh_mac_ctx_init(crypto->digest_type); + 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, &letter, 1); + ssh_mac_update(ctx, crypto->session_id, crypto->digest_len); + ssh_mac_final(digest, ctx); + + if (requested_len < output_len) { + output_len = requested_len; + } + memcpy(output, digest, output_len); + + while (requested_len > output_len) { + ctx = ssh_mac_ctx_init(crypto->digest_type); + 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); + if (requested_len < output_len + crypto->digest_len) { + memcpy(output + output_len, digest, requested_len - output_len); + } else { + memcpy(output + output_len, digest, crypto->digest_len); + } + output_len += crypto->digest_len; + } + + return 0; +} diff --git a/src/kex.c b/src/kex.c new file mode 100644 index 0000000..80b6e8a --- /dev/null +++ b/src/kex.c @@ -0,0 +1,1417 @@ +/* + * kex.c - key exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 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 +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/dh.h" +#ifdef WITH_GEX +#include "libssh/dh-gex.h" +#endif /* WITH_GEX */ +#include "libssh/kex.h" +#include "libssh/session.h" +#include "libssh/ssh2.h" +#include "libssh/string.h" +#include "libssh/curve25519.h" +#include "libssh/knownhosts.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" +#include "libssh/token.h" + +#ifdef WITH_BLOWFISH_CIPHER +# if defined(HAVE_OPENSSL_BLOWFISH_H) || defined(HAVE_LIBGCRYPT) || defined(HAVE_LIBMBEDCRYPTO) +# define BLOWFISH "blowfish-cbc," +# else +# define BLOWFISH "" +# endif +#else +# define BLOWFISH "" +#endif + +#ifdef HAVE_LIBGCRYPT +# define AES "aes256-gcm@openssh.com,aes128-gcm@openssh.com," \ + "aes256-ctr,aes192-ctr,aes128-ctr," \ + "aes256-cbc,aes192-cbc,aes128-cbc," +# define DES "3des-cbc" +# define DES_SUPPORTED "3des-cbc" + +#elif defined(HAVE_LIBMBEDCRYPTO) +# ifdef MBEDTLS_GCM_C +# define GCM "aes256-gcm@openssh.com,aes128-gcm@openssh.com," +# else +# define GCM "" +# endif /* MBEDTLS_GCM_C */ +# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr," \ + "aes256-cbc,aes192-cbc,aes128-cbc," +# define DES "3des-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 "aes256-cbc,aes192-cbc,aes128-cbc," +# else /* BROKEN_AES_CTR */ +# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc," +# endif /* BROKEN_AES_CTR */ +# else /* HAVE_OPENSSL_AES_H */ +# define AES "" +# endif /* HAVE_OPENSSL_AES_H */ + +# define DES "3des-cbc" +# define DES_SUPPORTED "3des-cbc" +#endif /* HAVE_LIBCRYPTO */ + +#ifdef WITH_ZLIB +#define ZLIB "none,zlib,zlib@openssh.com" +#else +#define ZLIB "none" +#endif + +#ifdef HAVE_CURVE25519 +#define CURVE25519 "curve25519-sha256,curve25519-sha256@libssh.org," +#else +#define CURVE25519 "" +#endif + +#ifdef HAVE_ECDH +#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," \ + "ecdsa-sha2-nistp384-cert-v01@openssh.com," \ + "ecdsa-sha2-nistp256-cert-v01@openssh.com," +#else +#define EC_HOSTKEYS "" +#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 + +#define HOSTKEYS "ssh-ed25519," \ + EC_HOSTKEYS \ + "rsa-sha2-512," \ + "rsa-sha2-256," \ + "ssh-rsa" \ + DSA_HOSTKEYS +#define PUBLIC_KEY_ALGORITHMS "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 "," \ + HOSTKEYS + +#ifdef WITH_GEX +#define GEX_SHA256 "diffie-hellman-group-exchange-sha256," +#define GEX_SHA1 "diffie-hellman-group-exchange-sha1," +#else +#define GEX_SHA256 +#define GEX_SHA1 +#endif /* WITH_GEX */ + +#define CHACHA20 "chacha20-poly1305@openssh.com," + +#define 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" +#define KEY_EXCHANGE_SUPPORTED \ + GEX_SHA1 \ + KEY_EXCHANGE + +/* RFC 8308 */ +#define KEX_EXTENSION_CLIENT "ext-info-c" + +/* Allowed algorithms in FIPS mode */ +#define FIPS_ALLOWED_CIPHERS "aes256-gcm@openssh.com,"\ + "aes256-ctr,"\ + "aes256-cbc,"\ + "aes128-gcm@openssh.com,"\ + "aes128-ctr,"\ + "aes128-cbc" + +#define FIPS_ALLOWED_HOSTKEYS EC_HOSTKEYS \ + "rsa-sha2-512," \ + "rsa-sha2-256" + +#define FIPS_ALLOWED_PUBLIC_KEY_ALGORITHMS EC_PUBLIC_KEY_ALGORITHMS \ + "rsa-sha2-512-cert-v01@openssh.com," \ + "rsa-sha2-256-cert-v01@openssh.com," \ + FIPS_ALLOWED_HOSTKEYS + +#define FIPS_ALLOWED_KEX "ecdh-sha2-nistp256,"\ + "ecdh-sha2-nistp384,"\ + "ecdh-sha2-nistp521,"\ + "diffie-hellman-group-exchange-sha256,"\ + "diffie-hellman-group14-sha256,"\ + "diffie-hellman-group16-sha512,"\ + "diffie-hellman-group18-sha512" + +#define FIPS_ALLOWED_MACS "hmac-sha2-256-etm@openssh.com,"\ + "hmac-sha1-etm@openssh.com,"\ + "hmac-sha2-512-etm@openssh.com,"\ + "hmac-sha2-256,"\ + "hmac-sha1,"\ + "hmac-sha2-512" + +/* NOTE: This is a fixed API and the index is defined by ssh_kex_types_e */ +static const char *fips_methods[] = { + FIPS_ALLOWED_KEX, + FIPS_ALLOWED_PUBLIC_KEY_ALGORITHMS, + FIPS_ALLOWED_CIPHERS, + FIPS_ALLOWED_CIPHERS, + FIPS_ALLOWED_MACS, + FIPS_ALLOWED_MACS, + ZLIB, + ZLIB, + "", + "", + NULL +}; + +/* NOTE: This is a fixed API and the index is defined by ssh_kex_types_e */ +static const char *default_methods[] = { + KEY_EXCHANGE, + PUBLIC_KEY_ALGORITHMS, + AES BLOWFISH DES, + AES BLOWFISH 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", + "", + "", + NULL +}; + +/* NOTE: This is a fixed API and the index is defined by ssh_kex_types_e */ +static const char *supported_methods[] = { + KEY_EXCHANGE_SUPPORTED, + PUBLIC_KEY_ALGORITHMS, + CHACHA20 AES BLOWFISH DES_SUPPORTED, + CHACHA20 AES BLOWFISH DES_SUPPORTED, + "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", + ZLIB, + ZLIB, + "", + "", + NULL +}; + +/* descriptions of the key exchange packet */ +static const char *ssh_kex_descriptions[] = { + "kex algos", + "server host key algo", + "encryption client->server", + "encryption server->client", + "mac algo client->server", + "mac algo server->client", + "compression algo client->server", + "compression algo server->client", + "languages client->server", + "languages server->client", + NULL +}; + +const char *ssh_kex_get_default_methods(uint32_t algo) +{ + if (algo >= SSH_KEX_METHODS) { + return NULL; + } + + return default_methods[algo]; +} + +const char *ssh_kex_get_supported_method(uint32_t algo) +{ + if (algo >= SSH_KEX_METHODS) { + return NULL; + } + + return supported_methods[algo]; +} + +const char *ssh_kex_get_description(uint32_t algo) { + if (algo >= SSH_KEX_METHODS) { + return NULL; + } + + return ssh_kex_descriptions[algo]; +} + +const char *ssh_kex_get_fips_methods(uint32_t algo) { + if (algo >= SSH_KEX_METHODS) { + return NULL; + } + + return fips_methods[algo]; +} + +/** + * @internal + * @brief returns whether the first client key exchange algorithm or + * hostkey type matches its server counterpart + * @returns whether the first client key exchange algorithm or hostkey type + * matches its server counterpart + */ +static int cmp_first_kex_algo(const char *client_str, + const char *server_str) { + size_t client_kex_len; + size_t server_kex_len; + + char *colon; + + int is_wrong = 1; + + colon = strchr(client_str, ','); + if (colon == NULL) { + client_kex_len = strlen(client_str); + } else { + client_kex_len = colon - client_str; + } + + colon = strchr(server_str, ','); + if (colon == NULL) { + server_kex_len = strlen(server_str); + } else { + server_kex_len = colon - server_str; + } + + if (client_kex_len != server_kex_len) { + return is_wrong; + } + + is_wrong = (strncmp(client_str, server_str, client_kex_len) != 0); + + return is_wrong; +} + +SSH_PACKET_CALLBACK(ssh_packet_kexinit) +{ + int i, ok; + int server_kex = session->server; + ssh_string str = NULL; + char *strings[SSH_KEX_METHODS] = {0}; + char *rsa_sig_ext = NULL; + int rc = SSH_ERROR; + size_t len; + + uint8_t first_kex_packet_follows = 0; + uint32_t kexinit_reserved = 0; + + (void)type; + (void)user; + + if (session->session_state == SSH_SESSION_STATE_AUTHENTICATED) { + SSH_LOG(SSH_LOG_INFO, "Initiating key re-exchange"); + } else if (session->session_state != SSH_SESSION_STATE_INITIAL_KEX) { + 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); + if (len != 16) { + 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); + if (ok < 0) { + ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed"); + goto error; + } + } else { + len = ssh_buffer_get_data(packet,session->next_crypto->server_kex.cookie, 16); + if (len != 16) { + 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); + if (ok < 0) { + ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed"); + goto error; + } + } + + for (i = 0; i < SSH_KEX_METHODS; i++) { + str = ssh_buffer_get_ssh_string(packet); + if (str == NULL) { + goto error; + } + + 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"); + goto error; + } + + strings[i] = ssh_string_to_char(str); + if (strings[i] == NULL) { + ssh_set_error_oom(session); + goto error; + } + SSH_STRING_FREE(str); + str = NULL; + } + + /* copy the server kex info into an array of strings */ + if (server_kex) { + for (i = 0; i < SSH_KEX_METHODS; i++) { + session->next_crypto->client_kex.methods[i] = strings[i]; + } + } else { /* client */ + for (i = 0; i < SSH_KEX_METHODS; i++) { + session->next_crypto->server_kex.methods[i] = strings[i]; + } + } + + /* + * Handle the two final fields for the KEXINIT message (RFC 4253 7.1): + * + * boolean first_kex_packet_follows + * uint32 0 (reserved for future extension) + * + * Notably if clients set 'first_kex_packet_follows', it is expected + * 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_add_u32(session->in_hashbuf, kexinit_reserved); + if (rc < 0) { + goto error; + } + + /* + * 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], + KEX_EXTENSION_CLIENT); + if (ok) { + const char *hostkeys = NULL; + + /* The client supports extension negotiation */ + session->extensions |= SSH_EXT_NEGOTIATION; + /* + * RFC 8332 Section 3.1: Use for Server Authentication + * Check what algorithms were provided in the SSH_HOSTKEYS list + * 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]; + 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 (is_allowed != NULL) { + session->extensions |= SSH_EXT_SIG_RSA_SHA512; + } + SAFE_FREE(is_allowed); + } + } + 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 (is_allowed != NULL) { + session->extensions |= SSH_EXT_SIG_RSA_SHA256; + } + SAFE_FREE(is_allowed); + } + } + + /* + * Ensure that the client preference is honored for the case + * both signature types are enabled. + */ + if ((session->extensions & SSH_EXT_SIG_RSA_SHA256) && + (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]); + if (rsa_sig_ext == NULL) { + goto error; /* should never happen */ + } else if (strcmp(rsa_sig_ext, "rsa-sha2-512") == 0) { + session->extensions |= SSH_EXT_SIG_RSA_SHA512; + } else if (strcmp(rsa_sig_ext, "rsa-sha2-256") == 0) { + session->extensions |= SSH_EXT_SIG_RSA_SHA256; + } else { + SAFE_FREE(rsa_sig_ext); + goto error; /* should never happen */ + } + SAFE_FREE(rsa_sig_ext); + } + + SSH_LOG(SSH_LOG_DEBUG, "The client supports extension " + "negotiation. Enabled signature algorithms: %s%s", + 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]); + } + } + + /* 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; + session->ssh_connection_callback(session); + return SSH_PACKET_USED; + +error: + SSH_STRING_FREE(str); + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (server_kex) { + session->next_crypto->client_kex.methods[i] = NULL; + } else { /* client */ + session->next_crypto->server_kex.methods[i] = NULL; + } + SAFE_FREE(strings[i]); + } + + session->session_state = SSH_SESSION_STATE_ERROR; + + return SSH_PACKET_USED; +} + +void ssh_list_kex(struct ssh_kex_struct *kex) { + int i = 0; + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("session cookie", kex->cookie, 16); +#endif + + for(i = 0; i < SSH_KEX_METHODS; i++) { + if (kex->methods[i] == NULL) { + continue; + } + SSH_LOG(SSH_LOG_FUNCTIONS, "%s: %s", + ssh_kex_descriptions[i], kex->methods[i]); + } +} + +/** + * @internal + * + * @brief selects the hostkey mechanisms to be chosen for the key exchange, + * as some hostkey mechanisms may be present in known_hosts files. + * + * @returns a cstring containing a comma-separated list of hostkey methods. + * NULL if no method matches + */ +char *ssh_client_select_hostkeys(ssh_session session) +{ + const char *wanted = NULL; + char *wanted_without_certs = NULL; + char *known_hosts_algorithms = NULL; + char *known_hosts_ordered = NULL; + char *new_hostkeys = NULL; + char *fips_hostkeys = NULL; + + wanted = session->opts.wanted_methods[SSH_HOSTKEYS]; + if (wanted == NULL) { + if (ssh_fips_mode()) { + wanted = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + wanted = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + } + + /* 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, + "List of allowed host key algorithms is empty or contains only " + "unsupported algorithms"); + return NULL; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Order of wanted host keys: \"%s\"", + wanted_without_certs); + + known_hosts_algorithms = ssh_known_hosts_get_algorithms_names(session); + if (known_hosts_algorithms == NULL) { + SSH_LOG(SSH_LOG_DEBUG, + "No key found in known_hosts; " + "changing host key method to \"%s\"", + wanted_without_certs); + + return wanted_without_certs; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Algorithms found in known_hosts files: \"%s\"", + known_hosts_algorithms); + + /* Filter and order the keys from known_hosts according to wanted list */ + known_hosts_ordered = ssh_find_all_matching(known_hosts_algorithms, + wanted_without_certs); + SAFE_FREE(known_hosts_algorithms); + if (known_hosts_ordered == NULL) { + SSH_LOG(SSH_LOG_DEBUG, + "No key found in known_hosts is allowed; " + "changing host key method to \"%s\"", + wanted_without_certs); + + return wanted_without_certs; + } + + /* Append the other supported keys after the preferred ones + * This function tolerates NULL pointers in parameters */ + new_hostkeys = ssh_append_without_duplicates(known_hosts_ordered, + wanted_without_certs); + SAFE_FREE(known_hosts_ordered); + SAFE_FREE(wanted_without_certs); + if (new_hostkeys == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + if (ssh_fips_mode()) { + /* Filter out algorithms not allowed in FIPS mode */ + fips_hostkeys = ssh_keep_fips_algos(SSH_HOSTKEYS, new_hostkeys); + SAFE_FREE(new_hostkeys); + if (fips_hostkeys == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "None of the wanted host keys or keys in known_hosts files " + "is allowed in FIPS mode."); + return NULL; + } + new_hostkeys = fips_hostkeys; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Changing host key method to \"%s\"", + new_hostkeys); + + return new_hostkeys; +} + +/** + * @brief sets the key exchange parameters to be sent to the server, + * in function of the options and available methods. + */ +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; + + ok = ssh_get_random(client->cookie, 16, 0); + if (!ok) { + ssh_set_error(session, SSH_FATAL, "PRNG error"); + 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++) { + if (i == SSH_HOSTKEYS) { + /* Set the hostkeys in the following order: + * - First: keys present in known_hosts files ordered by preference + * - Next: other wanted algorithms ordered by preference */ + client->methods[i] = ssh_client_select_hostkeys(session); + if (client->methods[i] == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + continue; + } + + wanted = session->opts.wanted_methods[i]; + if (wanted == NULL) { + if (ssh_fips_mode()) { + wanted = fips_methods[i]; + } else { + wanted = default_methods[i]; + } + } + client->methods[i] = strdup(wanted); + if (client->methods[i] == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + + /* For rekeying, skip the extension negotiation */ + if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) { + return SSH_OK; + } + + /* Here we append ext-info-c to the list of kex algorithms */ + kex = client->methods[SSH_KEX]; + len = strlen(kex); + if (len + strlen(KEX_EXTENSION_CLIENT) + 2 < 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; + + return SSH_OK; +} + +/** @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; + char *ext_start = NULL; + int i; + + /* Here we should drop the ext-info-c 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'; + } + + 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]); + return SSH_ERROR; + } else if ((i >= SSH_LANG_C_S) && (session->next_crypto->kex_methods[i] == NULL)) { + /* we can safely do that for languages */ + session->next_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", + session->next_crypto->kex_methods[SSH_KEX], + session->next_crypto->kex_methods[SSH_HOSTKEYS], + session->next_crypto->kex_methods[SSH_CRYPT_C_S], + session->next_crypto->kex_methods[SSH_CRYPT_S_C], + session->next_crypto->kex_methods[SSH_MAC_C_S], + session->next_crypto->kex_methods[SSH_MAC_S_C], + session->next_crypto->kex_methods[SSH_COMP_C_S], + session->next_crypto->kex_methods[SSH_COMP_S_C], + session->next_crypto->kex_methods[SSH_LANG_C_S], + session->next_crypto->kex_methods[SSH_LANG_S_C] + ); + return SSH_OK; +} + + +/* 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; + } + + 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; + } + + if (ssh_buffer_add_ssh_string(session->out_hashbuf, str) < 0) { + goto error; + } + if (ssh_buffer_add_ssh_string(session->out_buffer, str) < 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; + } + + if (ssh_packet_send(session) == SSH_ERROR) { + return -1; + } + + 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); + + return -1; +} + +/* + * Key re-exchange (rekey) is triggered by this function. + * It can not be called again after the rekey is initialized! + */ +int ssh_send_rekex(ssh_session session) +{ + int rc; + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + /* Rekey/Key exchange is already in progress */ + SSH_LOG(SSH_LOG_PACKET, "Attempting rekey in bad state"); + return SSH_ERROR; + } + + if (session->current_crypto == NULL) { + /* No current crypto used -- can not exchange it */ + SSH_LOG(SSH_LOG_PACKET, "No crypto to rekey"); + return SSH_ERROR; + } + + if (session->client) { + rc = ssh_set_client_kex(session); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Failed to set client kex"); + return rc; + } + } else { +#ifdef WITH_SERVER + rc = server_set_kex(session); + if (rc == SSH_ERROR) { + SSH_LOG(SSH_LOG_PACKET, "Failed to set server kex"); + return rc; + } +#else + SSH_LOG(SSH_LOG_PACKET, "Invalid session state."); + return SSH_ERROR; +#endif /* WITH_SERVER */ + } + + session->dh_handshake_state = DH_STATE_INIT; + rc = ssh_send_kex(session, session->server); + if (rc < 0) { + SSH_LOG(SSH_LOG_PACKET, "Failed to send kex"); + return rc; + } + + /* Reset the handshake state */ + session->dh_handshake_state = DH_STATE_INIT_SENT; + return SSH_OK; +} + +/* returns a copy of the provided list if everything is supported, + * otherwise a new list of the supported algorithms */ +char *ssh_keep_known_algos(enum ssh_kex_types_e algo, const char *list) +{ + if (algo > SSH_LANG_S_C) { + return NULL; + } + + return ssh_find_all_matching(supported_methods[algo], list); +} + +/** + * @internal + * + * @brief Return a new 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 + * the list; NULL in case of error. + */ +char *ssh_keep_fips_algos(enum ssh_kex_types_e algo, const char *list) +{ + if (algo > SSH_LANG_S_C) { + return NULL; + } + + return ssh_find_all_matching(fips_methods[algo], list); +} + +int ssh_make_sessionid(ssh_session session) +{ + ssh_string num = NULL; + ssh_buffer server_hash = NULL; + ssh_buffer client_hash = NULL; + ssh_buffer buf = NULL; + ssh_string server_pubkey_blob = NULL; + const_bignum client_pubkey, server_pubkey; +#ifdef WITH_GEX + const_bignum modulus, generator; +#endif + int rc = SSH_ERROR; + + buf = ssh_buffer_new(); + if (buf == NULL) { + return rc; + } + + rc = ssh_buffer_pack(buf, + "ss", + session->clientbanner, + session->serverbanner); + if (rc == SSH_ERROR) { + goto error; + } + + if (session->client) { + server_hash = session->in_hashbuf; + client_hash = session->out_hashbuf; + } else { + server_hash = session->out_hashbuf; + 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; + } + + rc = ssh_buffer_pack(buf, + "dPdPS", + ssh_buffer_get_len(client_hash), + ssh_buffer_get_len(client_hash), + ssh_buffer_get(client_hash), + ssh_buffer_get_len(server_hash), + ssh_buffer_get_len(server_hash), + ssh_buffer_get(server_hash), + server_pubkey_blob); + SSH_STRING_FREE(server_pubkey_blob); + if(rc != SSH_OK){ + goto error; + } + + 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: + rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, + DH_CLIENT_KEYPAIR, NULL, &client_pubkey); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, + DH_SERVER_KEYPAIR, NULL, &server_pubkey); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_buffer_pack(buf, + "BB", + client_pubkey, + server_pubkey); + if (rc != SSH_OK) { + goto error; + } + break; +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, + DH_CLIENT_KEYPAIR, NULL, &client_pubkey); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, + DH_SERVER_KEYPAIR, NULL, &server_pubkey); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_dh_get_parameters(session->next_crypto->dh_ctx, + &modulus, &generator); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_buffer_pack(buf, + "dddBBBB", + session->next_crypto->dh_pmin, + session->next_crypto->dh_pn, + session->next_crypto->dh_pmax, + modulus, + generator, + client_pubkey, + server_pubkey); + if (rc != SSH_OK) { + goto error; + } + 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: + if (session->next_crypto->ecdh_client_pubkey == NULL || + session->next_crypto->ecdh_server_pubkey == NULL) { + SSH_LOG(SSH_LOG_WARNING, "ECDH parameted missing"); + goto error; + } + rc = ssh_buffer_pack(buf, + "SS", + session->next_crypto->ecdh_client_pubkey, + session->next_crypto->ecdh_server_pubkey); + if (rc != SSH_OK) { + goto error; + } + break; +#endif +#ifdef HAVE_CURVE25519 + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + rc = ssh_buffer_pack(buf, + "dPdP", + CURVE25519_PUBKEY_SIZE, + (size_t)CURVE25519_PUBKEY_SIZE, session->next_crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE, + (size_t)CURVE25519_PUBKEY_SIZE, session->next_crypto->curve25519_server_pubkey); + + if (rc != SSH_OK) { + goto error; + } + break; +#endif + } + rc = ssh_buffer_pack(buf, "B", session->next_crypto->shared_secret); + if (rc != SSH_OK) { + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("hash buffer", ssh_buffer_get(buf), ssh_buffer_get_len(buf)); +#endif + + switch (session->next_crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: +#endif /* WITH_GEX */ + session->next_crypto->digest_len = SHA_DIGEST_LENGTH; + session->next_crypto->digest_type = SSH_KDF_SHA1; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if (session->next_crypto->secret_hash == NULL) { + ssh_set_error_oom(session); + goto error; + } + sha1(ssh_buffer_get(buf), ssh_buffer_get_len(buf), + session->next_crypto->secret_hash); + break; + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA256: +#endif /* WITH_GEX */ + session->next_crypto->digest_len = SHA256_DIGEST_LENGTH; + session->next_crypto->digest_type = SSH_KDF_SHA256; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if (session->next_crypto->secret_hash == NULL) { + ssh_set_error_oom(session); + goto error; + } + sha256(ssh_buffer_get(buf), ssh_buffer_get_len(buf), + session->next_crypto->secret_hash); + break; + case SSH_KEX_ECDH_SHA2_NISTP384: + session->next_crypto->digest_len = SHA384_DIGEST_LENGTH; + session->next_crypto->digest_type = SSH_KDF_SHA384; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if (session->next_crypto->secret_hash == NULL) { + ssh_set_error_oom(session); + goto error; + } + sha384(ssh_buffer_get(buf), ssh_buffer_get_len(buf), + session->next_crypto->secret_hash); + break; + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_KEX_DH_GROUP18_SHA512: + case SSH_KEX_ECDH_SHA2_NISTP521: + session->next_crypto->digest_len = SHA512_DIGEST_LENGTH; + session->next_crypto->digest_type = SSH_KDF_SHA512; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if (session->next_crypto->secret_hash == NULL) { + ssh_set_error_oom(session); + goto error; + } + sha512(ssh_buffer_get(buf), + ssh_buffer_get_len(buf), + session->next_crypto->secret_hash); + break; + } + /* During the first kex, secret hash and session ID are equal. However, after + * a key re-exchange, a new secret hash is calculated. This hash will not replace + * but complement existing session id. + */ + if (!session->next_crypto->session_id) { + session->next_crypto->session_id = malloc(session->next_crypto->digest_len); + if (session->next_crypto->session_id == NULL) { + ssh_set_error_oom(session); + goto error; + } + memcpy(session->next_crypto->session_id, session->next_crypto->secret_hash, + session->next_crypto->digest_len); + } +#ifdef DEBUG_CRYPTO + printf("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 + + rc = SSH_OK; +error: + SSH_BUFFER_FREE(buf); + SSH_BUFFER_FREE(client_hash); + SSH_BUFFER_FREE(server_hash); + + session->in_hashbuf = NULL; + session->out_hashbuf = NULL; + + SSH_STRING_FREE(num); + + return rc; +} + +int ssh_hashbufout_add_cookie(ssh_session session) +{ + int rc; + + session->out_hashbuf = ssh_buffer_new(); + if (session->out_hashbuf == NULL) { + return -1; + } + + rc = ssh_buffer_allocate_size(session->out_hashbuf, + sizeof(uint8_t) + 16); + if (rc < 0) { + ssh_buffer_reinit(session->out_hashbuf); + return -1; + } + + if (ssh_buffer_add_u8(session->out_hashbuf, 20) < 0) { + ssh_buffer_reinit(session->out_hashbuf); + return -1; + } + + if (session->server) { + if (ssh_buffer_add_data(session->out_hashbuf, + session->next_crypto->server_kex.cookie, 16) < 0) { + ssh_buffer_reinit(session->out_hashbuf); + return -1; + } + } else { + if (ssh_buffer_add_data(session->out_hashbuf, + session->next_crypto->client_kex.cookie, 16) < 0) { + ssh_buffer_reinit(session->out_hashbuf); + return -1; + } + } + + return 0; +} + +int ssh_hashbufin_add_cookie(ssh_session session, unsigned char *cookie) +{ + int rc; + + session->in_hashbuf = ssh_buffer_new(); + if (session->in_hashbuf == NULL) { + return -1; + } + + rc = ssh_buffer_allocate_size(session->in_hashbuf, + sizeof(uint8_t) + 20 + 16); + if (rc < 0) { + ssh_buffer_reinit(session->in_hashbuf); + return -1; + } + + if (ssh_buffer_add_u8(session->in_hashbuf, 20) < 0) { + ssh_buffer_reinit(session->in_hashbuf); + return -1; + } + if (ssh_buffer_add_data(session->in_hashbuf,cookie, 16) < 0) { + ssh_buffer_reinit(session->in_hashbuf); + return -1; + } + + return 0; +} + +int ssh_generate_session_keys(ssh_session session) +{ + ssh_string k_string = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + unsigned char *key = NULL; + unsigned char *IV_cli_to_srv = NULL; + unsigned char *IV_srv_to_cli = NULL; + unsigned char *enckey_cli_to_srv = NULL; + unsigned char *enckey_srv_to_cli = NULL; + unsigned char *intkey_cli_to_srv = NULL; + unsigned char *intkey_srv_to_cli = NULL; + size_t key_len = 0; + size_t IV_len = 0; + size_t enckey_cli_to_srv_len = 0; + size_t enckey_srv_to_cli_len = 0; + size_t intkey_cli_to_srv_len = 0; + size_t intkey_srv_to_cli_len = 0; + int rc = -1; + + k_string = ssh_make_bignum_string(crypto->shared_secret); + if (k_string == NULL) { + ssh_set_error_oom(session); + goto error; + } + /* See RFC4251 Section 5 for the definition of mpint which is the + * encoding we need to use for key in the SSH KDF */ + key = (unsigned char *)k_string; + key_len = ssh_string_len(k_string) + 4; + + IV_len = crypto->digest_len; + if (session->client) { + enckey_cli_to_srv_len = crypto->out_cipher->keysize / 8; + enckey_srv_to_cli_len = crypto->in_cipher->keysize / 8; + intkey_cli_to_srv_len = hmac_digest_len(crypto->out_hmac); + intkey_srv_to_cli_len = hmac_digest_len(crypto->in_hmac); + } else { + enckey_cli_to_srv_len = crypto->in_cipher->keysize / 8; + enckey_srv_to_cli_len = crypto->out_cipher->keysize / 8; + intkey_cli_to_srv_len = hmac_digest_len(crypto->in_hmac); + intkey_srv_to_cli_len = hmac_digest_len(crypto->out_hmac); + } + + IV_cli_to_srv = malloc(IV_len); + IV_srv_to_cli = malloc(IV_len); + enckey_cli_to_srv = malloc(enckey_cli_to_srv_len); + enckey_srv_to_cli = malloc(enckey_srv_to_cli_len); + intkey_cli_to_srv = malloc(intkey_cli_to_srv_len); + intkey_srv_to_cli = malloc(intkey_srv_to_cli_len); + if (IV_cli_to_srv == NULL || IV_srv_to_cli == NULL || + enckey_cli_to_srv == NULL || enckey_srv_to_cli == NULL || + intkey_cli_to_srv == NULL || intkey_srv_to_cli == NULL) { + ssh_set_error_oom(session); + goto error; + } + + /* IV */ + rc = ssh_kdf(crypto, key, key_len, 'A', IV_cli_to_srv, IV_len); + if (rc < 0) { + goto error; + } + rc = ssh_kdf(crypto, key, key_len, 'B', IV_srv_to_cli, IV_len); + if (rc < 0) { + goto error; + } + /* Encryption Key */ + rc = ssh_kdf(crypto, key, key_len, 'C', enckey_cli_to_srv, + enckey_cli_to_srv_len); + if (rc < 0) { + goto error; + } + rc = ssh_kdf(crypto, key, key_len, 'D', enckey_srv_to_cli, + enckey_srv_to_cli_len); + if (rc < 0) { + goto error; + } + /* Integrity Key */ + rc = ssh_kdf(crypto, key, key_len, 'E', intkey_cli_to_srv, + intkey_cli_to_srv_len); + if (rc < 0) { + goto error; + } + rc = ssh_kdf(crypto, key, key_len, 'F', intkey_srv_to_cli, + intkey_srv_to_cli_len); + if (rc < 0) { + goto error; + } + + if (session->client) { + crypto->encryptIV = IV_cli_to_srv; + crypto->decryptIV = IV_srv_to_cli; + crypto->encryptkey = enckey_cli_to_srv; + crypto->decryptkey = enckey_srv_to_cli; + crypto->encryptMAC = intkey_cli_to_srv; + crypto->decryptMAC = intkey_srv_to_cli; + } else { + crypto->encryptIV = IV_srv_to_cli; + crypto->decryptIV = IV_cli_to_srv; + crypto->encryptkey = enckey_srv_to_cli; + crypto->decryptkey = enckey_cli_to_srv; + crypto->encryptMAC = intkey_srv_to_cli; + crypto->decryptMAC = intkey_cli_to_srv; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Client to Server IV", IV_cli_to_srv, IV_len); + ssh_log_hexdump("Server to Client IV", IV_srv_to_cli, IV_len); + ssh_log_hexdump("Client to Server Encryption Key", enckey_cli_to_srv, + enckey_cli_to_srv_len); + ssh_log_hexdump("Server to Client Encryption Key", enckey_srv_to_cli, + enckey_srv_to_cli_len); + ssh_log_hexdump("Client to Server Integrity Key", intkey_cli_to_srv, + intkey_cli_to_srv_len); + ssh_log_hexdump("Server to Client Integrity Key", intkey_srv_to_cli, + intkey_srv_to_cli_len); +#endif + + rc = 0; +error: + ssh_string_burn(k_string); + SSH_STRING_FREE(k_string); + if (rc != 0) { + free(IV_cli_to_srv); + free(IV_srv_to_cli); + free(enckey_cli_to_srv); + free(enckey_srv_to_cli); + free(intkey_cli_to_srv); + free(intkey_srv_to_cli); + } + + return rc; +} diff --git a/src/known_hosts.c b/src/known_hosts.c new file mode 100644 index 0000000..ec6da30 --- /dev/null +++ b/src/known_hosts.c @@ -0,0 +1,569 @@ +/* + * keyfiles.c - private and public key handling for authentication. + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * Copyright (c) 2009 by Andreas Schneider + * + * 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 +#include +#include + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/buffer.h" +#include "libssh/misc.h" +#include "libssh/dh.h" +#include "libssh/pki.h" +#include "libssh/options.h" +#include "libssh/knownhosts.h" +/*todo: remove this include */ +#include "libssh/string.h" +#include "libssh/token.h" + +#ifndef _WIN32 +# include +# include +#endif + +/** + * @addtogroup libssh_session + * + * @{ + */ + +/** + * @internal + * + * @brief Return one line of known host file. + * + * This will return a token array containing (host|ip), keytype and key. + * + * @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[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 + * free that value. NULL if no match was found or the file + * was not found. + */ +static struct ssh_tokens_st *ssh_get_knownhost_line(FILE **file, + const char *filename, + const char **found_type) +{ + char buffer[4096] = {0}; + char *ptr; + struct ssh_tokens_st *tokens; + + if (*file == NULL) { + *file = fopen(filename,"r"); + if (*file == NULL) { + return NULL; + } + } + + while (fgets(buffer, sizeof(buffer), *file)) { + ptr = strchr(buffer, '\n'); + if (ptr) { + *ptr = '\0'; + } + + ptr = strchr(buffer,'\r'); + if (ptr) { + *ptr = '\0'; + } + + if (buffer[0] == '\0' || buffer[0] == '#') { + continue; /* skip empty lines */ + } + + tokens = ssh_tokenize(buffer, ' '); + if (tokens == NULL) { + fclose(*file); + *file = NULL; + + return NULL; + } + + if (tokens->tokens[0] == NULL || + tokens->tokens[1] == NULL || + tokens->tokens[2] == NULL) + { + /* it should have at least 3 tokens */ + ssh_tokens_free(tokens); + continue; + } + + *found_type = tokens->tokens[1]; + + return tokens; + } + + fclose(*file); + *file = NULL; + + /* we did not find anything, end of file*/ + return NULL; +} + +/** + * @internal + * + * @brief Check the public key in the known host line matches the public key of + * the currently connected server. + * + * @param[in] session The SSH session to use. + * + * @param[in] tokens A list of tokens in the known_hosts line. + * + * @returns 1 if the key matches, 0 if the key doesn't match and -1 + * on error. + */ +static int check_public_key(ssh_session session, char **tokens) { + ssh_string pubkey_blob = NULL; + ssh_buffer pubkey_buffer; + char *pubkey_64; + int rc; + + /* ssh-dss or ssh-rsa */ + pubkey_64 = tokens[2]; + pubkey_buffer = base64_to_bin(pubkey_64); + + if (pubkey_buffer == NULL) { + ssh_set_error(session, SSH_FATAL, + "Verifying that server is a known host: base64 error"); + return -1; + } + + rc = ssh_dh_get_current_server_publickey_blob(session, &pubkey_blob); + if (rc != 0) { + ssh_buffer_free(pubkey_buffer); + return -1; + } + + if (ssh_buffer_get_len(pubkey_buffer) != ssh_string_len(pubkey_blob)) { + ssh_string_free(pubkey_blob); + ssh_buffer_free(pubkey_buffer); + return 0; + } + + /* now test that they are identical */ + if (memcmp(ssh_buffer_get(pubkey_buffer), ssh_string_data(pubkey_blob), + ssh_buffer_get_len(pubkey_buffer)) != 0) { + ssh_string_free(pubkey_blob); + ssh_buffer_free(pubkey_buffer); + return 0; + } + + ssh_string_free(pubkey_blob); + ssh_buffer_free(pubkey_buffer); + return 1; +} + +/** + * @internal + * @brief Check if a hostname matches a openssh-style hashed known host. + * + * @param[in] host The host to check. + * + * @param[in] hashed The hashed value. + * + * @returns 1 if it matches, 0 otherwise. + */ +static int match_hashed_host(const char *host, const char *sourcehash) +{ + /* Openssh hash structure : + * |1|base64 encoded salt|base64 encoded hash + * hash is produced that way : + * hash := HMAC_SHA1(key=salt,data=host) + */ + unsigned char buffer[256] = {0}; + ssh_buffer salt; + ssh_buffer hash; + HMACCTX mac; + char *source; + char *b64hash; + int match; + unsigned int size; + + if (strncmp(sourcehash, "|1|", 3) != 0) { + return 0; + } + + source = strdup(sourcehash + 3); + if (source == NULL) { + return 0; + } + + b64hash = strchr(source, '|'); + if (b64hash == NULL) { + /* Invalid hash */ + SAFE_FREE(source); + + return 0; + } + + *b64hash = '\0'; + b64hash++; + + salt = base64_to_bin(source); + if (salt == NULL) { + SAFE_FREE(source); + + return 0; + } + + hash = base64_to_bin(b64hash); + SAFE_FREE(source); + if (hash == NULL) { + ssh_buffer_free(salt); + + return 0; + } + + mac = hmac_init(ssh_buffer_get(salt), ssh_buffer_get_len(salt), SSH_HMAC_SHA1); + if (mac == NULL) { + ssh_buffer_free(salt); + ssh_buffer_free(hash); + + return 0; + } + size = sizeof(buffer); + hmac_update(mac, host, strlen(host)); + hmac_final(mac, buffer, &size); + + if (size == ssh_buffer_get_len(hash) && + memcmp(buffer, ssh_buffer_get(hash), size) == 0) { + match = 1; + } else { + match = 0; + } + + ssh_buffer_free(salt); + ssh_buffer_free(hash); + + SSH_LOG(SSH_LOG_PACKET, + "Matching a hashed host: %s match=%d", host, match); + + return match; +} + +/* How it's working : + * 1- we open the known host file and bitch if it doesn't exist + * 2- we need to examine each line of the file, until going on state SSH_SERVER_KNOWN_OK: + * - there's a match. if the key is good, state is SSH_SERVER_KNOWN_OK, + * else it's SSH_SERVER_KNOWN_CHANGED (or SSH_SERVER_FOUND_OTHER) + * - there's no match : no change + */ + +/** + * @brief This function is deprecated + * + * @deprecated Please use ssh_session_is_known_server() + * @see ssh_session_is_known_server() + */ +int ssh_is_server_known(ssh_session session) +{ + FILE *file = NULL; + char *host; + char *hostport; + const char *type; + int match; + int i = 0; + char *files[3]; + + struct ssh_tokens_st *tokens; + + int ret = SSH_SERVER_NOT_KNOWN; + + if (session->opts.knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Can't find a known_hosts file"); + + return SSH_SERVER_FILE_NOT_FOUND; + } + } + + if (session->opts.host == NULL) { + ssh_set_error(session, SSH_FATAL, + "Can't verify host in known hosts if the hostname isn't known"); + + return SSH_SERVER_ERROR; + } + + if (session->current_crypto == NULL){ + ssh_set_error(session, SSH_FATAL, + "ssh_is_host_known called without cryptographic context"); + + return SSH_SERVER_ERROR; + } + + host = ssh_lowercase(session->opts.host); + hostport = ssh_hostport(host, session->opts.port > 0 ? session->opts.port : 22); + if (host == NULL || hostport == NULL) { + ssh_set_error_oom(session); + SAFE_FREE(host); + SAFE_FREE(hostport); + + return SSH_SERVER_ERROR; + } + + /* Set the list of known hosts files */ + i = 0; + if (session->opts.global_knownhosts != NULL){ + files[i++] = session->opts.global_knownhosts; + } + files[i++] = session->opts.knownhosts; + files[i] = NULL; + i = 0; + + do { + tokens = ssh_get_knownhost_line(&file, + files[i], + &type); + + /* End of file, return the current state or use next file */ + if (tokens == NULL) { + ++i; + if(files[i] == NULL) + break; + else + continue; + } + match = match_hashed_host(host, tokens->tokens[0]); + if (match == 0){ + match = match_hostname(hostport, tokens->tokens[0], + strlen(tokens->tokens[0])); + } + if (match == 0) { + match = match_hostname(host, tokens->tokens[0], + strlen(tokens->tokens[0])); + } + if (match == 0) { + match = match_hashed_host(hostport, tokens->tokens[0]); + } + if (match) { + ssh_key pubkey = ssh_dh_get_current_server_publickey(session); + const char *pubkey_type = ssh_key_type_to_char(ssh_key_type(pubkey)); + + /* We got a match. Now check the key type */ + if (strcmp(pubkey_type, type) != 0) { + SSH_LOG(SSH_LOG_PACKET, + "ssh_is_server_known: server type [%s] doesn't match the " + "type [%s] in known_hosts file", + pubkey_type, + type); + /* Different type. We don't override the known_changed error which is + * more important */ + if (ret != SSH_SERVER_KNOWN_CHANGED) + ret = SSH_SERVER_FOUND_OTHER; + ssh_tokens_free(tokens); + continue; + } + /* so we know the key type is good. We may get a good key or a bad key. */ + match = check_public_key(session, tokens->tokens); + ssh_tokens_free(tokens); + + if (match < 0) { + ret = SSH_SERVER_ERROR; + break; + } else if (match == 1) { + ret = SSH_SERVER_KNOWN_OK; + break; + } else if(match == 0) { + /* We override the status with the wrong key state */ + ret = SSH_SERVER_KNOWN_CHANGED; + } + } else { + ssh_tokens_free(tokens); + } + } while (1); + + if ((ret == SSH_SERVER_NOT_KNOWN) && + (session->opts.StrictHostKeyChecking == 0)) { + int rv = ssh_session_update_known_hosts(session); + if (rv != SSH_OK) { + ret = SSH_SERVER_ERROR; + } else { + ret = SSH_SERVER_KNOWN_OK; + } + } + + SAFE_FREE(host); + SAFE_FREE(hostport); + if (file != NULL) { + fclose(file); + } + + /* Return the current state at end of file */ + return ret; +} + +/** + * @deprecated Please use ssh_session_export_known_hosts_entry() + * @brief This function is deprecated. + */ +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; + + if (session->opts.host == NULL) { + ssh_set_error(session, SSH_FATAL, + "Can't write host in known hosts if the hostname isn't known"); + return NULL; + } + + host = ssh_lowercase(session->opts.host); + /* If using a nonstandard port, save the host in the [host]:port format */ + if (session->opts.port > 0 && session->opts.port != 22) { + hostport = ssh_hostport(host, session->opts.port); + SAFE_FREE(host); + if (hostport == NULL) { + return NULL; + } + host = hostport; + hostport = NULL; + } + + if (session->current_crypto==NULL) { + ssh_set_error(session, SSH_FATAL, "No current crypto context"); + SAFE_FREE(host); + return NULL; + } + + server_pubkey = ssh_dh_get_current_server_publickey(session); + if (server_pubkey == NULL){ + ssh_set_error(session, SSH_FATAL, "No public key present"); + SAFE_FREE(host); + return NULL; + } + + buffer = calloc (1, 4096); + if (!buffer) { + SAFE_FREE(host); + return NULL; + } + + rc = ssh_pki_export_pubkey_base64(server_pubkey, &b64_key); + if (rc < 0) { + SAFE_FREE(buffer); + SAFE_FREE(host); + return NULL; + } + + snprintf(buffer, len, + "%s %s %s\n", + host, + server_pubkey->type_c, + b64_key); + + SAFE_FREE(host); + SAFE_FREE(b64_key); + + return buffer; +} + +/** + * @deprecated Please use ssh_session_update_known_hosts() + * @brief This function is deprecated + */ +int ssh_write_knownhost(ssh_session session) +{ + FILE *file; + char *buffer = NULL; + char *dir; + int rc; + + if (session->opts.knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Can't find a known_hosts file"); + return SSH_ERROR; + } + } + + errno = 0; + file = fopen(session->opts.knownhosts, "a"); + if (file == NULL) { + if (errno == ENOENT) { + dir = ssh_dirname(session->opts.knownhosts); + if (dir == NULL) { + ssh_set_error(session, SSH_FATAL, "%s", strerror(errno)); + return SSH_ERROR; + } + + rc = ssh_mkdirs(dir, 0700); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Cannot create %s directory: %s", + dir, strerror(errno)); + SAFE_FREE(dir); + return SSH_ERROR; + } + SAFE_FREE(dir); + + errno = 0; + file = fopen(session->opts.knownhosts, "a"); + if (file == NULL) { + ssh_set_error(session, SSH_FATAL, + "Couldn't open known_hosts file %s" + " for appending: %s", + session->opts.knownhosts, strerror(errno)); + 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)); + return SSH_ERROR; + } + } + + rc = ssh_session_export_known_hosts_entry(session, &buffer); + if (rc != SSH_OK) { + fclose(file); + return SSH_ERROR; + } + + if (fwrite(buffer, strlen(buffer), 1, file) != 1 || ferror(file)) { + SAFE_FREE(buffer); + fclose(file); + return -1; + } + + SAFE_FREE(buffer); + fclose(file); + return 0; +} + +#define KNOWNHOSTS_MAXTYPES 10 + +/** @} */ diff --git a/src/knownhosts.c b/src/knownhosts.c new file mode 100644 index 0000000..fed75f9 --- /dev/null +++ b/src/knownhosts.c @@ -0,0 +1,1260 @@ +/* + * known_hosts: Host and public key verification. + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * Copyright (c) 2009-2017 by Andreas Schneider + * + * 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 +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/dh.h" +#include "libssh/session.h" +#include "libssh/options.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/dh.h" +#include "libssh/knownhosts.h" +#include "libssh/token.h" + +/** + * @addtogroup libssh_session + * + * @{ + */ + +static int hash_hostname(const char *name, + unsigned char *salt, + unsigned int salt_size, + unsigned char **hash, + unsigned int *hash_size) +{ + HMACCTX mac_ctx; + + mac_ctx = hmac_init(salt, salt_size, SSH_HMAC_SHA1); + if (mac_ctx == NULL) { + return SSH_ERROR; + } + + hmac_update(mac_ctx, name, strlen(name)); + hmac_final(mac_ctx, *hash, hash_size); + + return SSH_OK; +} + +static int match_hashed_hostname(const char *host, const char *hashed_host) +{ + char *hashed; + char *b64_hash; + ssh_buffer salt = NULL; + 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); + int cmp; + int rc; + int match = 0; + + cmp = strncmp(hashed_host, "|1|", 3); + if (cmp != 0) { + return 0; + } + + hashed = strdup(hashed_host + 3); + if (hashed == NULL) { + return 0; + } + + b64_hash = strchr(hashed, '|'); + if (b64_hash == NULL) { + goto error; + } + *b64_hash = '\0'; + b64_hash++; + + salt = base64_to_bin(hashed); + if (salt == NULL) { + goto error; + } + + hash = base64_to_bin(b64_hash); + if (hash == NULL) { + goto error; + } + + rc = hash_hostname(host, + ssh_buffer_get(salt), + ssh_buffer_get_len(salt), + &hashed_buf_ptr, + &hashed_buf_size); + if (rc != SSH_OK) { + goto error; + } + + if (hashed_buf_size != ssh_buffer_get_len(hash)) { + goto error; + } + + cmp = memcmp(hashed_buf, ssh_buffer_get(hash), hashed_buf_size); + if (cmp == 0) { + match = 1; + } + +error: + free(hashed); + SSH_BUFFER_FREE(salt); + SSH_BUFFER_FREE(hash); + + return match; +} + +/** + * @brief Free an allocated ssh_knownhosts_entry. + * + * Use SSH_KNOWNHOSTS_ENTRY_FREE() to set the pointer to NULL. + * + * @param[in] entry The entry to free. + */ +void ssh_knownhosts_entry_free(struct ssh_knownhosts_entry *entry) +{ + if (entry == NULL) { + return; + } + + SAFE_FREE(entry->hostname); + SAFE_FREE(entry->unparsed); + ssh_key_free(entry->publickey); + SAFE_FREE(entry->comment); + SAFE_FREE(entry); +} + +static int known_hosts_read_line(FILE *fp, + char *buf, + size_t buf_size, + size_t *buf_len, + size_t *lineno) +{ + while (fgets(buf, buf_size, fp) != NULL) { + size_t len; + if (buf[0] == '\0') { + continue; + } + + *lineno += 1; + len = strlen(buf); + if (buf_len != NULL) { + *buf_len = len; + } + if (buf[len - 1] == '\n' || feof(fp)) { + return 0; + } else { + errno = E2BIG; + return -1; + } + } + + return -1; +} + +static int +ssh_known_hosts_entries_compare(struct ssh_knownhosts_entry *k1, + struct ssh_knownhosts_entry *k2) +{ + int cmp; + + if (k1 == NULL || k2 == NULL) { + return 1; + } + + cmp = strcmp(k1->hostname, k2->hostname); + if (cmp != 0) { + return cmp; + } + + cmp = ssh_key_cmp(k1->publickey, k2->publickey, SSH_KEY_CMP_PUBLIC); + if (cmp != 0) { + return cmp; + } + + return 0; +} + +/* This method reads the known_hosts file referenced by the path + * in filename argument, and entries matching the match argument + * will be added to the list in entries argument. + * If the entries list is NULL, it will allocate a new list. Caller + * is responsible to free it even if an error occurs. + */ +static int ssh_known_hosts_read_entries(const char *match, + const char *filename, + struct ssh_list **entries) +{ + char line[8192]; + size_t lineno = 0; + size_t len = 0; + FILE *fp; + int rc; + + fp = fopen(filename, "r"); + if (fp == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to open the known_hosts file '%s': %s", + filename, strerror(errno)); + /* The missing file is not an error here */ + return SSH_OK; + } + + if (*entries == NULL) { + *entries = ssh_list_new(); + if (*entries == NULL) { + fclose(fp); + return SSH_ERROR; + } + } + + for (rc = known_hosts_read_line(fp, line, sizeof(line), &len, &lineno); + rc == 0; + rc = known_hosts_read_line(fp, line, sizeof(line), &len, &lineno)) { + struct ssh_knownhosts_entry *entry = NULL; + struct ssh_iterator *it = NULL; + char *p = NULL; + + if (line[len] != '\n') { + len = strcspn(line, "\n"); + } + line[len] = '\0'; + + /* Skip leading spaces */ + for (p = line; isspace((int)p[0]); p++); + + /* Skip comments and empty lines */ + if (p[0] == '\0' || p[0] == '#') { + continue; + } + + /* Skip lines starting with markers (@cert-authority, @revoked): + * we do not completely support them anyway */ + if (p[0] == '@') { + continue; + } + + rc = ssh_known_hosts_parse_line(match, + line, + &entry); + if (rc == SSH_AGAIN) { + continue; + } else if (rc != SSH_OK) { + goto error; + } + + /* Check for duplicates */ + for (it = ssh_list_get_iterator(*entries); + it != NULL; + it = it->next) { + struct ssh_knownhosts_entry *entry2; + int cmp; + entry2 = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + cmp = ssh_known_hosts_entries_compare(entry, entry2); + if (cmp == 0) { + ssh_knownhosts_entry_free(entry); + entry = NULL; + break; + } + } + if (entry != NULL) { + ssh_list_append(*entries, entry); + } + } + + fclose(fp); + return SSH_OK; +error: + fclose(fp); + return SSH_ERROR; +} + +static char *ssh_session_get_host_port(ssh_session session) +{ + char *host_port; + char *host; + + if (session->opts.host == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Can't verify server in known hosts if the host we " + "should connect to has not been set"); + + return NULL; + } + + host = ssh_lowercase(session->opts.host); + if (host == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + if (session->opts.port == 0 || session->opts.port == 22) { + host_port = host; + } else { + host_port = ssh_hostport(host, session->opts.port); + SAFE_FREE(host); + if (host_port == NULL) { + ssh_set_error_oom(session); + return NULL; + } + } + + return host_port; +} + +/** + * @internal + * @brief Check which host keys should be preferred for the session. + * + * This checks the known_hosts file to find out which algorithms should be + * preferred for the connection we are going to establish. + * + * @param[in] session The ssh session to use. + * + * @return A list of supported key types, NULL on error. + */ +struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session) +{ + struct ssh_list *entry_list = NULL; + struct ssh_iterator *it = NULL; + char *host_port = NULL; + size_t count; + struct ssh_list *list = NULL; + int list_error = 0; + int rc; + + if (session->opts.knownhosts == NULL || + session->opts.global_knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Can't find a known_hosts file"); + + return NULL; + } + } + + host_port = ssh_session_get_host_port(session); + if (host_port == NULL) { + return NULL; + } + + list = ssh_list_new(); + if (list == NULL) { + SAFE_FREE(host_port); + return NULL; + } + + rc = ssh_known_hosts_read_entries(host_port, + session->opts.knownhosts, + &entry_list); + if (rc != 0) { + ssh_list_free(entry_list); + ssh_list_free(list); + return NULL; + } + + rc = ssh_known_hosts_read_entries(host_port, + session->opts.global_knownhosts, + &entry_list); + SAFE_FREE(host_port); + if (rc != 0) { + ssh_list_free(entry_list); + ssh_list_free(list); + return NULL; + } + + if (entry_list == NULL) { + ssh_list_free(list); + return NULL; + } + + count = ssh_list_count(entry_list); + if (count == 0) { + ssh_list_free(list); + ssh_list_free(entry_list); + return NULL; + } + + for (it = ssh_list_get_iterator(entry_list); + it != NULL; + it = ssh_list_get_iterator(entry_list)) { + struct ssh_iterator *it2 = NULL; + struct ssh_knownhosts_entry *entry = NULL; + const char *algo = NULL; + bool present = false; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + algo = entry->publickey->type_c; + + /* Check for duplicates */ + for (it2 = ssh_list_get_iterator(list); + it2 != NULL; + it2 = it2->next) { + char *alg2 = ssh_iterator_value(char *, it2); + int cmp = strcmp(alg2, algo); + if (cmp == 0) { + present = true; + break; + } + } + + /* Add to the new list only if it is unique */ + if (!present) { + rc = ssh_list_append(list, algo); + if (rc != SSH_OK) { + list_error = 1; + } + } + + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); + } + ssh_list_free(entry_list); + if (list_error) { + goto error; + } + + return list; +error: + ssh_list_free(list); + return NULL; +} + +/** + * @internal + * + * @brief Returns a static string containing a list of the signature types the + * given key type can generate. + * + * @returns A static cstring containing the signature types the key is able to + * generate separated by commas; NULL in case of error + */ +static const char *ssh_known_host_sigs_from_hostkey_type(enum ssh_keytypes_e type) +{ + switch (type) { + case SSH_KEYTYPE_RSA: + 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_ECDSA_P256: + return "ecdsa-sha2-nistp256"; + case SSH_KEYTYPE_ECDSA_P384: + return "ecdsa-sha2-nistp384"; + case SSH_KEYTYPE_ECDSA_P521: + return "ecdsa-sha2-nistp521"; +#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; + } +} + +/** + * @internal + * @brief Get the host keys algorithms identifiers from the known_hosts files + * + * This expands the signatures types that can be generated from the keys types + * present in the known_hosts files + * + * @param[in] session The ssh session to use. + * + * @return A newly allocated cstring containing a list of signature algorithms + * that can be generated by the host using the keys listed in the known_hosts + * files, NULL on error. + */ +char *ssh_known_hosts_get_algorithms_names(ssh_session session) +{ + char methods_buffer[256 + 1] = {0}; + struct ssh_list *entry_list = NULL; + struct ssh_iterator *it = NULL; + char *host_port = NULL; + size_t count; + bool needcomma = false; + char *names; + + int rc; + + if (session->opts.knownhosts == NULL || + session->opts.global_knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Can't find a known_hosts file"); + + return NULL; + } + } + + host_port = ssh_session_get_host_port(session); + if (host_port == NULL) { + return NULL; + } + + rc = ssh_known_hosts_read_entries(host_port, + session->opts.knownhosts, + &entry_list); + if (rc != 0) { + SAFE_FREE(host_port); + ssh_list_free(entry_list); + return NULL; + } + + rc = ssh_known_hosts_read_entries(host_port, + session->opts.global_knownhosts, + &entry_list); + SAFE_FREE(host_port); + if (rc != 0) { + ssh_list_free(entry_list); + return NULL; + } + + if (entry_list == NULL) { + return NULL; + } + + count = ssh_list_count(entry_list); + if (count == 0) { + ssh_list_free(entry_list); + return NULL; + } + + for (it = ssh_list_get_iterator(entry_list); + it != NULL; + it = ssh_list_get_iterator(entry_list)) + { + struct ssh_knownhosts_entry *entry = NULL; + const char *algo = NULL; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + algo = ssh_known_host_sigs_from_hostkey_type(entry->publickey->type); + if (algo == NULL) { + continue; + } + + if (needcomma) { + strncat(methods_buffer, + ",", + sizeof(methods_buffer) - strlen(methods_buffer) - 1); + } + + strncat(methods_buffer, + algo, + sizeof(methods_buffer) - strlen(methods_buffer) - 1); + needcomma = true; + + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); + } + + ssh_list_free(entry_list); + + names = ssh_remove_duplicates(methods_buffer); + + return names; +} + +/** + * @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 + * consumeable form. You can use the PKI key function to further work with it. + * + * @param[in] hostname The hostname to match the line to + * + * @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 + * entry structure. The user needs to free the memory + * using SSH_KNOWNHOSTS_ENTRY_FREE(). + * + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_known_hosts_parse_line(const char *hostname, + const char *line, + struct ssh_knownhosts_entry **entry) +{ + struct ssh_knownhosts_entry *e = NULL; + char *known_host = NULL; + char *p; + enum ssh_keytypes_e key_type; + int match = 0; + int rc = SSH_OK; + + known_host = strdup(line); + if (known_host == NULL) { + return SSH_ERROR; + } + + /* match pattern for hostname or hashed hostname */ + p = strtok(known_host, " "); + if (p == NULL ) { + free(known_host); + return SSH_ERROR; + } + + e = calloc(1, sizeof(struct ssh_knownhosts_entry)); + if (e == NULL) { + free(known_host); + return SSH_ERROR; + } + + if (hostname != NULL) { + char *host_port = NULL; + char *q = NULL; + + /* Hashed */ + if (p[0] == '|') { + match = match_hashed_hostname(hostname, p); + } + + for (q = strtok(p, ","); + q != NULL; + q = strtok(NULL, ",")) { + 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 + * in braces with, possibly standard or wildcard, port. + * We need to test against [host]:port pair here. + */ + if (host_port == NULL) { + host_port = ssh_hostport(hostname, 22); + if (host_port == NULL) { + rc = SSH_ERROR; + goto out; + } + } + + cmp = match_hostname(host_port, q, strlen(q)); + } else { + cmp = match_hostname(hostname, q, strlen(q)); + } + if (cmp == 1) { + match = 1; + break; + } + } + free(host_port); + + if (match == 0) { + rc = SSH_AGAIN; + goto out; + } + + e->hostname = strdup(hostname); + if (e->hostname == NULL) { + rc = SSH_ERROR; + goto out; + } + } + + /* Restart parsing */ + SAFE_FREE(known_host); + known_host = strdup(line); + if (known_host == NULL) { + rc = SSH_ERROR; + goto out; + } + + p = strtok(known_host, " "); + if (p == NULL ) { + rc = SSH_ERROR; + goto out; + } + + e->unparsed = strdup(p); + if (e->unparsed == NULL) { + rc = SSH_ERROR; + goto out; + } + + /* pubkey type */ + p = strtok(NULL, " "); + if (p == NULL) { + rc = SSH_ERROR; + goto out; + } + + key_type = ssh_key_type_from_name(p); + if (key_type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_WARN, "key type '%s' unknown!", p); + rc = SSH_ERROR; + goto out; + } + + /* public key */ + p = strtok(NULL, " "); + if (p == NULL) { + rc = SSH_ERROR; + goto out; + } + + rc = ssh_pki_import_pubkey_base64(p, + key_type, + &e->publickey); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to parse %s key for entry: %s!", + ssh_key_type_to_char(key_type), + e->unparsed); + goto out; + } + + /* comment */ + p = strtok(NULL, " "); + if (p != NULL) { + p = strstr(line, p); + if (p != NULL) { + e->comment = strdup(p); + if (e->comment == NULL) { + rc = SSH_ERROR; + goto out; + } + } + } + + *entry = e; + SAFE_FREE(known_host); + + return SSH_OK; +out: + SAFE_FREE(known_host); + ssh_knownhosts_entry_free(e); + return rc; +} + +/** + * @brief Check if the set hostname and port matches an entry in known_hosts. + * + * This check if the set hostname and port has 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. + * + * @return A ssh_known_hosts_e return value. + */ +enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) +{ + struct ssh_list *entry_list = NULL; + struct ssh_iterator *it = NULL; + char *host_port = NULL; + bool global_known_hosts_found = false; + bool known_hosts_found = false; + int rc; + + if (session->opts.knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Cannot find a known_hosts file"); + + return SSH_KNOWN_HOSTS_NOT_FOUND; + } + } + + if (session->opts.knownhosts == NULL && + session->opts.global_knownhosts == NULL) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "No path set for a known_hosts file"); + + return SSH_KNOWN_HOSTS_NOT_FOUND; + } + + 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", + session->opts.knownhosts); + } + } + + if (session->opts.global_knownhosts != NULL) { + 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", + session->opts.global_knownhosts); + } + } + + if ((!known_hosts_found) && (!global_known_hosts_found)) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Cannot find a known_hosts file"); + + return SSH_KNOWN_HOSTS_NOT_FOUND; + } + + host_port = ssh_session_get_host_port(session); + if (host_port == NULL) { + return SSH_KNOWN_HOSTS_ERROR; + } + + if (known_hosts_found) { + rc = ssh_known_hosts_read_entries(host_port, + session->opts.knownhosts, + &entry_list); + if (rc != 0) { + SAFE_FREE(host_port); + ssh_list_free(entry_list); + return SSH_KNOWN_HOSTS_ERROR; + } + } + + if (global_known_hosts_found) { + rc = ssh_known_hosts_read_entries(host_port, + session->opts.global_knownhosts, + &entry_list); + if (rc != 0) { + SAFE_FREE(host_port); + ssh_list_free(entry_list); + return SSH_KNOWN_HOSTS_ERROR; + } + } + + SAFE_FREE(host_port); + + if (ssh_list_count(entry_list) == 0) { + ssh_list_free(entry_list); + return SSH_KNOWN_HOSTS_UNKNOWN; + } + + for (it = ssh_list_get_iterator(entry_list); + it != NULL; + it = ssh_list_get_iterator(entry_list)) { + struct ssh_knownhosts_entry *entry = NULL; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); + } + ssh_list_free(entry_list); + + return SSH_KNOWN_HOSTS_OK; +} + +/** + * @brief Export the current session information to a known_hosts string. + * + * This exports the current information of a session which is connected so a + * ssh server into an entry line which can be added to a known_hosts file. + * + * @param[in] session The session with information to export. + * + * @param[in] pentry_string A pointer to a string to store the alloocated + * line of the entry. The user must free it using + * ssh_string_free_char(). + * + * @return SSH_OK on succcess, 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 *b64_key = NULL; + int rc; + + if (pentry_string == NULL) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + + if (session->opts.host == NULL) { + ssh_set_error(session, SSH_FATAL, + "Can't create known_hosts entry - hostname unknown"); + return SSH_ERROR; + } + + host = ssh_session_get_host_port(session); + if (host == NULL) { + return SSH_ERROR; + } + + if (session->current_crypto == NULL) { + ssh_set_error(session, SSH_FATAL, + "No current crypto context, please connect first"); + SAFE_FREE(host); + return SSH_ERROR; + } + + server_pubkey = ssh_dh_get_current_server_publickey(session); + if (server_pubkey == NULL){ + ssh_set_error(session, SSH_FATAL, "No public key present"); + SAFE_FREE(host); + return SSH_ERROR; + } + + rc = ssh_pki_export_pubkey_base64(server_pubkey, &b64_key); + if (rc < 0) { + SAFE_FREE(host); + return SSH_ERROR; + } + + snprintf(entry_buf, sizeof(entry_buf), + "%s %s %s\n", + host, + server_pubkey->type_c, + b64_key); + + SAFE_FREE(host); + SAFE_FREE(b64_key); + + *pentry_string = strdup(entry_buf); + if (*pentry_string == NULL) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @brief Add the current 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 + * read-only so it is not touched by this function. + * + * @param[in] session The session to use to write the entry. + * + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_session_update_known_hosts(ssh_session session) +{ + FILE *fp = NULL; + char *entry = NULL; + char *dir = NULL; + size_t nwritten; + size_t len; + int rc; + + if (session->opts.knownhosts == NULL) { + rc = ssh_options_apply(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Can't find a known_hosts file"); + return SSH_ERROR; + } + } + + errno = 0; + fp = fopen(session->opts.knownhosts, "a"); + if (fp == NULL) { + if (errno == ENOENT) { + dir = ssh_dirname(session->opts.knownhosts); + if (dir == NULL) { + ssh_set_error(session, SSH_FATAL, "%s", strerror(errno)); + return SSH_ERROR; + } + + rc = ssh_mkdirs(dir, 0700); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Cannot create %s directory: %s", + dir, strerror(errno)); + SAFE_FREE(dir); + return SSH_ERROR; + } + SAFE_FREE(dir); + + errno = 0; + fp = fopen(session->opts.knownhosts, "a"); + if (fp == NULL) { + ssh_set_error(session, SSH_FATAL, + "Couldn't open known_hosts file %s" + " for appending: %s", + session->opts.knownhosts, strerror(errno)); + 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)); + return SSH_ERROR; + } + } + + rc = ssh_session_export_known_hosts_entry(session, &entry); + if (rc != SSH_OK) { + fclose(fp); + return rc; + } + + len = strlen(entry); + nwritten = fwrite(entry, sizeof(char), len, fp); + SAFE_FREE(entry); + 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)); + fclose(fp); + return SSH_ERROR; + } + + fclose(fp); + return SSH_OK; +} + +static enum ssh_known_hosts_e +ssh_known_hosts_check_server_key(const char *hosts_entry, + const char *filename, + ssh_key server_key, + struct ssh_knownhosts_entry **pentry) +{ + struct ssh_list *entry_list = NULL; + struct ssh_iterator *it = NULL; + enum ssh_known_hosts_e found = SSH_KNOWN_HOSTS_UNKNOWN; + int rc; + + rc = ssh_known_hosts_read_entries(hosts_entry, + filename, + &entry_list); + if (rc != 0) { + ssh_list_free(entry_list); + return SSH_KNOWN_HOSTS_UNKNOWN; + } + + it = ssh_list_get_iterator(entry_list); + if (it == NULL) { + ssh_list_free(entry_list); + return SSH_KNOWN_HOSTS_UNKNOWN; + } + + for (;it != NULL; it = it->next) { + struct ssh_knownhosts_entry *entry = NULL; + int cmp; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + + cmp = ssh_key_cmp(server_key, entry->publickey, SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + found = SSH_KNOWN_HOSTS_OK; + if (pentry != NULL) { + *pentry = entry; + ssh_list_remove(entry_list, it); + } + break; + } + + if (ssh_key_type(server_key) == ssh_key_type(entry->publickey)) { + found = SSH_KNOWN_HOSTS_CHANGED; + continue; + } + + if (found != SSH_KNOWN_HOSTS_CHANGED) { + found = SSH_KNOWN_HOSTS_OTHER; + } + } + + for (it = ssh_list_get_iterator(entry_list); + it != NULL; + it = ssh_list_get_iterator(entry_list)) { + struct ssh_knownhosts_entry *entry = NULL; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); + } + ssh_list_free(entry_list); + + return found; +} + +/** + * @brief Get the known_hosts entry for the current connected session. + * + * @param[in] session The session to validate. + * + * @param[in] pentry A pointer to store the allocated known hosts entry. + * + * @returns SSH_KNOWN_HOSTS_OK: The server is known and has not changed.\n + * SSH_KNOWN_HOSTS_CHANGED: The server key has changed. Either you + * are under attack or the administrator + * changed the key. You HAVE to warn the + * user about a possible attack.\n + * SSH_KNOWN_HOSTS_OTHER: The server gave use a key of a type while + * we had an other type recorded. It is a + * possible attack.\n + * SSH_KNOWN_HOSTS_UNKNOWN: The server is unknown. User should + * confirm the public key hash is correct.\n + * 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. + * + * @see ssh_knownhosts_entry_free() + */ +enum ssh_known_hosts_e +ssh_session_get_known_hosts_entry(ssh_session session, + struct ssh_knownhosts_entry **pentry) +{ + enum ssh_known_hosts_e old_rv, rv = SSH_KNOWN_HOSTS_UNKNOWN; + + if (session->opts.knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Can't find a known_hosts file"); + + return SSH_KNOWN_HOSTS_NOT_FOUND; + } + } + + rv = ssh_session_get_known_hosts_entry_file(session, + session->opts.knownhosts, + pentry); + if (rv == SSH_KNOWN_HOSTS_OK) { + /* We already found a match in the first file: return */ + return rv; + } + + old_rv = rv; + rv = ssh_session_get_known_hosts_entry_file(session, + session->opts.global_knownhosts, + pentry); + + /* If we did not find any match at all: we report the previous result */ + if (rv == SSH_KNOWN_HOSTS_UNKNOWN) { + if (session->opts.StrictHostKeyChecking == 0) { + return SSH_KNOWN_HOSTS_OK; + } + return old_rv; + } + + /* We found some match: return it */ + return rv; + +} + +/** + * @brief Get the known_hosts entry for the current connected session + * from the given known_hosts file. + * + * @param[in] session The session to validate. + * + * @param[in] filename The filename to parse. + * + * @param[in] pentry A pointer to store the allocated known hosts entry. + * + * @returns SSH_KNOWN_HOSTS_OK: The server is known and has not changed.\n + * SSH_KNOWN_HOSTS_CHANGED: The server key has changed. Either you + * are under attack or the administrator + * changed the key. You HAVE to warn the + * user about a possible attack.\n + * SSH_KNOWN_HOSTS_OTHER: The server gave use a key of a type while + * we had an other type recorded. It is a + * possible attack.\n + * SSH_KNOWN_HOSTS_UNKNOWN: The server is unknown. User should + * confirm the public key hash is correct.\n + * 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. + * + * @see ssh_knownhosts_entry_free() + */ +enum ssh_known_hosts_e +ssh_session_get_known_hosts_entry_file(ssh_session session, + const char *filename, + struct ssh_knownhosts_entry **pentry) +{ + ssh_key server_pubkey = NULL; + char *host_port = NULL; + enum ssh_known_hosts_e found = SSH_KNOWN_HOSTS_UNKNOWN; + + server_pubkey = ssh_dh_get_current_server_publickey(session); + if (server_pubkey == NULL) { + ssh_set_error(session, + SSH_FATAL, + "ssh_session_is_known_host called without a " + "server_key!"); + + return SSH_KNOWN_HOSTS_ERROR; + } + + host_port = ssh_session_get_host_port(session); + if (host_port == NULL) { + return SSH_KNOWN_HOSTS_ERROR; + } + + found = ssh_known_hosts_check_server_key(host_port, + filename, + server_pubkey, + pentry); + SAFE_FREE(host_port); + + return found; +} + +/** + * @brief Check if the servers public key for the connected session is known. + * + * This checks if we already know the public key of the server we want to + * connect to. This allows to detect if there is a MITM attach going on + * of if there have been changes on the server we don't know about. + * + * @param[in] session The SSH to validate. + * + * @returns SSH_KNOWN_HOSTS_OK: The server is known and has not changed.\n + * SSH_KNOWN_HOSTS_CHANGED: The server key has changed. Either you + * are under attack or the administrator + * changed the key. You HAVE to warn the + * user about a possible attack.\n + * SSH_KNOWN_HOSTS_OTHER: The server gave use a key of a type while + * we had an other type recorded. It is a + * possible attack.\n + * SSH_KNOWN_HOSTS_UNKNOWN: The server is unknown. User should + * confirm the public key hash is correct.\n + * 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 error checking the host. + */ +enum ssh_known_hosts_e ssh_session_is_known_server(ssh_session session) +{ + return ssh_session_get_known_hosts_entry(session, NULL); +} + +/** @} */ diff --git a/src/legacy.c b/src/legacy.c new file mode 100644 index 0000000..8ae3d92 --- /dev/null +++ b/src/legacy.c @@ -0,0 +1,750 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 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. + */ + +/** functions in that file are wrappers to the newly named functions. All + * of them are depreciated, but these wrapper will avoid breaking backward + * compatibility + */ + +#include "config.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include "libssh/pki_priv.h" +#include +#include +#include "libssh/options.h" + +/* AUTH FUNCTIONS */ +int ssh_auth_list(ssh_session session) { + return ssh_userauth_list(session, NULL); +} + +int ssh_userauth_offer_pubkey(ssh_session session, const char *username, + int type, ssh_string publickey) +{ + ssh_key key; + int rc; + + (void) type; /* unused */ + + rc = ssh_pki_import_pubkey_blob(publickey, &key); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Failed to convert public key"); + return SSH_AUTH_ERROR; + } + + rc = ssh_userauth_try_publickey(session, username, key); + ssh_key_free(key); + + return rc; +} + +int ssh_userauth_pubkey(ssh_session session, + const char *username, + ssh_string publickey, + ssh_private_key privatekey) +{ + ssh_key key; + int rc; + + (void) publickey; /* unused */ + + key = ssh_key_new(); + if (key == NULL) { + return SSH_AUTH_ERROR; + } + + 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; + key->rsa = privatekey->rsa_priv; + + rc = ssh_userauth_publickey(session, username, key); + key->dsa = NULL; + key->rsa = NULL; + ssh_key_free(key); + + return rc; +} + +int ssh_userauth_autopubkey(ssh_session session, const char *passphrase) { + return ssh_userauth_publickey_auto(session, NULL, passphrase); +} + +int ssh_userauth_privatekey_file(ssh_session session, + const char *username, + const char *filename, + const char *passphrase) { + char *pubkeyfile = NULL; + ssh_string pubkey = NULL; + ssh_private_key privkey = NULL; + int type = 0; + int rc = SSH_AUTH_ERROR; + size_t klen = strlen(filename) + 4 + 1; + + pubkeyfile = malloc(klen); + if (pubkeyfile == NULL) { + ssh_set_error_oom(session); + + return SSH_AUTH_ERROR; + } + snprintf(pubkeyfile, klen, "%s.pub", filename); + + pubkey = publickey_from_file(session, pubkeyfile, &type); + if (pubkey == NULL) { + SSH_LOG(SSH_LOG_RARE, "Public key file %s not found. Trying to generate it.", pubkeyfile); + /* auto-detect the key type with type=0 */ + privkey = privatekey_from_file(session, filename, 0, passphrase); + } else { + SSH_LOG(SSH_LOG_RARE, "Public key file %s loaded.", pubkeyfile); + privkey = privatekey_from_file(session, filename, type, passphrase); + } + if (privkey == NULL) { + goto error; + } + /* ssh_userauth_pubkey is responsible for taking care of null-pubkey */ + rc = ssh_userauth_pubkey(session, username, pubkey, privkey); + privatekey_free(privkey); + +error: + SAFE_FREE(pubkeyfile); + ssh_string_free(pubkey); + + return rc; +} + +/* BUFFER FUNCTIONS */ + +void buffer_free(ssh_buffer buffer){ + ssh_buffer_free(buffer); +} +void *buffer_get(ssh_buffer buffer){ + return ssh_buffer_get(buffer); +} +uint32_t buffer_get_len(ssh_buffer buffer){ + return ssh_buffer_get_len(buffer); +} +ssh_buffer buffer_new(void){ + return ssh_buffer_new(); +} + +ssh_channel channel_accept_x11(ssh_channel channel, int timeout_ms){ + return ssh_channel_accept_x11(channel, timeout_ms); +} + +int channel_change_pty_size(ssh_channel channel,int cols,int rows){ + return ssh_channel_change_pty_size(channel,cols,rows); +} + +ssh_channel channel_forward_accept(ssh_session session, int timeout_ms){ + return ssh_channel_accept_forward(session, timeout_ms, NULL); +} + +int channel_close(ssh_channel channel){ + return ssh_channel_close(channel); +} + +int channel_forward_cancel(ssh_session session, const char *address, int port){ + return ssh_channel_cancel_forward(session, address, port); +} + +int channel_forward_listen(ssh_session session, const char *address, + int port, int *bound_port){ + return ssh_channel_listen_forward(session, address, port, bound_port); +} + +void channel_free(ssh_channel channel){ + ssh_channel_free(channel); +} + +int channel_get_exit_status(ssh_channel channel){ + return ssh_channel_get_exit_status(channel); +} + +ssh_session channel_get_session(ssh_channel channel){ + return ssh_channel_get_session(channel); +} + +int channel_is_closed(ssh_channel channel){ + return ssh_channel_is_closed(channel); +} + +int channel_is_eof(ssh_channel channel){ + return ssh_channel_is_eof(channel); +} + +int channel_is_open(ssh_channel channel){ + return ssh_channel_is_open(channel); +} + +ssh_channel channel_new(ssh_session session){ + return ssh_channel_new(session); +} + +int channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport){ + return ssh_channel_open_forward(channel, remotehost, remoteport, + sourcehost,localport); +} + +int channel_open_session(ssh_channel channel){ + return ssh_channel_open_session(channel); +} + +int channel_poll(ssh_channel channel, int is_stderr){ + return ssh_channel_poll(channel, is_stderr); +} + +int channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr){ + return ssh_channel_read(channel, dest, count, is_stderr); +} + +/* + * This function will completely be depreciated. The old implementation was not + * renamed. + * int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, + * int is_stderr); + */ + +int channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr){ + return ssh_channel_read_nonblocking(channel, dest, count, is_stderr); +} + +int channel_request_env(ssh_channel channel, const char *name, const char *value){ + return ssh_channel_request_env(channel, name, value); +} + +int channel_request_exec(ssh_channel channel, const char *cmd){ + return ssh_channel_request_exec(channel, cmd); +} + +int channel_request_pty(ssh_channel channel){ + return ssh_channel_request_pty(channel); +} + +int channel_request_pty_size(ssh_channel channel, const char *term, + int cols, int rows){ + return ssh_channel_request_pty_size(channel, term, cols, rows); +} + +int channel_request_shell(ssh_channel channel){ + return ssh_channel_request_shell(channel); +} + +int channel_request_send_signal(ssh_channel channel, const char *signum){ + return ssh_channel_request_send_signal(channel, signum); +} + +int channel_request_sftp(ssh_channel channel){ + return ssh_channel_request_sftp(channel); +} + +int channel_request_subsystem(ssh_channel channel, const char *subsystem){ + return ssh_channel_request_subsystem(channel, subsystem); +} + +int channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number){ + return ssh_channel_request_x11(channel, single_connection, protocol, cookie, + screen_number); +} + +int channel_send_eof(ssh_channel channel){ + return ssh_channel_send_eof(channel); +} + +int channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout){ + return ssh_channel_select(readchans, writechans, exceptchans, timeout); +} + +void channel_set_blocking(ssh_channel channel, int blocking){ + ssh_channel_set_blocking(channel, blocking); +} + +int channel_write(ssh_channel channel, const void *data, uint32_t len){ + return ssh_channel_write(channel, data, len); +} + +/* + * These functions have to be wrapped around the pki.c functions. + +void privatekey_free(ssh_private_key prv); +ssh_private_key privatekey_from_file(ssh_session session, const char *filename, + int type, const char *passphrase); +int ssh_publickey_to_file(ssh_session session, const char *file, + ssh_string pubkey, int type); +ssh_string publickey_to_string(ssh_public_key key); + * + */ + +void string_burn(ssh_string str){ + ssh_string_burn(str); +} + +ssh_string string_copy(ssh_string str){ + return ssh_string_copy(str); +} + +void *string_data(ssh_string str){ + return ssh_string_data(str); +} + +int string_fill(ssh_string str, const void *data, size_t len){ + return ssh_string_fill(str,data,len); +} + +void string_free(ssh_string str){ + ssh_string_free(str); +} + +ssh_string string_from_char(const char *what){ + return ssh_string_from_char(what); +} + +size_t string_len(ssh_string str){ + return ssh_string_len(str); +} + +ssh_string string_new(size_t size){ + return ssh_string_new(size); +} + +char *string_to_char(ssh_string str){ + return ssh_string_to_char(str); +} + +/* OLD PKI FUNCTIONS */ + +void publickey_free(ssh_public_key key) { + if (key == NULL) { + return; + } + + 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); +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_pk_free(key->rsa_pub); + SAFE_FREE(key->rsa_pub); +#endif + break; + default: + break; + } + SAFE_FREE(key); +} + +ssh_public_key publickey_from_privatekey(ssh_private_key prv) { + struct ssh_public_key_struct *p; + ssh_key privkey; + ssh_key pubkey; + int rc; + + privkey = ssh_key_new(); + if (privkey == NULL) { + return NULL; + } + + 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; + privkey->rsa = prv->rsa_priv; + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + privkey->dsa = NULL; + privkey->rsa = NULL; + ssh_key_free(privkey); + if (rc < 0) { + return NULL; + } + + p = ssh_pki_convert_key_to_publickey(pubkey); + ssh_key_free(pubkey); + + return p; +} + +ssh_private_key privatekey_from_file(ssh_session session, + const char *filename, + int type, + const char *passphrase) { + ssh_auth_callback auth_fn = NULL; + void *auth_data = NULL; + ssh_private_key privkey; + ssh_key key; + int rc; + + (void) type; /* unused */ + + if (session->common.callbacks) { + auth_fn = session->common.callbacks->auth_function; + auth_data = session->common.callbacks->userdata; + } + + + rc = ssh_pki_import_privkey_file(filename, + passphrase, + auth_fn, + auth_data, + &key); + if (rc == SSH_ERROR) { + return NULL; + } + + privkey = malloc(sizeof(struct ssh_private_key_struct)); + if (privkey == NULL) { + ssh_key_free(key); + return NULL; + } + + privkey->type = key->type; + privkey->dsa_priv = key->dsa; + privkey->rsa_priv = key->rsa; + + key->dsa = NULL; + key->rsa = NULL; + + ssh_key_free(key); + + return privkey; +} + +enum ssh_keytypes_e ssh_privatekey_type(ssh_private_key privatekey){ + if (privatekey==NULL) + return SSH_KEYTYPE_UNKNOWN; + return privatekey->type; +} + +void privatekey_free(ssh_private_key prv) { + if (prv == NULL) { + return; + } + +#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); +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_pk_free(prv->rsa_priv); + SAFE_FREE(prv->rsa_priv); +#endif + memset(prv, 0, sizeof(struct ssh_private_key_struct)); + SAFE_FREE(prv); +} + +ssh_string publickey_from_file(ssh_session session, const char *filename, + int *type) { + ssh_key key; + ssh_string key_str = NULL; + int rc; + + (void) session; /* unused */ + + rc = ssh_pki_import_pubkey_file(filename, &key); + if (rc < 0) { + return NULL; + } + + rc = ssh_pki_export_pubkey_blob(key, &key_str); + if (rc < 0) { + ssh_key_free(key); + return NULL; + } + + if (type) { + *type = key->type; + } + ssh_key_free(key); + + return key_str; +} + +const char *ssh_type_to_char(int type) { + return ssh_key_type_to_char(type); +} + +int ssh_type_from_name(const char *name) { + return ssh_key_type_from_name(name); +} + +ssh_public_key publickey_from_string(ssh_session session, ssh_string pubkey_s) { + struct ssh_public_key_struct *pubkey; + ssh_key key; + int rc; + + (void) session; /* unused */ + + rc = ssh_pki_import_pubkey_blob(pubkey_s, &key); + if (rc < 0) { + return NULL; + } + + pubkey = malloc(sizeof(struct ssh_public_key_struct)); + if (pubkey == NULL) { + ssh_key_free(key); + return NULL; + } + + pubkey->type = key->type; + pubkey->type_c = key->type_c; + + pubkey->dsa_pub = key->dsa; + key->dsa = NULL; + pubkey->rsa_pub = key->rsa; + key->rsa = NULL; + + ssh_key_free(key); + + return pubkey; +} + +ssh_string publickey_to_string(ssh_public_key pubkey) { + ssh_key key; + ssh_string key_blob; + int rc; + + if (pubkey == NULL) { + return NULL; + } + + key = ssh_key_new(); + if (key == NULL) { + return NULL; + } + + key->type = pubkey->type; + key->type_c = pubkey->type_c; + + key->dsa = pubkey->dsa_pub; + key->rsa = pubkey->rsa_pub; + + rc = ssh_pki_export_pubkey_blob(key, &key_blob); + if (rc < 0) { + key_blob = NULL; + } + + key->dsa = NULL; + key->rsa = NULL; + ssh_key_free(key); + + return key_blob; +} + +int ssh_publickey_to_file(ssh_session session, + const char *file, + ssh_string pubkey, + int type) +{ + FILE *fp; + char *user; + char buffer[1024]; + char host[256]; + unsigned char *pubkey_64; + size_t len; + int rc; + if(session==NULL) + return SSH_ERROR; + if(file==NULL || pubkey==NULL){ + ssh_set_error(session, SSH_FATAL, "Invalid parameters"); + return SSH_ERROR; + } + pubkey_64 = bin_to_base64(ssh_string_data(pubkey), ssh_string_len(pubkey)); + if (pubkey_64 == NULL) { + return SSH_ERROR; + } + + user = ssh_get_local_username(); + if (user == NULL) { + SAFE_FREE(pubkey_64); + return SSH_ERROR; + } + + rc = gethostname(host, sizeof(host)); + if (rc < 0) { + SAFE_FREE(user); + SAFE_FREE(pubkey_64); + return SSH_ERROR; + } + + snprintf(buffer, sizeof(buffer), "%s %s %s@%s\n", + ssh_type_to_char(type), + pubkey_64, + user, + host); + + SAFE_FREE(pubkey_64); + SAFE_FREE(user); + + SSH_LOG(SSH_LOG_RARE, "Trying to write public key file: %s", file); + SSH_LOG(SSH_LOG_PACKET, "public key file content: %s", buffer); + + fp = fopen(file, "w+"); + if (fp == NULL) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Error opening %s: %s", file, strerror(errno)); + return SSH_ERROR; + } + + len = strlen(buffer); + if (fwrite(buffer, len, 1, fp) != 1 || ferror(fp)) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Unable to write to %s", file); + fclose(fp); + unlink(file); + return SSH_ERROR; + } + + fclose(fp); + return SSH_OK; +} + +int ssh_try_publickey_from_file(ssh_session session, + const char *keyfile, + ssh_string *publickey, + int *type) { + char *pubkey_file; + size_t len; + ssh_string pubkey_string; + int pubkey_type; + + if (session == NULL || keyfile == NULL || publickey == NULL || type == NULL) { + return -1; + } + + if (session->opts.sshdir == NULL) { + if (ssh_options_apply(session) < 0) { + return -1; + } + } + + SSH_LOG(SSH_LOG_PACKET, "Trying to open privatekey %s", keyfile); + if (!ssh_file_readaccess_ok(keyfile)) { + SSH_LOG(SSH_LOG_PACKET, "Failed to open privatekey %s", keyfile); + return -1; + } + + len = strlen(keyfile) + 5; + pubkey_file = malloc(len); + if (pubkey_file == NULL) { + return -1; + } + snprintf(pubkey_file, len, "%s.pub", keyfile); + + SSH_LOG(SSH_LOG_PACKET, "Trying to open publickey %s", + pubkey_file); + if (!ssh_file_readaccess_ok(pubkey_file)) { + SSH_LOG(SSH_LOG_PACKET, "Failed to open publickey %s", + pubkey_file); + SAFE_FREE(pubkey_file); + return 1; + } + + SSH_LOG(SSH_LOG_PACKET, "Success opening public and private key"); + + /* + * We are sure both the private and public key file is readable. We return + * the public as a string, and the private filename as an argument + */ + pubkey_string = publickey_from_file(session, pubkey_file, &pubkey_type); + if (pubkey_string == NULL) { + SSH_LOG(SSH_LOG_PACKET, + "Wasn't able to open public key file %s: %s", + pubkey_file, + ssh_get_error(session)); + SAFE_FREE(pubkey_file); + return -1; + } + + SAFE_FREE(pubkey_file); + + *publickey = pubkey_string; + *type = pubkey_type; + + return 0; +} + +ssh_string ssh_get_pubkey(ssh_session session) +{ + ssh_string pubkey_blob = NULL; + int rc; + + if (session == NULL || + session->current_crypto == NULL || + session->current_crypto->server_pubkey == NULL) { + return NULL; + } + + rc = ssh_dh_get_current_server_publickey_blob(session, + &pubkey_blob); + if (rc != 0) { + return NULL; + } + + return pubkey_blob; +} + +/**************************************************************************** + * SERVER SUPPORT + ****************************************************************************/ + +#ifdef WITH_SERVER +int ssh_accept(ssh_session session) { + return ssh_handle_key_exchange(session); +} + +int channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) { + return ssh_channel_write(channel, data, len); +} + +/** @deprecated + * @brief Interface previously exported by error. + */ +ssh_message ssh_message_retrieve(ssh_session session, uint32_t packettype){ + (void) packettype; + ssh_set_error(session, SSH_FATAL, "ssh_message_retrieve: obsolete libssh call"); + return NULL; +} + +#endif /* WITH_SERVER */ diff --git a/src/libcrypto-compat.c b/src/libcrypto-compat.c new file mode 100644 index 0000000..048d178 --- /dev/null +++ b/src/libcrypto-compat.c @@ -0,0 +1,396 @@ +/* + * 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 +#include "libcrypto-compat.h" + +#ifndef OPENSSL_NO_ENGINE +#include +#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); +} + +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; +} diff --git a/src/libcrypto-compat.h b/src/libcrypto-compat.h new file mode 100644 index 0000000..bda0473 --- /dev/null +++ b/src/libcrypto-compat.h @@ -0,0 +1,49 @@ +#ifndef LIBCRYPTO_COMPAT_H +#define LIBCRYPTO_COMPAT_H + +#include +#if OPENSSL_VERSION_NUMBER < 0x10100000L + +#include +#include +#include +#include +#include +#include +#include + +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); + +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); +#endif /* OPENSSL_VERSION_NUMBER */ + +#endif /* LIBCRYPTO_COMPAT_H */ diff --git a/src/libcrypto.c b/src/libcrypto.c new file mode 100644 index 0000000..eefc98d --- /dev/null +++ b/src/libcrypto.c @@ -0,0 +1,1144 @@ +/* + * 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 +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/wrapper.h" +#include "libssh/libcrypto.h" + +#ifdef HAVE_LIBCRYPTO + +#include +#include +#include +#include +#include +#include +#include + +#include "libcrypto-compat.h" + +#ifdef HAVE_OPENSSL_AES_H +#define HAS_AES +#include +#endif +#ifdef HAVE_OPENSSL_DES_H +#define HAS_DES +#include +#endif + +#if (defined(HAVE_VALGRIND_VALGRIND_H) && defined(HAVE_OPENSSL_IA32CAP_LOC)) +#include +#define CAN_DISABLE_AESNI +#endif + +#include "libssh/crypto.h" + +#ifdef HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID +#include +#endif + +#ifdef HAVE_OPENSSL_CRYPTO_CTR128_ENCRYPT +#include +#endif + +#include "libssh/crypto.h" + +static int libcrypto_initialized = 0; + +void ssh_reseed(void){ +#ifndef _WIN32 + struct timeval tv; + gettimeofday(&tv, NULL); + RAND_add(&tv, sizeof(tv), 0.0); +#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); +} + +SHACTX sha1_init(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); +} + +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); + } +} + +#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: + 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); + } +} + +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; + } + 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); +} + +#ifdef HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID +static const EVP_MD *sshkdf_digest_to_md(enum ssh_kdf_digest digest_type) +{ + switch (digest_type) { + case SSH_KDF_SHA1: + return EVP_sha1(); + case SSH_KDF_SHA256: + return EVP_sha256(); + case SSH_KDF_SHA384: + return EVP_sha384(); + case SSH_KDF_SHA512: + return EVP_sha512(); + } + return NULL; +} + +int ssh_kdf(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + int key_type, unsigned char *output, + size_t requested_len) +{ + EVP_KDF_CTX *ctx = EVP_KDF_CTX_new_id(EVP_KDF_SSHKDF); + int rc; + + if (ctx == NULL) { + return -1; + } + + rc = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_MD, + sshkdf_digest_to_md(crypto->digest_type)); + if (rc != 1) { + goto out; + } + rc = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_KEY, key, key_len); + if (rc != 1) { + goto out; + } + rc = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_SSHKDF_XCGHASH, + crypto->secret_hash, crypto->digest_len); + if (rc != 1) { + goto out; + } + rc = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_SSHKDF_TYPE, key_type); + if (rc != 1) { + goto out; + } + rc = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_SSHKDF_SESSION_ID, + crypto->session_id, crypto->digest_len); + if (rc != 1) { + goto out; + } + rc = EVP_KDF_derive(ctx, output, requested_len); + if (rc != 1) { + goto out; + } + +out: + EVP_KDF_CTX_free(ctx); + if (rc < 0) { + return rc; + } + return 0; +} + +#else +int ssh_kdf(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + int key_type, unsigned char *output, + size_t requested_len) +{ + return sshkdf_derive_key(crypto, key, key_len, + key_type, output, requested_len); +} +#endif + +HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) { + HMACCTX ctx = NULL; + + ctx = HMAC_CTX_new(); + if (ctx == NULL) { + return NULL; + } + + + switch(type) { + case SSH_HMAC_SHA1: + HMAC_Init_ex(ctx, key, len, EVP_sha1(), NULL); + break; + case SSH_HMAC_SHA256: + HMAC_Init_ex(ctx, key, len, EVP_sha256(), NULL); + break; + case SSH_HMAC_SHA512: + HMAC_Init_ex(ctx, key, len, EVP_sha512(), NULL); + break; + case SSH_HMAC_MD5: + HMAC_Init_ex(ctx, key, len, EVP_md5(), NULL); + break; + default: + HMAC_CTX_free(ctx); + ctx = NULL; + } + + return ctx; +} + +void hmac_update(HMACCTX ctx, const void *data, unsigned long len) { + HMAC_Update(ctx, data, len); +} + +void hmac_final(HMACCTX ctx, unsigned char *hashmacbuf, unsigned int *len) { + HMAC_Final(ctx,hashmacbuf,len); + +#if OPENSSL_VERSION_NUMBER > 0x10100000L + HMAC_CTX_free(ctx); + ctx = NULL; +#else + HMAC_cleanup(ctx); + SAFE_FREE(ctx); + ctx = NULL; +#endif +} + +static void evp_cipher_init(struct ssh_cipher_struct *cipher) { + if (cipher->ctx == NULL) { + cipher->ctx = EVP_CIPHER_CTX_new(); + } + + switch(cipher->ciphertype){ + case SSH_AES128_CBC: + cipher->cipher = EVP_aes_128_cbc(); + break; + case SSH_AES192_CBC: + cipher->cipher = EVP_aes_192_cbc(); + break; + 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; + case SSH_AES192_CTR: + cipher->cipher = EVP_aes_192_ctr(); + break; + 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; +#ifdef WITH_BLOWFISH_CIPHER + case SSH_BLOWFISH_CBC: + cipher->cipher = EVP_bf_cbc(); + break; + /* ciphers not using EVP */ +#endif + case SSH_AEAD_CHACHA20_POLY1305: + SSH_LOG(SSH_LOG_WARNING, "The ChaCha cipher cannot be handled here"); + break; + case SSH_NO_CIPHER: + SSH_LOG(SSH_LOG_WARNING, "No valid ciphertype found"); + break; + } +} + +static int evp_cipher_set_encrypt_key(struct ssh_cipher_struct *cipher, + void *key, void *IV) +{ + int rc; + + evp_cipher_init(cipher); + EVP_CIPHER_CTX_init(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"); + 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) { + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, + EVP_CTRL_GCM_SET_IV_FIXED, + -1, + (uint8_t *)IV); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_SET_IV_FIXED failed"); + return SSH_ERROR; + } + } +#endif /* HAVE_OPENSSL_EVP_AES_GCM */ + + EVP_CIPHER_CTX_set_padding(cipher->ctx, 0); + + return SSH_OK; +} + +static int evp_cipher_set_decrypt_key(struct ssh_cipher_struct *cipher, + void *key, void *IV) { + int rc; + + evp_cipher_init(cipher); + EVP_CIPHER_CTX_init(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"); + 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) { + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, + EVP_CTRL_GCM_SET_IV_FIXED, + -1, + (uint8_t *)IV); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_SET_IV_FIXED failed"); + return SSH_ERROR; + } + } +#endif /* HAVE_OPENSSL_EVP_AES_GCM */ + + EVP_CIPHER_CTX_set_padding(cipher->ctx, 0); + + return SSH_OK; +} + +/* EVP wrapper function for encrypt/decrypt */ +static void evp_cipher_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + int outlen = 0; + int rc = 0; + + rc = EVP_EncryptUpdate(cipher->ctx, + (unsigned char *)out, + &outlen, + (unsigned char *)in, + (int)len); + if (rc != 1){ + SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptUpdate failed"); + return; + } + if (outlen != (int)len){ + SSH_LOG(SSH_LOG_WARNING, + "EVP_EncryptUpdate: output size %d for %zu in", + outlen, + len); + return; + } +} + +static void evp_cipher_decrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + int outlen = 0; + int rc = 0; + + rc = EVP_DecryptUpdate(cipher->ctx, + (unsigned char *)out, + &outlen, + (unsigned char *)in, + (int)len); + if (rc != 1){ + SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptUpdate failed"); + return; + } + if (outlen != (int)len){ + SSH_LOG(SSH_LOG_WARNING, + "EVP_DecryptUpdate: output size %d for %zu in", + outlen, + len); + return; + } +} + +static void evp_cipher_cleanup(struct ssh_cipher_struct *cipher) { + if (cipher->ctx != NULL) { + EVP_CIPHER_CTX_cleanup(cipher->ctx); + EVP_CIPHER_CTX_free(cipher->ctx); + } +} + +#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, + uint8_t *out, + size_t len, + uint64_t seq) +{ + (void)cipher; + (void)seq; + + /* The length is not encrypted: Copy it to the result buffer */ + memcpy(out, in, len); + + return SSH_OK; +} + +static void +evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + size_t authlen, aadlen; + uint8_t lastiv[1]; + int tmplen = 0; + size_t outlen; + int rc; + + (void) seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + /* increment IV */ + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, + EVP_CTRL_GCM_IV_GEN, + 1, + lastiv); + if (rc == 0) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_IV_GEN failed"); + return; + } + + /* Pass over the authenticated data (not encrypted) */ + rc = EVP_EncryptUpdate(cipher->ctx, + NULL, + &tmplen, + (unsigned char *)in, + (int)aadlen); + outlen = tmplen; + if (rc == 0 || outlen != aadlen) { + SSH_LOG(SSH_LOG_WARNING, "Failed to pass authenticated data"); + return; + } + memcpy(out, in, aadlen); + + /* Encrypt the rest of the data */ + rc = EVP_EncryptUpdate(cipher->ctx, + (unsigned char *)out + aadlen, + &tmplen, + (unsigned char *)in + aadlen, + (int)len - aadlen); + outlen = tmplen; + if (rc != 1 || outlen != (int)len - aadlen) { + SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptUpdate failed"); + return; + } + + /* compute tag */ + rc = EVP_EncryptFinal(cipher->ctx, + NULL, + &tmplen); + if (rc < 0) { + SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptFinal failed: Failed to create a tag"); + return; + } + + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, + EVP_CTRL_GCM_GET_TAG, + authlen, + (unsigned char *)tag); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_GET_TAG failed"); + return; + } +} + +static int +evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + size_t authlen, aadlen; + uint8_t lastiv[1]; + int outlen = 0; + int rc = 0; + + (void)seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + /* increment IV */ + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, + EVP_CTRL_GCM_IV_GEN, + 1, + lastiv); + if (rc == 0) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_IV_GEN failed"); + return SSH_ERROR; + } + + /* set tag for authentication */ + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, + EVP_CTRL_GCM_SET_TAG, + authlen, + (unsigned char *)complete_packet + aadlen + encrypted_size); + if (rc == 0) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_SET_TAG failed"); + return SSH_ERROR; + } + + /* Pass over the authenticated data (not encrypted) */ + rc = EVP_DecryptUpdate(cipher->ctx, + NULL, + &outlen, + (unsigned char *)complete_packet, + (int)aadlen); + if (rc == 0) { + SSH_LOG(SSH_LOG_WARNING, "Failed to pass authenticated data"); + return SSH_ERROR; + } + /* Do not copy the length to the target buffer, because it is already processed */ + //memcpy(out, complete_packet, aadlen); + + /* Decrypt the rest of the data */ + rc = EVP_DecryptUpdate(cipher->ctx, + (unsigned char *)out, + &outlen, + (unsigned char *)complete_packet + aadlen, + encrypted_size /* already substracted aadlen*/); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptUpdate failed"); + return SSH_ERROR; + } + + if (outlen != (int)encrypted_size) { + SSH_LOG(SSH_LOG_WARNING, + "EVP_DecryptUpdate: output size %d for %zd in", + outlen, + encrypted_size); + return SSH_ERROR; + } + + /* verify tag */ + rc = EVP_DecryptFinal(cipher->ctx, + NULL, + &outlen); + if (rc < 0) { + SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptFinal failed: Failed authentication"); + return SSH_ERROR; + } + + return SSH_OK; +} + +#endif /* HAVE_OPENSSL_EVP_AES_GCM */ + +/* + * The table of supported ciphers + */ +static struct ssh_cipher_struct ssh_ciphertab[] = { +#ifdef WITH_BLOWFISH_CIPHER + { + .name = "blowfish-cbc", + .blocksize = 8, + .ciphertype = SSH_BLOWFISH_CBC, + .keysize = 128, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup + }, +#endif +#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, + .ciphertype = SSH_AES128_CTR, + .keysize = 128, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup + }, + { + .name = "aes192-ctr", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES192_CTR, + .keysize = 192, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup + }, + { + .name = "aes256-ctr", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES256_CTR, + .keysize = 256, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .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, + .ciphertype = SSH_AES128_CBC, + .keysize = 128, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup + }, + { + .name = "aes192-cbc", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES192_CBC, + .keysize = 192, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup + }, + { + .name = "aes256-cbc", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES256_CBC, + .keysize = 256, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup + }, +#ifdef HAVE_OPENSSL_EVP_AES_GCM + { + .name = "aes128-gcm@openssh.com", + .blocksize = AES_BLOCK_SIZE, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .ciphertype = SSH_AEAD_AES128_GCM, + .keysize = 128, + .tag_size = AES_GCM_TAGLEN, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .aead_encrypt = evp_cipher_aead_encrypt, + .aead_decrypt_length = evp_cipher_aead_get_length, + .aead_decrypt = evp_cipher_aead_decrypt, + .cleanup = evp_cipher_cleanup + }, + { + .name = "aes256-gcm@openssh.com", + .blocksize = AES_BLOCK_SIZE, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .ciphertype = SSH_AEAD_AES256_GCM, + .keysize = 256, + .tag_size = AES_GCM_TAGLEN, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .aead_encrypt = evp_cipher_aead_encrypt, + .aead_decrypt_length = evp_cipher_aead_get_length, + .aead_decrypt = evp_cipher_aead_decrypt, + .cleanup = evp_cipher_cleanup + }, +#endif /* HAVE_OPENSSL_EVP_AES_GCM */ +#endif /* HAS_AES */ +#ifdef HAS_DES + { + .name = "3des-cbc", + .blocksize = 8, + .ciphertype = SSH_3DES_CBC, + .keysize = 192, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup + }, +#endif /* HAS_DES */ + { + .name = "chacha20-poly1305@openssh.com" + }, + { + .name = NULL + } +}; + +struct ssh_cipher_struct *ssh_get_ciphertab(void) +{ + return ssh_ciphertab; +} + +/** + * @internal + * @brief Initialize libcrypto's subsystem + */ +int ssh_crypto_init(void) +{ + size_t i; + + if (libcrypto_initialized) { + return SSH_OK; + } + if (SSLeay() != OPENSSL_VERSION_NUMBER){ + SSH_LOG(SSH_LOG_WARNING, "libssh compiled with %s " + "headers, currently running with %s.", + OPENSSL_VERSION_TEXT, + SSLeay_version(SSLeay()) + ); + } +#ifdef CAN_DISABLE_AESNI + /* + * disable AES-NI when running within Valgrind, because they generate + * too many "uninitialized memory access" false positives + */ + if (RUNNING_ON_VALGRIND){ + SSH_LOG(SSH_LOG_INFO, "Running within Valgrind, disabling AES-NI"); + /* Bit #57 denotes AES-NI instruction set extension */ + OPENSSL_ia32cap &= ~(1LL << 57); + } +#endif +#if OPENSSL_VERSION_NUMBER < 0x10100000L + OpenSSL_add_all_algorithms(); +#endif + + for (i = 0; ssh_ciphertab[i].name != NULL; i++) { + int cmp; + + cmp = strcmp(ssh_ciphertab[i].name, "chacha20-poly1305@openssh.com"); + if (cmp == 0) { + memcpy(&ssh_ciphertab[i], + ssh_get_chacha20poly1305_cipher(), + sizeof(struct ssh_cipher_struct)); + break; + } + } + + libcrypto_initialized = 1; + + return SSH_OK; +} + +/** + * @internal + * @brief Finalize libcrypto's subsystem + */ +void ssh_crypto_finalize(void) +{ + if (!libcrypto_initialized) { + return; + } + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); +#endif + + libcrypto_initialized = 0; +} + +#endif /* LIBCRYPTO */ diff --git a/src/libgcrypt.c b/src/libgcrypt.c new file mode 100644 index 0000000..8fbf215 --- /dev/null +++ b/src/libgcrypt.c @@ -0,0 +1,815 @@ +/* + * 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 +#include +#include + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/wrapper.h" +#include "libssh/string.h" +#include "libssh/misc.h" + +#ifdef HAVE_LIBGCRYPT +#include + +static int libgcrypt_initialized = 0; + +static int alloc_key(struct ssh_cipher_struct *cipher) { + cipher->key = malloc(cipher->keylen); + if (cipher->key == NULL) { + return -1; + } + + return 0; +} + +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, + 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 c = NULL; + + switch(type) { + case SSH_HMAC_SHA1: + gcry_md_open(&c, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC); + break; + case SSH_HMAC_SHA256: + gcry_md_open(&c, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC); + break; + case SSH_HMAC_SHA512: + gcry_md_open(&c, GCRY_MD_SHA512, GCRY_MD_FLAG_HMAC); + break; + case SSH_HMAC_MD5: + gcry_md_open(&c, GCRY_MD_MD5, GCRY_MD_FLAG_HMAC); + break; + default: + c = NULL; + } + + gcry_md_setkey(c, key, len); + + return c; +} + +void hmac_update(HMACCTX c, const void *data, unsigned long len) { + gcry_md_write(c, data, len); +} + +void hmac_final(HMACCTX c, unsigned char *hashmacbuf, unsigned int *len) { + *len = gcry_md_get_algo_dlen(gcry_md_get_algo(c)); + memcpy(hashmacbuf, gcry_md_read(c, 0), *len); + gcry_md_close(c); +} + +#ifdef WITH_BLOWFISH_CIPHER +/* the wrapper functions for blowfish */ +static int blowfish_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV){ + if (cipher->key == NULL) { + if (alloc_key(cipher) < 0) { + return -1; + } + + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_BLOWFISH, + GCRY_CIPHER_MODE_CBC, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setkey(cipher->key[0], key, 16)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setiv(cipher->key[0], IV, 8)) { + SAFE_FREE(cipher->key); + return -1; + } + } + + return 0; +} + +static void blowfish_encrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, unsigned long 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) { + gcry_cipher_decrypt(cipher->key[0], out, len, in, len); +} +#endif /* WITH_BLOWFISH_CIPHER */ + +static int aes_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { + int mode=GCRY_CIPHER_MODE_CBC; + if (cipher->key == NULL) { + if (alloc_key(cipher) < 0) { + return -1; + } + if(strstr(cipher->name,"-ctr")) + mode=GCRY_CIPHER_MODE_CTR; + if (strstr(cipher->name, "-gcm")) + mode = GCRY_CIPHER_MODE_GCM; + switch (cipher->keysize) { + case 128: + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_AES128, + mode, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + break; + case 192: + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_AES192, + mode, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + break; + case 256: + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_AES256, + mode, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + break; + default: + SSH_LOG(SSH_LOG_WARNING, "Unksupported key length %u.", cipher->keysize); + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setkey(cipher->key[0], key, cipher->keysize / 8)) { + SAFE_FREE(cipher->key); + return -1; + } + if(mode == GCRY_CIPHER_MODE_CBC){ + if (gcry_cipher_setiv(cipher->key[0], IV, 16)) { + + SAFE_FREE(cipher->key); + return -1; + } + } else if (mode == GCRY_CIPHER_MODE_GCM) { + /* Store the IV so we can handle the packet counter increments later + * The IV is passed to the cipher context later. + */ + memcpy(cipher->last_iv, IV, AES_GCM_IVLEN); + } else { + if(gcry_cipher_setctr(cipher->key[0],IV,16)){ + SAFE_FREE(cipher->key); + return -1; + } + } + } + + return 0; +} + +static void aes_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + gcry_cipher_encrypt(cipher->key[0], out, len, in, len); +} + +static void aes_decrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + gcry_cipher_decrypt(cipher->key[0], out, len, in, len); +} + +static int +aes_aead_get_length(struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + (void)cipher; + (void)seq; + + /* The length is not encrypted: Copy it to the result buffer */ + memcpy(out, in, len); + + return SSH_OK; +} + +static void +aes_gcm_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + gpg_error_t err; + size_t aadlen, authlen; + + (void)seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + /* increment IV */ + err = gcry_cipher_setiv(cipher->key[0], + cipher->last_iv, + AES_GCM_IVLEN); + /* This actualy 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. + * + * The IV buffer has the following structure: + * [ 4B static IV ][ 8B packet counter ][ 4B block counter ] + */ + uint64_inc(cipher->last_iv + 4); + if (err) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + return; + } + + /* 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", + gpg_strerror(err)); + return; + } + memcpy(out, in, aadlen); + + /* Encrypt the rest of the data */ + err = gcry_cipher_encrypt(cipher->key[0], + (unsigned char *)out + aadlen, + len - aadlen, + (unsigned char *)in + aadlen, + len - aadlen); + if (err) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s", + gpg_strerror(err)); + return; + } + + /* Calculate the tag */ + err = gcry_cipher_gettag(cipher->key[0], + (void *)tag, + authlen); + if (err) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_gettag failed: %s", + gpg_strerror(err)); + return; + } +} + +static int +aes_gcm_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + gpg_error_t err; + size_t aadlen, authlen; + + (void)seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + /* increment IV */ + err = gcry_cipher_setiv(cipher->key[0], + cipher->last_iv, + AES_GCM_IVLEN); + /* This actualy 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. + * + * The IV buffer has the following structure: + * [ 4B static IV ][ 8B packet counter ][ 4B block counter ] + */ + uint64_inc(cipher->last_iv + 4); + if (err) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + + /* Pass the authenticated data (packet_length) */ + err = gcry_cipher_authenticate(cipher->key[0], + complete_packet, + aadlen); + if (err) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_authenticate failed: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + /* Do not copy the length to the target buffer, because it is already processed */ + //memcpy(out, complete_packet, aadlen); + + /* Encrypt the rest of the data */ + err = gcry_cipher_decrypt(cipher->key[0], + out, + encrypted_size, + (unsigned char *)complete_packet + aadlen, + encrypted_size); + if (err) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_decrypt failed: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + + /* Check the tag */ + err = gcry_cipher_checktag(cipher->key[0], + (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"); + return SSH_ERROR; + } else if (err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_WARNING, "General error while decryption: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + return SSH_OK; +} + +static int des3_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { + if (cipher->key == NULL) { + if (alloc_key(cipher) < 0) { + return -1; + } + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_3DES, + GCRY_CIPHER_MODE_CBC, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setkey(cipher->key[0], key, 24)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setiv(cipher->key[0], IV, 8)) { + SAFE_FREE(cipher->key); + return -1; + } + } + + return 0; +} + +static void des3_encrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, unsigned long 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) { + gcry_cipher_decrypt(cipher->key[0], out, len, in, len); +} + +/* the table of supported ciphers */ +static struct ssh_cipher_struct ssh_ciphertab[] = { +#ifdef WITH_BLOWFISH_CIPHER + { + .name = "blowfish-cbc", + .blocksize = 8, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 128, + .set_encrypt_key = blowfish_set_key, + .set_decrypt_key = blowfish_set_key, + .encrypt = blowfish_encrypt, + .decrypt = blowfish_decrypt + }, +#endif /* WITH_BLOWFISH_CIPHER */ + { + .name = "aes128-ctr", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 128, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_encrypt + }, + { + .name = "aes192-ctr", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 192, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_encrypt + }, + { + .name = "aes256-ctr", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 256, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_encrypt + }, + { + .name = "aes128-cbc", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 128, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_decrypt + }, + { + .name = "aes192-cbc", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 192, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_decrypt + }, + { + .name = "aes256-cbc", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 256, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_decrypt + }, + { + .name = "aes128-gcm@openssh.com", + .blocksize = 16, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 128, + .tag_size = AES_GCM_TAGLEN, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .aead_encrypt = aes_gcm_encrypt, + .aead_decrypt_length = aes_aead_get_length, + .aead_decrypt = aes_gcm_decrypt, + }, + { + .name = "aes256-gcm@openssh.com", + .blocksize = 16, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 256, + .tag_size = AES_GCM_TAGLEN, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .aead_encrypt = aes_gcm_encrypt, + .aead_decrypt_length = aes_aead_get_length, + .aead_decrypt = aes_gcm_decrypt, + }, + { + .name = "3des-cbc", + .blocksize = 8, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 192, + .set_encrypt_key = des3_set_key, + .set_decrypt_key = des3_set_key, + .encrypt = des3_encrypt, + .decrypt = des3_decrypt + }, + { + .name = "chacha20-poly1305@openssh.com" + }, + { + .name = NULL, + .blocksize = 0, + .keylen = 0, + .key = NULL, + .keysize = 0, + .set_encrypt_key = NULL, + .set_decrypt_key = NULL, + .encrypt = NULL, + .decrypt = NULL + } +}; + +struct ssh_cipher_struct *ssh_get_ciphertab(void) +{ + return ssh_ciphertab; +} + +/* + * Extract an MPI from the given s-expression SEXP named NAME which is + * encoded using INFORMAT and store it in a newly allocated ssh_string + * encoded using OUTFORMAT. + */ +ssh_string ssh_sexp_extract_mpi(const gcry_sexp_t sexp, + const char *name, + enum gcry_mpi_format informat, + enum gcry_mpi_format outformat) +{ + gpg_error_t err; + ssh_string result = NULL; + gcry_sexp_t fragment = NULL; + gcry_mpi_t mpi = NULL; + size_t size; + + fragment = gcry_sexp_find_token(sexp, name, 0); + if (fragment == NULL) { + goto fail; + } + + mpi = gcry_sexp_nth_mpi(fragment, 1, informat); + if (mpi == NULL) { + goto fail; + } + + err = gcry_mpi_print(outformat, NULL, 0, &size, mpi); + if (err != 0) { + goto fail; + } + + result = ssh_string_new(size); + if (result == NULL) { + goto fail; + } + + err = gcry_mpi_print(outformat, ssh_string_data(result), size, NULL, mpi); + if (err != 0) { + ssh_string_burn(result); + SSH_STRING_FREE(result); + result = NULL; + goto fail; + } + +fail: + gcry_sexp_release(fragment); + gcry_mpi_release(mpi); + return result; +} + + +/** + * @internal + * + * @brief Initialize libgcrypt's subsystem + */ +int ssh_crypto_init(void) +{ + size_t i; + + if (libgcrypt_initialized) { + return SSH_OK; + } + + gcry_check_version(NULL); + + /* While the secure memory is not set up */ + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + + if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P, 0)) { + gcry_control(GCRYCTL_INIT_SECMEM, 4096); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + } + + /* Re-enable warning */ + gcry_control (GCRYCTL_RESUME_SECMEM_WARN); + + for (i = 0; ssh_ciphertab[i].name != NULL; i++) { + int cmp; + cmp = strcmp(ssh_ciphertab[i].name, "chacha20-poly1305@openssh.com"); + if (cmp == 0) { + memcpy(&ssh_ciphertab[i], + ssh_get_chacha20poly1305_cipher(), + sizeof(struct ssh_cipher_struct)); + break; + } + } + + libgcrypt_initialized = 1; + + return SSH_OK; +} + +/** + * @internal + * + * @brief Finalize libgcrypt's subsystem + */ +void ssh_crypto_finalize(void) +{ + if (!libgcrypt_initialized) { + return; + } + + gcry_control(GCRYCTL_TERM_SECMEM); + + libgcrypt_initialized = 0; +} + +#endif diff --git a/src/libmbedcrypto.c b/src/libmbedcrypto.c new file mode 100644 index 0000000..755c5eb --- /dev/null +++ b/src/libmbedcrypto.c @@ -0,0 +1,1102 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * 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/wrapper.h" +#include "libssh/crypto.h" +#include "libssh/priv.h" +#include "libssh/misc.h" + +#ifdef HAVE_LIBMBEDCRYPTO +#include +#ifdef MBEDTLS_GCM_C +#include +#endif /* MBEDTLS_GCM_C */ + +static mbedtls_entropy_context ssh_mbedtls_entropy; +static mbedtls_ctr_drbg_context ssh_mbedtls_ctr_drbg; + +static int libmbedcrypto_initialized = 0; + +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, + 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 ctx = NULL; + const mbedtls_md_info_t *md_info = NULL; + int rc; + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + switch (type) { + case SSH_HMAC_SHA1: + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + break; + case SSH_HMAC_SHA256: + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + break; + case SSH_HMAC_SHA512: + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + break; + default: + goto error; + } + + mbedtls_md_init(ctx); + + if (md_info == NULL) { + goto error; + } + + rc = mbedtls_md_setup(ctx, md_info, 1); + if (rc != 0) { + goto error; + } + + rc = mbedtls_md_hmac_starts(ctx, key, len); + if (rc != 0) { + goto error; + } + + return ctx; + +error: + mbedtls_md_free(ctx); + SAFE_FREE(ctx); + return NULL; +} + +void hmac_update(HMACCTX c, const void *data, unsigned long len) +{ + mbedtls_md_hmac_update(c, data, len); +} + +void hmac_final(HMACCTX c, unsigned char *hashmacbuf, unsigned int *len) +{ + *len = mbedtls_md_get_size(c->md_info); + mbedtls_md_hmac_finish(c, hashmacbuf); + mbedtls_md_free(c); + SAFE_FREE(c); +} + +static int +cipher_init(struct ssh_cipher_struct *cipher, + mbedtls_operation_t operation, + void *key, + void *IV) +{ + const mbedtls_cipher_info_t *cipher_info = NULL; + mbedtls_cipher_context_t *ctx; + int rc; + + if (operation == MBEDTLS_ENCRYPT) { + ctx = &cipher->encrypt_ctx; + } else if (operation == MBEDTLS_DECRYPT) { + ctx = &cipher->decrypt_ctx; + } else { + SSH_LOG(SSH_LOG_WARNING, "unknown operation"); + return 1; + } + + mbedtls_cipher_init(ctx); + cipher_info = mbedtls_cipher_info_from_type(cipher->type); + + rc = mbedtls_cipher_setup(ctx, cipher_info); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_setup failed"); + goto error; + } + + rc = mbedtls_cipher_setkey(ctx, key, + cipher_info->key_bitlen, + operation); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_setkey failed"); + goto error; + } + + rc = mbedtls_cipher_set_iv(ctx, IV, cipher_info->iv_size); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_set_iv failed"); + goto error; + } + + return 0; +error: + mbedtls_cipher_free(ctx); + return 1; +} + +static int +cipher_set_encrypt_key(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + int rc; + + rc = cipher_init(cipher, MBEDTLS_ENCRYPT, key, IV); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARNING, "cipher_init failed"); + goto error; + } + + rc = mbedtls_cipher_reset(&cipher->encrypt_ctx); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_reset failed"); + goto error; + } + + return SSH_OK; +error: + return SSH_ERROR; +} + +static int +cipher_set_encrypt_key_cbc(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + int rc; + + rc = cipher_init(cipher, MBEDTLS_ENCRYPT, key, IV); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARNING, "cipher_init failed"); + goto error; + } + + /* libssh only encypts 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"); + goto error; + } + + rc = mbedtls_cipher_reset(&cipher->encrypt_ctx); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_reset failed"); + goto error; + } + + return SSH_OK; +error: + mbedtls_cipher_free(&cipher->encrypt_ctx); + return SSH_ERROR; +} + +#ifdef MBEDTLS_GCM_C +static int +cipher_set_key_gcm(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + const mbedtls_cipher_info_t *cipher_info = NULL; + 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); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_gcm_setkey failed"); + goto error; + } + + /* Store the IV so we can increment the packet counter later */ + memcpy(cipher->last_iv, IV, AES_GCM_IVLEN); + + return 0; +error: + mbedtls_gcm_free(&cipher->gcm_ctx); + return 1; +} +#endif /* MBEDTLS_GCM_C */ + +static int +cipher_set_decrypt_key(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + int rc; + + rc = cipher_init(cipher, MBEDTLS_DECRYPT, key, IV); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARNING, "cipher_init failed"); + goto error; + } + + mbedtls_cipher_reset(&cipher->decrypt_ctx); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_reset failed"); + goto error; + } + + return SSH_OK; +error: + mbedtls_cipher_free(&cipher->decrypt_ctx); + return SSH_ERROR; +} + +static int +cipher_set_decrypt_key_cbc(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + int rc; + + rc = cipher_init(cipher, MBEDTLS_DECRYPT, key, IV); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARNING, "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"); + goto error; + } + + mbedtls_cipher_reset(&cipher->decrypt_ctx); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_reset failed"); + goto error; + } + + return SSH_OK; +error: + mbedtls_cipher_free(&cipher->decrypt_ctx); + return SSH_ERROR; +} + +static void cipher_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + size_t outlen = 0; + size_t total_len = 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"); + return; + } + + total_len += outlen; + + if (total_len == len) { + return; + } + + rc = mbedtls_cipher_finish(&cipher->encrypt_ctx, (unsigned char *) out + outlen, + &outlen); + + total_len += outlen; + + if (rc != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_finish failed during encryption"); + return; + } + + if (total_len != len) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update: output size %zu for %zu", + outlen, len); + return; + } + +} + +static void cipher_encrypt_cbc(struct ssh_cipher_struct *cipher, void *in, void *out, + unsigned long 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"); + return; + } + + if (outlen != len) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update: output size %zu for %zu", + outlen, len); + return; + } + +} + +static void cipher_decrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + size_t outlen = 0; + int rc = 0; + size_t total_len = 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"); + return; + } + + total_len += outlen; + + if (total_len == len) { + return; + } + + rc = mbedtls_cipher_finish(&cipher->decrypt_ctx, (unsigned char *) out + + outlen, &outlen); + + if (rc != 0) { + SSH_LOG(SSH_LOG_WARNING, "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", + outlen, len); + return; + } + +} + +static void cipher_decrypt_cbc(struct ssh_cipher_struct *cipher, void *in, void *out, + unsigned long 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"); + return; + } + + /* MbedTLS caches the last block when decrypting with cbc. + * By calling finish the block is flushed to out, however the unprocessed + * data counter is not reset. + * Calling mbedtls_cipher_reset resets the unprocessed data counter. + */ + if (outlen == 0) { + rc = mbedtls_cipher_finish(&cipher->decrypt_ctx, out, &outlen); + } else if (outlen == len) { + return; + } else { + rc = mbedtls_cipher_finish(&cipher->decrypt_ctx, (unsigned char *) out + + outlen , &outlen); + } + + if (rc != 0) { + SSH_LOG(SSH_LOG_WARNING, "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"); + return; + } + + if (outlen != len) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update: output size %zu for %zu", + outlen, len); + return; + } + +} + +#ifdef MBEDTLS_GCM_C +static int +cipher_gcm_get_length(struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + (void)cipher; + (void)seq; + + /* The length is not encrypted: Copy it to the result buffer */ + memcpy(out, in, len); + + return SSH_OK; +} + +static void +cipher_encrypt_gcm(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + size_t authlen, aadlen; + int rc; + + (void) seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + /* The length is not encrypted */ + memcpy(out, in, aadlen); + rc = mbedtls_gcm_crypt_and_tag(&cipher->gcm_ctx, + MBEDTLS_GCM_ENCRYPT, + len - aadlen, /* encrypted data len */ + cipher->last_iv, /* IV */ + AES_GCM_IVLEN, + in, /* aad */ + aadlen, + (const unsigned char *)in + aadlen, /* input */ + (unsigned char *)out + aadlen, /* output */ + authlen, + tag); /* tag */ + if (rc != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_gcm_crypt_and_tag failed"); + return; + } + + /* Increment the IV for the next invocation */ + uint64_inc(cipher->last_iv + 4); +} + +static int +cipher_decrypt_gcm(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + size_t authlen, aadlen; + int rc; + + (void) seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + rc = mbedtls_gcm_auth_decrypt(&cipher->gcm_ctx, + encrypted_size, /* encrypted data len */ + cipher->last_iv, /* IV */ + AES_GCM_IVLEN, + complete_packet, /* aad */ + aadlen, + (const uint8_t *)complete_packet + aadlen + encrypted_size, /* tag */ + authlen, + (const uint8_t *)complete_packet + aadlen, /* input */ + (unsigned char *)out); /* output */ + if (rc != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_gcm_auth_decrypt failed"); + return SSH_ERROR; + } + + /* Increment the IV for the next invocation */ + uint64_inc(cipher->last_iv + 4); + + return SSH_OK; +} +#endif /* MBEDTLS_GCM_C */ + +static void cipher_cleanup(struct ssh_cipher_struct *cipher) +{ + mbedtls_cipher_free(&cipher->encrypt_ctx); + mbedtls_cipher_free(&cipher->decrypt_ctx); +#ifdef MBEDTLS_GCM_C + mbedtls_gcm_free(&cipher->gcm_ctx); +#endif /* MBEDTLS_GCM_C */ +} + +static struct ssh_cipher_struct ssh_ciphertab[] = { +#ifdef WITH_BLOWFISH_CIPHER + { + .name = "blowfish-cbc", + .blocksize = 8, + .keysize = 128, + .type = MBEDTLS_CIPHER_BLOWFISH_CBC, + .set_encrypt_key = cipher_set_encrypt_key_cbc, + .set_decrypt_key = cipher_set_decrypt_key_cbc, + .encrypt = cipher_encrypt_cbc, + .decrypt = cipher_decrypt_cbc, + .cleanup = cipher_cleanup + }, +#endif /* WITH_BLOWFISH_CIPHER */ + { + .name = "aes128-ctr", + .blocksize = 16, + .keysize = 128, + .type = MBEDTLS_CIPHER_AES_128_CTR, + .set_encrypt_key = cipher_set_encrypt_key, + .set_decrypt_key = cipher_set_decrypt_key, + .encrypt = cipher_encrypt, + .decrypt = cipher_decrypt, + .cleanup = cipher_cleanup + }, + { + .name = "aes192-ctr", + .blocksize = 16, + .keysize = 192, + .type = MBEDTLS_CIPHER_AES_192_CTR, + .set_encrypt_key = cipher_set_encrypt_key, + .set_decrypt_key = cipher_set_decrypt_key, + .encrypt = cipher_encrypt, + .decrypt = cipher_decrypt, + .cleanup = cipher_cleanup + }, + { + .name = "aes256-ctr", + .blocksize = 16, + .keysize = 256, + .type = MBEDTLS_CIPHER_AES_256_CTR, + .set_encrypt_key = cipher_set_encrypt_key, + .set_decrypt_key = cipher_set_decrypt_key, + .encrypt = cipher_encrypt, + .decrypt = cipher_decrypt, + .cleanup = cipher_cleanup + }, + { + .name = "aes128-cbc", + .blocksize = 16, + .keysize = 128, + .type = MBEDTLS_CIPHER_AES_128_CBC, + .set_encrypt_key = cipher_set_encrypt_key_cbc, + .set_decrypt_key = cipher_set_decrypt_key_cbc, + .encrypt = cipher_encrypt_cbc, + .decrypt = cipher_decrypt_cbc, + .cleanup = cipher_cleanup + }, + { + .name = "aes192-cbc", + .blocksize = 16, + .keysize = 192, + .type = MBEDTLS_CIPHER_AES_192_CBC, + .set_encrypt_key = cipher_set_encrypt_key_cbc, + .set_decrypt_key = cipher_set_decrypt_key_cbc, + .encrypt = cipher_encrypt_cbc, + .decrypt = cipher_decrypt_cbc, + .cleanup = cipher_cleanup + }, + { + .name = "aes256-cbc", + .blocksize = 16, + .keysize = 256, + .type = MBEDTLS_CIPHER_AES_256_CBC, + .set_encrypt_key = cipher_set_encrypt_key_cbc, + .set_decrypt_key = cipher_set_decrypt_key_cbc, + .encrypt = cipher_encrypt_cbc, + .decrypt = cipher_decrypt_cbc, + .cleanup = cipher_cleanup + }, +#ifdef MBEDTLS_GCM_C + { + .name = "aes128-gcm@openssh.com", + .blocksize = 16, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .keysize = 128, + .tag_size = AES_GCM_TAGLEN, + .type = MBEDTLS_CIPHER_AES_128_GCM, + .set_encrypt_key = cipher_set_key_gcm, + .set_decrypt_key = cipher_set_key_gcm, + .aead_encrypt = cipher_encrypt_gcm, + .aead_decrypt_length = cipher_gcm_get_length, + .aead_decrypt = cipher_decrypt_gcm, + .cleanup = cipher_cleanup + }, + { + .name = "aes256-gcm@openssh.com", + .blocksize = 16, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .keysize = 256, + .tag_size = AES_GCM_TAGLEN, + .type = MBEDTLS_CIPHER_AES_256_GCM, + .set_encrypt_key = cipher_set_key_gcm, + .set_decrypt_key = cipher_set_key_gcm, + .aead_encrypt = cipher_encrypt_gcm, + .aead_decrypt_length = cipher_gcm_get_length, + .aead_decrypt = cipher_decrypt_gcm, + .cleanup = cipher_cleanup + }, +#endif /* MBEDTLS_GCM_C */ + { + .name = "3des-cbc", + .blocksize = 8, + .keysize = 192, + .type = MBEDTLS_CIPHER_DES_EDE3_CBC, + .set_encrypt_key = cipher_set_encrypt_key_cbc, + .set_decrypt_key = cipher_set_decrypt_key_cbc, + .encrypt = cipher_encrypt_cbc, + .decrypt = cipher_decrypt_cbc, + .cleanup = cipher_cleanup + }, + { + .name = "chacha20-poly1305@openssh.com" + }, + { + .name = NULL, + .blocksize = 0, + .keysize = 0, + .set_encrypt_key = NULL, + .set_decrypt_key = NULL, + .encrypt = NULL, + .decrypt = NULL, + .cleanup = NULL + } +}; + +struct ssh_cipher_struct *ssh_get_ciphertab(void) +{ + return ssh_ciphertab; +} + +int ssh_crypto_init(void) +{ + size_t i; + int rc; + + if (libmbedcrypto_initialized) { + return SSH_OK; + } + + mbedtls_entropy_init(&ssh_mbedtls_entropy); + mbedtls_ctr_drbg_init(&ssh_mbedtls_ctr_drbg); + + rc = mbedtls_ctr_drbg_seed(&ssh_mbedtls_ctr_drbg, mbedtls_entropy_func, + &ssh_mbedtls_entropy, NULL, 0); + if (rc != 0) { + mbedtls_ctr_drbg_free(&ssh_mbedtls_ctr_drbg); + } + + for (i = 0; ssh_ciphertab[i].name != NULL; i++) { + int cmp; + + cmp = strcmp(ssh_ciphertab[i].name, "chacha20-poly1305@openssh.com"); + if (cmp == 0) { + memcpy(&ssh_ciphertab[i], + ssh_get_chacha20poly1305_cipher(), + sizeof(struct ssh_cipher_struct)); + break; + } + } + + libmbedcrypto_initialized = 1; + + 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; +} + +void ssh_crypto_finalize(void) +{ + if (!libmbedcrypto_initialized) { + return; + } + + mbedtls_ctr_drbg_free(&ssh_mbedtls_ctr_drbg); + mbedtls_entropy_free(&ssh_mbedtls_entropy); + + libmbedcrypto_initialized = 0; +} + +#endif /* HAVE_LIBMBEDCRYPTO */ diff --git a/src/libssh.map b/src/libssh.map new file mode 100644 index 0000000..c9bedee --- /dev/null +++ b/src/libssh.map @@ -0,0 +1,449 @@ +# This map file was updated with abimap-0.3.1 + +LIBSSH_4_5_0 # Released +{ + global: + _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_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_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_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_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_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_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_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_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_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_keepalive; + ssh_server_init_kex; + ssh_service_request; + ssh_session_export_known_hosts_entry; + ssh_session_has_known_hosts_entry; + ssh_session_is_known_server; + 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_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_try_publickey; + ssh_version; + ssh_write_knownhost; + string_burn; + string_copy; + string_data; + string_fill; + string_free; + string_from_char; + string_len; + string_new; + string_to_char; + local: + *; +} ; + +LIBSSH_4_6_0 # Released +{ + global: + ssh_print_hash; +} LIBSSH_4_5_0; + +LIBSSH_4_7_0 # Released +{ + global: + sftp_client_message_get_submessage; + ssh_get_fingerprint_hash; + ssh_pki_export_privkey_base64; +} LIBSSH_4_6_0; + +LIBSSH_4_8_0 # Released +{ + global: + sftp_server_free; + ssh_bind_options_parse_config; + ssh_channel_open_forward_unix; + ssh_message_channel_request_open_reply_accept_channel; +} LIBSSH_4_7_0; + +LIBSSH_4_8_1 # Released +{ + global: + ssh_session_get_known_hosts_entry; + ssh_threads_get_default; +} LIBSSH_4_8_0; diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..a8664b1 --- /dev/null +++ b/src/log.c @@ -0,0 +1,242 @@ +/* + * log.c - logging and debugging functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2013 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 +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#ifdef HAVE_SYS_UTIME_H +#include +#endif /* HAVE_SYS_UTIME_H */ +#include + +#include "libssh/priv.h" +#include "libssh/misc.h" +#include "libssh/session.h" + +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. + * @ingroup libssh + * + * Logging functions for debugging and problem resolving. + * + * @{ + */ + +static int current_timestring(int hires, char *buf, size_t len) +{ + char tbuf[64]; + struct timeval tv; + struct tm *tm; + time_t t; + + gettimeofday(&tv, NULL); + t = (time_t) tv.tv_sec; + + tm = localtime(&t); + if (tm == NULL) { + return -1; + } + + if (hires) { + strftime(tbuf, sizeof(tbuf) - 1, "%Y/%m/%d %H:%M:%S", tm); + snprintf(buf, len, "%s.%06ld", tbuf, (long)tv.tv_usec); + } else { + strftime(tbuf, sizeof(tbuf) - 1, "%Y/%m/%d %H:%M:%S", tm); + snprintf(buf, len, "%s", tbuf); + } + + return 0; +} + +static void ssh_log_stderr(int verbosity, + const char *function, + const char *buffer) +{ + char date[128] = {0}; + int rc; + + rc = current_timestring(1, date, sizeof(date)); + if (rc == 0) { + fprintf(stderr, "[%s, %d] %s:", date, verbosity, function); + } else { + fprintf(stderr, "[%d] %s", verbosity, function); + } + + fprintf(stderr, " %s\n", buffer); +} + +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()); + return; + } + + ssh_log_stderr(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); + va_end(va); + ssh_log_function(verbosity, function, buffer); + } +} + +/* LEGACY */ + +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); + va_end(va); + ssh_log_function(verbosity, "", buffer); + } +} + +/** @internal + * @brief log a SSH event with a common pointer + * @param common The SSH/bind session. + * @param verbosity The verbosity of the event. + * @param format The format string of the log entry. + */ +void ssh_log_common(struct ssh_common_struct *common, + int verbosity, + 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); + va_end(va); + ssh_log_function(verbosity, function, buffer); + } +} + + +/* PUBLIC */ + +/** + * @brief Set the log level of the library. + * + * @param[in] level The level to set. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int ssh_set_log_level(int level) { + if (level < 0) { + return SSH_ERROR; + } + + ssh_log_level = level; + + return SSH_OK; +} + +/** + * @brief Get the log level of the library. + * + * @return The value of the log level. + */ +int ssh_get_log_level(void) { + return ssh_log_level; +} + +int ssh_set_log_callback(ssh_logging_callback cb) { + if (cb == NULL) { + return SSH_ERROR; + } + + ssh_log_cb = cb; + + return SSH_OK; +} + +ssh_logging_callback ssh_get_log_callback(void) { + return ssh_log_cb; +} + +/** + * @brief Get the userdata of the logging function. + * + * @return The userdata if set or NULL. + */ +void *ssh_get_log_userdata(void) +{ + if (ssh_log_userdata == NULL) { + return NULL; + } + + return ssh_log_userdata; +} + +/** + * @brief Set the userdata for the logging function. + * + * @param[in] data The userdata to set. + * + * @return SSH_OK on success. + */ +int ssh_set_log_userdata(void *data) +{ + ssh_log_userdata = data; + + return 0; +} + +/** @} */ diff --git a/src/match.c b/src/match.c new file mode 100644 index 0000000..1a60d73 --- /dev/null +++ b/src/match.c @@ -0,0 +1,197 @@ +/* + * Author: Tatu Ylonen + * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland + * All rights reserved + * Simple pattern matching, with '*' and '?' as wildcards. + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + */ + +/* + * Copyright (c) 2000 Markus Friedl. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 "config.h" + +#include +#include +#include + +#include "libssh/priv.h" + +#define MAX_MATCH_RECURSION 32 + +/* + * Returns true if the given string matches the pattern (which may contain ? + * and * as wildcards), and zero if it does not match. + */ +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; + } + + for (;;) { + /* If at end of pattern, accept if also at end of string. */ + if (*pattern == '\0') { + return (*s == '\0'); + } + + while (*pattern == '*') { + /* Skip the asterisk. */ + had_asterisk = true; + 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; + } + /* + * 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; + } + + /* Move to the next character, both in string and in pattern. */ + s++; + pattern++; + } + + /* NOTREACHED */ + return 0; +} + +/* + * Tries to match the string against the comma-separated sequence of subpatterns + * (each possibly preceded by ! to indicate negation). + * Returns -1 if negation matches, 1 if there is a positive match, 0 if there is + * no match at all. + */ +int match_pattern_list(const char *string, const char *pattern, + unsigned int len, int dolower) { + char sub[1024]; + int negated; + int got_positive; + unsigned int i, subi; + + got_positive = 0; + for (i = 0; i < len;) { + /* Check if the subpattern is negated. */ + if (pattern[i] == '!') { + negated = 1; + i++; + } else { + negated = 0; + } + + /* + * Extract the subpattern up to a comma or end. Convert the + * subpattern to lowercase. + */ + for (subi = 0; + i < len && subi < sizeof(sub) - 1 && pattern[i] != ','; + subi++, i++) { + sub[subi] = dolower && isupper(pattern[i]) ? + (char)tolower(pattern[i]) : pattern[i]; + } + + /* If subpattern too long, return failure (no match). */ + if (subi >= sizeof(sub) - 1) { + return 0; + } + + /* If the subpattern was terminated by a comma, skip the comma. */ + if (i < len && pattern[i] == ',') { + i++; + } + + /* Null-terminate the subpattern. */ + sub[subi] = '\0'; + + /* Try to match the subpattern against the string. */ + if (match_pattern(string, sub, MAX_MATCH_RECURSION)) { + if (negated) { + return -1; /* Negative */ + } else { + got_positive = 1; /* Positive */ + } + } + } + + /* + * Return success if got a positive match. If there was a negative + * match, we have already returned -1 and never get here. + */ + return got_positive; +} + +/* + * Tries to match the host name (which must be in all lowercase) against the + * comma-separated sequence of subpatterns (each possibly preceded by ! to + * indicate negation). + * Returns -1 if negation matches, 1 if there is a positive match, 0 if there + * is no match at all. + */ +int match_hostname(const char *host, const char *pattern, unsigned int len) { + return match_pattern_list(host, pattern, len, 1); +} diff --git a/src/mbedcrypto_missing.c b/src/mbedcrypto_missing.c new file mode 100644 index 0000000..ac0d688 --- /dev/null +++ b/src/mbedcrypto_missing.c @@ -0,0 +1,180 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * 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/priv.h" +#include "libssh/libmbedcrypto.h" + +#ifdef HAVE_LIBMBEDCRYPTO +bignum ssh_mbedcry_bn_new(void) +{ + bignum bn; + + bn = malloc(sizeof(mbedtls_mpi)); + if (bn) { + mbedtls_mpi_init(bn); + } + + return bn; +} + +void ssh_mbedcry_bn_free(bignum bn) +{ + mbedtls_mpi_free(bn); + SAFE_FREE(bn); +} + +unsigned char *ssh_mbedcry_bn2num(const_bignum num, int radix) +{ + char *buf = NULL; + size_t olen; + int rc; + + rc = mbedtls_mpi_write_string(num, radix, buf, 0, &olen); + if (rc != 0 && rc != MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL) { + return NULL; + } + + buf = malloc(olen); + if (buf == NULL) { + return NULL; + } + + rc = mbedtls_mpi_write_string(num, radix, buf, olen, &olen); + if (rc != 0) { + SAFE_FREE(buf); + return NULL; + } + + return (unsigned char *) buf; +} + +int ssh_mbedcry_rand(bignum rnd, int bits, int top, int bottom) +{ + size_t len; + int rc; + int i; + + if (bits <= 0) { + return 0; + } + + len = bits / 8 + 1; + /* FIXME weird bug: over 1024, fill_random function returns an error code + * MBEDTLS_ERR_MPI_BAD_INPUT_DATA -0x0004 + */ + if (len > 1024){ + len = 1024; + } + rc = mbedtls_mpi_fill_random(rnd, + len, + mbedtls_ctr_drbg_random, + ssh_get_mbedtls_ctr_drbg_context()); + if (rc != 0) { + return 0; + } + + for (i = len * 8 - 1; i >= bits; i--) { + rc = mbedtls_mpi_set_bit(rnd, i, 0); + if (rc != 0) { + return 0; + } + } + + if (top == 0) { + rc = mbedtls_mpi_set_bit(rnd, bits - 1, 0); + if (rc != 0) { + return 0; + } + } + + if (top == 1) { + if (bits < 2) { + return 0; + } + + rc = mbedtls_mpi_set_bit(rnd, bits - 2, 0); + if (rc != 0) { + return 0; + } + } + + if (bottom) { + rc = mbedtls_mpi_set_bit(rnd, 0, 1); + if (rc != 0) { + return 0; + } + } + + return 1; +} + +int ssh_mbedcry_is_bit_set(bignum num, size_t pos) +{ + int bit; + bit = mbedtls_mpi_get_bit(num, pos); + return bit; +} + +/** @brief generates a random integer between 0 and max + * @returns 1 in case of success, 0 otherwise + */ +int ssh_mbedcry_rand_range(bignum dest, bignum max) +{ + size_t bits; + bignum rnd; + int rc; + + bits = bignum_num_bits(max) + 64; + rnd = bignum_new(); + if (rnd == NULL){ + return 0; + } + rc = bignum_rand(rnd, bits); + if (rc != 1) { + bignum_safe_free(rnd); + return rc; + } + mbedtls_mpi_mod_mpi(dest, rnd, max); + bignum_safe_free(rnd); + return 1; +} + +int ssh_mbedcry_hex2bn(bignum *dest, char *data) +{ + int rc; + + *dest = bignum_new(); + if (*dest == NULL){ + return 0; + } + rc = mbedtls_mpi_read_string(*dest, 16, data); + if (rc == 0) { + return 1; + } + + return 0; +} + +#endif diff --git a/src/messages.c b/src/messages.c new file mode 100644 index 0000000..25683b2 --- /dev/null +++ b/src/messages.c @@ -0,0 +1,1606 @@ +/* + * messages.c - message parsing for client and server + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 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 +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/dh.h" +#include "libssh/messages.h" +#ifdef WITH_SERVER +#include "libssh/server.h" +#include "libssh/gssapi.h" +#endif + +/** + * @defgroup libssh_messages The SSH message functions + * @ingroup libssh + * + * 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 + * 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 + * if you don't know what to do). + * + * @{ + */ + +static ssh_message ssh_message_new(ssh_session session) +{ + ssh_message msg = calloc(1, sizeof(struct ssh_message_struct)); + if (msg == NULL) { + return NULL; + } + msg->session = session; + + /* Set states explicitly */ + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_NONE; + + return msg; +} + +#ifndef WITH_SERVER + +/* Reduced version of the reply default that only reply with + * SSH_MSG_UNIMPLEMENTED + */ +static int ssh_message_reply_default(ssh_message msg) { + SSH_LOG(SSH_LOG_FUNCTIONS, "Reporting unknown packet"); + + if (ssh_buffer_add_u8(msg->session->out_buffer, SSH2_MSG_UNIMPLEMENTED) < 0) + goto error; + if (ssh_buffer_add_u32(msg->session->out_buffer, + htonl(msg->session->recv_seq-1)) < 0) + goto error; + return ssh_packet_send(msg->session); + error: + return SSH_ERROR; +} + +#endif + +#ifdef WITH_SERVER + +static int ssh_execute_server_request(ssh_session session, ssh_message msg) +{ + ssh_channel channel = NULL; + int rc; + + switch(msg->type) { + case SSH_REQUEST_AUTH: + if (msg->auth_request.method == SSH_AUTH_METHOD_PASSWORD && + ssh_callbacks_exists(session->server_callbacks, auth_password_function)) { + rc = session->server_callbacks->auth_password_function(session, + msg->auth_request.username, msg->auth_request.password, + session->server_callbacks->userdata); + if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL) { + ssh_message_auth_reply_success(msg, rc == SSH_AUTH_PARTIAL); + } else { + ssh_message_reply_default(msg); + } + + return SSH_OK; + } else if(msg->auth_request.method == SSH_AUTH_METHOD_PUBLICKEY && + ssh_callbacks_exists(session->server_callbacks, auth_pubkey_function)) { + rc = session->server_callbacks->auth_pubkey_function(session, + msg->auth_request.username, msg->auth_request.pubkey, + msg->auth_request.signature_state, + session->server_callbacks->userdata); + if (msg->auth_request.signature_state != SSH_PUBLICKEY_STATE_NONE) { + if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL) { + ssh_message_auth_reply_success(msg, rc == SSH_AUTH_PARTIAL); + } else { + ssh_message_reply_default(msg); + } + } else { + if (rc == SSH_AUTH_SUCCESS) { + ssh_message_auth_reply_pk_ok_simple(msg); + } else { + ssh_message_reply_default(msg); + } + } + + return SSH_OK; + } else if (msg->auth_request.method == SSH_AUTH_METHOD_NONE && + ssh_callbacks_exists(session->server_callbacks, auth_none_function)) { + rc = session->server_callbacks->auth_none_function(session, + msg->auth_request.username, session->server_callbacks->userdata); + if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL){ + ssh_message_auth_reply_success(msg, rc == SSH_AUTH_PARTIAL); + } else { + ssh_message_reply_default(msg); + } + + return SSH_OK; + } + break; + case SSH_REQUEST_CHANNEL_OPEN: + if (msg->channel_request_open.type == SSH_CHANNEL_SESSION && + ssh_callbacks_exists(session->server_callbacks, channel_open_request_session_function)) { + channel = session->server_callbacks->channel_open_request_session_function(session, + session->server_callbacks->userdata); + if (channel != NULL) { + rc = ssh_message_channel_request_open_reply_accept_channel(msg, channel); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to send reply for accepting a channel " + "open"); + } + return SSH_OK; + } else { + ssh_message_reply_default(msg); + } + + return SSH_OK; + } + break; + case SSH_REQUEST_CHANNEL: + channel = msg->channel_request.channel; + + if (msg->channel_request.type == SSH_CHANNEL_REQUEST_PTY){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_pty_request_function) { + rc = ssh_callbacks_iterate_exec(channel_pty_request_function, + session, + channel, + msg->channel_request.TERM, + msg->channel_request.width, + msg->channel_request.height, + msg->channel_request.pxwidth, + msg->channel_request.pxheight); + if (rc == 0) { + ssh_message_channel_request_reply_success(msg); + } else { + ssh_message_reply_default(msg); + } + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_SHELL){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_shell_request_function) { + rc = ssh_callbacks_iterate_exec(channel_shell_request_function, + session, + channel); + if (rc == 0) { + ssh_message_channel_request_reply_success(msg); + } else { + ssh_message_reply_default(msg); + } + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_X11){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_x11_req_function) { + ssh_callbacks_iterate_exec(channel_x11_req_function, + session, + channel, + msg->channel_request.x11_single_connection, + msg->channel_request.x11_auth_protocol, + msg->channel_request.x11_auth_cookie, + msg->channel_request.x11_screen_number); + ssh_message_channel_request_reply_success(msg); + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_WINDOW_CHANGE){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_pty_window_change_function) { + rc = ssh_callbacks_iterate_exec(channel_pty_window_change_function, + session, + channel, + msg->channel_request.width, + msg->channel_request.height, + msg->channel_request.pxwidth, + msg->channel_request.pxheight); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to iterate callbacks for window change"); + } + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_EXEC){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_exec_request_function) { + rc = ssh_callbacks_iterate_exec(channel_exec_request_function, + session, + channel, + msg->channel_request.command); + if (rc == 0) { + ssh_message_channel_request_reply_success(msg); + } else { + ssh_message_reply_default(msg); + } + + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_ENV){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_env_request_function) { + rc = ssh_callbacks_iterate_exec(channel_env_request_function, + session, + channel, + msg->channel_request.var_name, + msg->channel_request.var_value); + if (rc == 0) { + ssh_message_channel_request_reply_success(msg); + } else { + ssh_message_reply_default(msg); + } + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_SUBSYSTEM){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_subsystem_request_function) { + rc = ssh_callbacks_iterate_exec(channel_subsystem_request_function, + session, + channel, + msg->channel_request.subsystem); + if (rc == 0) { + ssh_message_channel_request_reply_success(msg); + } else { + ssh_message_reply_default(msg); + } + + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } + break; + case SSH_REQUEST_SERVICE: + if (ssh_callbacks_exists(session->server_callbacks, service_request_function)) { + rc = session->server_callbacks->service_request_function(session, + msg->service_request.service, session->server_callbacks->userdata); + if (rc == 0) { + ssh_message_reply_default(msg); + } else { + ssh_disconnect(session); + } + + return SSH_OK; + } + + return SSH_AGAIN; + case SSH_REQUEST_GLOBAL: + break; + } + + return SSH_AGAIN; +} + +static int ssh_execute_client_request(ssh_session session, ssh_message msg) +{ + ssh_channel channel = NULL; + int rc = SSH_AGAIN; + + if (msg->type == SSH_REQUEST_CHANNEL_OPEN + && msg->channel_request_open.type == SSH_CHANNEL_X11 + && ssh_callbacks_exists(session->common.callbacks, channel_open_request_x11_function)) { + channel = session->common.callbacks->channel_open_request_x11_function (session, + 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; + } 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_OK; + } + + return rc; +} + +/** @internal + * Executes the callbacks defined in session->server_callbacks, out of an ssh_message + * I don't like ssh_message interface but it works. + * @returns SSH_OK if the message has been handled, or SSH_AGAIN otherwise. + */ +static int ssh_execute_server_callbacks(ssh_session session, ssh_message msg){ + int rc = SSH_AGAIN; + + if (session->server_callbacks != NULL){ + rc = ssh_execute_server_request(session, msg); + } else if (session->common.callbacks != NULL) { + /* This one is in fact a client callback... */ + rc = ssh_execute_client_request(session, msg); + } + + return rc; +} + +#endif /* WITH_SERVER */ + +static int ssh_execute_message_callback(ssh_session session, ssh_message msg) { + int ret; + if(session->ssh_message_callback != NULL) { + ret = session->ssh_message_callback(session, msg, + session->ssh_message_callback_data); + if(ret == 1) { + ret = ssh_message_reply_default(msg); + SSH_MESSAGE_FREE(msg); + if(ret != SSH_OK) { + return ret; + } + } else { + SSH_MESSAGE_FREE(msg); + } + } else { + ret = ssh_message_reply_default(msg); + SSH_MESSAGE_FREE(msg); + if(ret != SSH_OK) { + return ret; + } + } + return SSH_OK; +} + +/** + * @internal + * + * @brief Add a message to the current queue of messages to be parsed and/or call + * the various callback functions. + * + * @param[in] session The SSH session to add the message. + * + * @param[in] message The message to add to the queue. + */ +static void ssh_message_queue(ssh_session session, ssh_message message) +{ +#ifdef WITH_SERVER + int ret; +#endif + + if (message == NULL) { + return; + } + +#ifdef WITH_SERVER + /* probably not the best place to execute server callbacks, but still better + * than nothing. + */ + ret = ssh_execute_server_callbacks(session, message); + if (ret == SSH_OK) { + SSH_MESSAGE_FREE(message); + return; + } +#endif /* WITH_SERVER */ + + if (session->ssh_message_callback != NULL) { + /* This will transfer the message, do not free. */ + ssh_execute_message_callback(session, message); + return; + } + + if (session->server_callbacks != NULL) { + /* if we have server callbacks, but nothing was executed, it means we are + * in non-synchronous mode, and we just don't care about the message we + * received. Just send a default response. Do not queue it. + */ + ssh_message_reply_default(message); + SSH_MESSAGE_FREE(message); + return; + } + + if (session->ssh_message_list == NULL) { + session->ssh_message_list = ssh_list_new(); + if (session->ssh_message_list == NULL) { + /* + * If the message list couldn't be allocated, the message can't be + * enqueued + */ + ssh_message_reply_default(message); + ssh_set_error_oom(session); + SSH_MESSAGE_FREE(message); + return; + } + } + + /* This will transfer the message, do not free. */ + ssh_list_append(session->ssh_message_list, message); + return; +} + +/** + * @internal + * + * @brief Pop a message from the message list and dequeue it. + * + * @param[in] session The SSH session to pop the message. + * + * @returns The head message or NULL if it doesn't exist. + */ +ssh_message ssh_message_pop_head(ssh_session session){ + ssh_message msg=NULL; + struct ssh_iterator *i; + if(session->ssh_message_list == NULL) + return NULL; + i=ssh_list_get_iterator(session->ssh_message_list); + if(i != NULL){ + msg=ssh_iterator_value(ssh_message,i); + ssh_list_remove(session->ssh_message_list,i); + } + return msg; +} + +/* Returns 1 if there is a message available */ +static int ssh_message_termination(void *s){ + ssh_session session = s; + struct ssh_iterator *it; + if(session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + it = ssh_list_get_iterator(session->ssh_message_list); + if(!it) + return 0; + else + return 1; +} +/** + * @brief Retrieve a SSH message from a SSH session. + * + * @param[in] session The SSH session to get the message. + * + * @returns The SSH message received, NULL in case of error, or timeout + * elapsed. + * + * @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; + + 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); + + return msg; +} + +/** + * @brief Get the type of the message. + * + * @param[in] msg The message to get the type from. + * + * @return The message type or -1 on error. + */ +int ssh_message_type(ssh_message msg) { + if (msg == NULL) { + return -1; + } + + return msg->type; +} + +/** + * @brief Get the subtype of the message. + * + * @param[in] msg The message to get the subtype from. + * + * @return The message type or -1 on error. + */ +int ssh_message_subtype(ssh_message msg) { + if (msg == NULL) { + return -1; + } + + switch(msg->type) { + case SSH_REQUEST_AUTH: + return msg->auth_request.method; + case SSH_REQUEST_CHANNEL_OPEN: + return msg->channel_request_open.type; + case SSH_REQUEST_CHANNEL: + return msg->channel_request.type; + case SSH_REQUEST_GLOBAL: + return msg->global_request.type; + } + + return -1; +} + +/** + * @brief Free a SSH message. + * + * @param[in] msg The message to release the memory. + */ +void ssh_message_free(ssh_message msg){ + if (msg == NULL) { + return; + } + + switch(msg->type) { + case SSH_REQUEST_AUTH: + SAFE_FREE(msg->auth_request.username); + if (msg->auth_request.password) { + explicit_bzero(msg->auth_request.password, + strlen(msg->auth_request.password)); + SAFE_FREE(msg->auth_request.password); + } + ssh_key_free(msg->auth_request.pubkey); + break; + case SSH_REQUEST_CHANNEL_OPEN: + SAFE_FREE(msg->channel_request_open.originator); + SAFE_FREE(msg->channel_request_open.destination); + break; + case SSH_REQUEST_CHANNEL: + SAFE_FREE(msg->channel_request.TERM); + SAFE_FREE(msg->channel_request.modes); + SAFE_FREE(msg->channel_request.var_name); + SAFE_FREE(msg->channel_request.var_value); + SAFE_FREE(msg->channel_request.command); + SAFE_FREE(msg->channel_request.subsystem); + switch (msg->channel_request.type) { + case SSH_CHANNEL_REQUEST_EXEC: + SAFE_FREE(msg->channel_request.command); + break; + case SSH_CHANNEL_REQUEST_ENV: + SAFE_FREE(msg->channel_request.var_name); + SAFE_FREE(msg->channel_request.var_value); + break; + case SSH_CHANNEL_REQUEST_PTY: + SAFE_FREE(msg->channel_request.TERM); + break; + case SSH_CHANNEL_REQUEST_SUBSYSTEM: + SAFE_FREE(msg->channel_request.subsystem); + break; + case SSH_CHANNEL_REQUEST_X11: + SAFE_FREE(msg->channel_request.x11_auth_protocol); + SAFE_FREE(msg->channel_request.x11_auth_cookie); + break; + } + break; + case SSH_REQUEST_SERVICE: + SAFE_FREE(msg->service_request.service); + break; + case SSH_REQUEST_GLOBAL: + SAFE_FREE(msg->global_request.bind_address); + break; + } + ZERO_STRUCTP(msg); + SAFE_FREE(msg); +} + +#ifdef WITH_SERVER + +SSH_PACKET_CALLBACK(ssh_packet_service_request) +{ + char *service_c = NULL; + ssh_message msg = NULL; + int rc; + + (void)type; + (void)user; + + rc = ssh_buffer_unpack(packet, + "s", + &service_c); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Invalid SSH_MSG_SERVICE_REQUEST packet"); + goto error; + } + + SSH_LOG(SSH_LOG_PACKET, + "Received a SERVICE_REQUEST for service %s", + service_c); + + msg = ssh_message_new(session); + if (msg == NULL) { + SAFE_FREE(service_c); + goto error; + } + + msg->type = SSH_REQUEST_SERVICE; + msg->service_request.service = service_c; + + ssh_message_queue(session, msg); +error: + + return SSH_PACKET_USED; +} + + +/* + * This function concats in a buffer the values needed to do a signature + * verification. + */ +static ssh_buffer ssh_msg_userauth_build_digest(ssh_session session, + ssh_message msg, + const char *service, + ssh_string algo) +{ + struct ssh_crypto_struct *crypto = NULL; + ssh_buffer buffer; + ssh_string str=NULL; + int rc; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto == NULL) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + rc = ssh_pki_export_pubkey_blob(msg->auth_request.pubkey, &str); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "dPbsssbsS", + crypto->digest_len, /* session ID string */ + (size_t)crypto->digest_len, crypto->session_id, + SSH2_MSG_USERAUTH_REQUEST, /* type */ + msg->auth_request.username, + service, + "publickey", /* method */ + 1, /* has to be signed (true) */ + ssh_string_get_char(algo), /* pubkey algorithm */ + str); /* public key as a blob */ + + SSH_STRING_FREE(str); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(buffer); + return NULL; + } + + return buffer; +} + +/** + * @internal + * + * @brief Handle a SSH_MSG_MSG_USERAUTH_REQUEST packet and queue a + * SSH Message + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_request){ + ssh_message msg = NULL; + ssh_signature sig = NULL; + char *service = NULL; + char *method = NULL; + int cmp; + int rc; + + (void)user; + (void)type; + + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + msg->type = SSH_REQUEST_AUTH; + rc = ssh_buffer_unpack(packet, + "sss", + &msg->auth_request.username, + &service, + &method); + + if (rc != SSH_OK) { + goto error; + } + + SSH_LOG(SSH_LOG_PACKET, + "Auth request for service %s, method %s for user '%s'", + service, method, + msg->auth_request.username); + + cmp = strcmp(service, "ssh-connection"); + if (cmp != 0) { + SSH_LOG(SSH_LOG_WARNING, + "Invalid service request: %s", + service); + goto end; + } + + if (strcmp(method, "none") == 0) { + msg->auth_request.method = SSH_AUTH_METHOD_NONE; + goto end; + } + + if (strcmp(method, "password") == 0) { + uint8_t tmp; + + msg->auth_request.method = SSH_AUTH_METHOD_PASSWORD; + rc = ssh_buffer_unpack(packet, "bs", &tmp, &msg->auth_request.password); + if (rc != SSH_OK) { + goto error; + } + goto end; + } + + if (strcmp(method, "keyboard-interactive") == 0) { + ssh_string lang = NULL; + ssh_string submethods = NULL; + + msg->auth_request.method = SSH_AUTH_METHOD_INTERACTIVE; + lang = ssh_buffer_get_ssh_string(packet); + if (lang == NULL) { + goto error; + } + /* from the RFC 4256 + * 3.1. Initial Exchange + * "The language tag is deprecated and SHOULD be the empty string." + */ + SSH_STRING_FREE(lang); + + submethods = ssh_buffer_get_ssh_string(packet); + if (submethods == NULL) { + goto error; + } + /* from the RFC 4256 + * 3.1. Initial Exchange + * "One possible implementation strategy of the submethods field on the + * server is that, unless the user may use multiple different + * submethods, the server ignores this field." + */ + SSH_STRING_FREE(submethods); + + goto end; + } + + if (strcmp(method, "publickey") == 0) { + ssh_string algo = NULL; + ssh_string pubkey_blob = NULL; + uint8_t has_sign; + + msg->auth_request.method = SSH_AUTH_METHOD_PUBLICKEY; + SAFE_FREE(method); + rc = ssh_buffer_unpack(packet, "bSS", + &has_sign, + &algo, + &pubkey_blob + ); + + if (rc != SSH_OK) { + goto error; + } + + rc = ssh_pki_import_pubkey_blob(pubkey_blob, &msg->auth_request.pubkey); + SSH_STRING_FREE(pubkey_blob); + pubkey_blob = NULL; + if (rc < 0) { + SSH_STRING_FREE(algo); + algo = NULL; + goto error; + } + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_NONE; + // has a valid signature ? + if(has_sign) { + ssh_string sig_blob = NULL; + ssh_buffer digest = NULL; + + sig_blob = ssh_buffer_get_ssh_string(packet); + if(sig_blob == NULL) { + SSH_LOG(SSH_LOG_PACKET, "Invalid signature packet from peer"); + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_ERROR; + SSH_STRING_FREE(algo); + algo = NULL; + goto error; + } + + digest = ssh_msg_userauth_build_digest(session, msg, service, algo); + SSH_STRING_FREE(algo); + algo = NULL; + if (digest == NULL) { + SSH_STRING_FREE(sig_blob); + SSH_LOG(SSH_LOG_PACKET, "Failed to get digest"); + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_WRONG; + goto error; + } + + rc = ssh_pki_import_signature_blob(sig_blob, + msg->auth_request.pubkey, + &sig); + if (rc == SSH_OK) { + /* Check if the signature from client matches server preferences */ + if (session->opts.pubkey_accepted_types) { + if (!ssh_match_group(session->opts.pubkey_accepted_types, + sig->type_c)) + { + ssh_set_error(session, + SSH_FATAL, + "Public key from client (%s) doesn't match server " + "preference (%s)", + sig->type_c, + session->opts.pubkey_accepted_types); + rc = SSH_ERROR; + } + } + + if (rc == SSH_OK) { + rc = ssh_pki_signature_verify(session, + sig, + msg->auth_request.pubkey, + ssh_buffer_get(digest), + ssh_buffer_get_len(digest)); + } + } + SSH_STRING_FREE(sig_blob); + SSH_BUFFER_FREE(digest); + ssh_signature_free(sig); + if (rc < 0) { + SSH_LOG( + SSH_LOG_PACKET, + "Received an invalid signature from peer"); + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_WRONG; + goto error; + } + + SSH_LOG(SSH_LOG_PACKET, "Valid signature received"); + + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_VALID; + } + SSH_STRING_FREE(algo); + goto end; + } +#ifdef WITH_GSSAPI + if (strcmp(method, "gssapi-with-mic") == 0) { + uint32_t n_oid; + ssh_string *oids; + ssh_string oid; + char *hexa; + int i; + ssh_buffer_get_u32(packet, &n_oid); + n_oid=ntohl(n_oid); + if(n_oid > 100){ + ssh_set_error(session, SSH_FATAL, "USERAUTH_REQUEST: gssapi-with-mic OID count too big (%d)",n_oid); + goto error; + } + SSH_LOG(SSH_LOG_PACKET, "gssapi: %d OIDs", n_oid); + oids = calloc(n_oid, sizeof(ssh_string)); + if (oids == NULL){ + ssh_set_error_oom(session); + goto error; + } + for (i=0;i<(int) n_oid;++i){ + oid=ssh_buffer_get_ssh_string(packet); + if(oid == NULL){ + for(i=i-1;i>=0;--i){ + SAFE_FREE(oids[i]); + } + SAFE_FREE(oids); + ssh_set_error(session, SSH_LOG_PACKET, "USERAUTH_REQUEST: gssapi-with-mic missing OID"); + goto error; + } + oids[i] = oid; + if(session->common.log_verbosity >= SSH_LOG_PACKET){ + hexa = ssh_get_hexa(ssh_string_data(oid), ssh_string_len(oid)); + SSH_LOG(SSH_LOG_PACKET,"gssapi: OID %d: %s",i, hexa); + SAFE_FREE(hexa); + } + } + ssh_gssapi_handle_userauth(session, msg->auth_request.username, n_oid, oids); + + for(i=0;i<(int)n_oid;++i){ + SAFE_FREE(oids[i]); + } + SAFE_FREE(oids); + /* bypass the message queue thing */ + SAFE_FREE(service); + SAFE_FREE(method); + SSH_MESSAGE_FREE(msg); + + return SSH_PACKET_USED; + } +#endif + + msg->auth_request.method = SSH_AUTH_METHOD_UNKNOWN; + SAFE_FREE(method); + goto end; +error: + SAFE_FREE(service); + SAFE_FREE(method); + + SSH_MESSAGE_FREE(msg); + + return SSH_PACKET_USED; +end: + SAFE_FREE(service); + SAFE_FREE(method); + + ssh_message_queue(session,msg); + + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ +/** + * @internal + * + * @brief Handle a SSH_MSG_MSG_USERAUTH_INFO_RESPONSE packet and queue a + * SSH Message + */ +#ifndef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ + (void)session; + (void)type; + (void)packet; + (void)user; + return SSH_PACKET_USED; +} +#else /* WITH_SERVER */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ + uint32_t nanswers; + uint32_t i; + ssh_string tmp; + int rc; + + ssh_message msg = NULL; + + /* GSSAPI_TOKEN has same packed number. XXX fix this */ +#ifdef WITH_GSSAPI + if (session->gssapi != NULL) { + return ssh_packet_userauth_gssapi_token(session, type, packet, user); + } +#endif + (void)user; + (void)type; + + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + + /* HACK: we forge a message to be able to handle it in the + * same switch() as other auth methods */ + msg->type = SSH_REQUEST_AUTH; + msg->auth_request.method = SSH_AUTH_METHOD_INTERACTIVE; + msg->auth_request.kbdint_response = 1; +#if 0 // should we wipe the username ? + msg->auth_request.username = NULL; +#endif + + rc = ssh_buffer_unpack(packet, "d", &nanswers); + if (rc != SSH_OK) { + ssh_set_error_invalid(session); + goto error; + } + + if (session->kbdint == NULL) { + SSH_LOG(SSH_LOG_PROTOCOL, "Warning: Got a keyboard-interactive " + "response but it seems we didn't send the request."); + + session->kbdint = ssh_kbdint_new(); + if (session->kbdint == NULL) { + ssh_set_error_oom(session); + + goto error; + } + } else if (session->kbdint->answers != NULL) { + uint32_t n; + + for (n = 0; n < session->kbdint->nanswers; n++) { + explicit_bzero(session->kbdint->answers[n], + strlen(session->kbdint->answers[n])); + SAFE_FREE(session->kbdint->answers[n]); + } + SAFE_FREE(session->kbdint->answers); + session->kbdint->nanswers = 0; + } + + SSH_LOG(SSH_LOG_PACKET,"kbdint: %d answers",nanswers); + if (nanswers > KBDINT_MAX_PROMPT) { + ssh_set_error(session, SSH_FATAL, + "Too much answers received from client: %u (0x%.4x)", + nanswers, nanswers); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + goto error; + } + + 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); + } + session->kbdint->nanswers = nanswers; + + session->kbdint->answers = calloc(nanswers, sizeof(char *)); + if (session->kbdint->answers == NULL) { + session->kbdint->nanswers = 0; + ssh_set_error_oom(session); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + goto error; + } + + for (i = 0; i < nanswers; i++) { + tmp = ssh_buffer_get_ssh_string(packet); + if (tmp == NULL) { + ssh_set_error(session, SSH_FATAL, "Short INFO_RESPONSE packet"); + session->kbdint->nanswers = i; + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + goto error; + } + session->kbdint->answers[i] = ssh_string_to_char(tmp); + SSH_STRING_FREE(tmp); + if (session->kbdint->answers[i] == NULL) { + ssh_set_error_oom(session); + session->kbdint->nanswers = i; + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + goto error; + } + } + + ssh_message_queue(session,msg); + + return SSH_PACKET_USED; + +error: + SSH_MESSAGE_FREE(msg); + + return SSH_PACKET_USED; +} +#endif /* WITH_SERVER */ + +SSH_PACKET_CALLBACK(ssh_packet_channel_open){ + ssh_message msg = NULL; + char *type_c = NULL; + uint32_t originator_port, destination_port; + int rc; + + (void)type; + (void)user; + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + + msg->type = SSH_REQUEST_CHANNEL_OPEN; + rc = ssh_buffer_unpack(packet, "s", &type_c); + if (rc != SSH_OK){ + goto error; + } + + SSH_LOG(SSH_LOG_PACKET, + "Clients wants to open a %s channel", type_c); + + ssh_buffer_unpack(packet,"ddd", + &msg->channel_request_open.sender, + &msg->channel_request_open.window, + &msg->channel_request_open.packet_size); + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED){ + ssh_set_error(session,SSH_FATAL, "Invalid state when receiving channel open request (must be authenticated)"); + goto error; + } + + if (strcmp(type_c,"session") == 0) { + msg->channel_request_open.type = SSH_CHANNEL_SESSION; + SAFE_FREE(type_c); + goto end; + } + + if (strcmp(type_c,"direct-tcpip") == 0) { + rc = ssh_buffer_unpack(packet, + "sdsd", + &msg->channel_request_open.destination, + &destination_port, + &msg->channel_request_open.originator, + &originator_port); + 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; + msg->channel_request_open.type = SSH_CHANNEL_DIRECT_TCPIP; + goto end; + } + + if (strcmp(type_c,"forwarded-tcpip") == 0) { + rc = ssh_buffer_unpack(packet, "sdsd", + &msg->channel_request_open.destination, + &destination_port, + &msg->channel_request_open.originator, + &originator_port + ); + 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; + msg->channel_request_open.type = SSH_CHANNEL_FORWARDED_TCPIP; + goto end; + } + + if (strcmp(type_c,"x11") == 0) { + rc = ssh_buffer_unpack(packet, "sd", + &msg->channel_request_open.originator, + &originator_port); + if (rc != SSH_OK){ + goto error; + } + msg->channel_request_open.originator_port = (uint16_t) originator_port; + msg->channel_request_open.type = SSH_CHANNEL_X11; + goto end; + } + + if (strcmp(type_c,"auth-agent@openssh.com") == 0) { + msg->channel_request_open.type = SSH_CHANNEL_AUTH_AGENT; + goto end; + } + + msg->channel_request_open.type = SSH_CHANNEL_UNKNOWN; + goto end; + +error: + SSH_MESSAGE_FREE(msg); +end: + SAFE_FREE(type_c); + if(msg != NULL) + ssh_message_queue(session,msg); + + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief This function accepts a channel open request for the specified channel. + * + * @param[in] msg The message. + * + * @param[in] chan The channel the request is made on. + * + * @returns SSH_OK on success, SSH_ERROR if an error occured. + */ +int ssh_message_channel_request_open_reply_accept_channel(ssh_message msg, ssh_channel chan) { + ssh_session session; + int rc; + + if (msg == NULL) { + return SSH_ERROR; + } + + session = msg->session; + + chan->local_channel = ssh_channel_new_id(session); + chan->local_maxpacket = 35000; + chan->local_window = 32000; + chan->remote_channel = msg->channel_request_open.sender; + chan->remote_maxpacket = msg->channel_request_open.packet_size; + chan->remote_window = msg->channel_request_open.window; + chan->state = SSH_CHANNEL_STATE_OPEN; + chan->flags &= ~SSH_CHANNEL_FLAG_NOT_BOUND; + + rc = ssh_buffer_pack(session->out_buffer, + "bdddd", + SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, + chan->remote_channel, + chan->local_channel, + chan->local_window, + chan->local_maxpacket); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PACKET, + "Accepting a channel request_open for chan %d", + chan->remote_channel); + + rc = ssh_packet_send(session); + + return rc; +} + +/** + * @internal + * + * @brief This function accepts a channel open request. + * + * @param[in] msg The message. + * + * @returns a valid ssh_channel handle if the request is to be allowed + * + * @returns NULL in case of error + */ +ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg) { + ssh_channel chan; + int rc; + + if (msg == NULL) { + return NULL; + } + + chan = ssh_channel_new(msg->session); + if (chan == NULL) { + return NULL; + } + rc = ssh_message_channel_request_open_reply_accept_channel(msg, chan); + if (rc < 0) { + ssh_channel_free(chan); + chan = NULL; + } + return chan; + +} + +/** + * @internal + * + * @brief This function parses the last end of a channel request packet. + * + * This is normally converted to a SSH message and placed in the queue. + * + * @param[in] session The SSH session. + * + * @param[in] channel The channel the request is made on. + * + * @param[in] packet The rest of the packet to be parsed. + * + * @param[in] request The type of request. + * + * @param[in] want_reply The want_reply field from the request. + * + * @returns SSH_OK on success, SSH_ERROR if an error occured. + */ +int ssh_message_handle_channel_request(ssh_session session, ssh_channel channel, ssh_buffer packet, + const char *request, uint8_t want_reply) { + ssh_message msg = NULL; + int rc; + + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + + SSH_LOG(SSH_LOG_PACKET, + "Received a %s channel_request for channel (%d:%d) (want_reply=%hhd)", + request, channel->local_channel, channel->remote_channel, want_reply); + + msg->type = SSH_REQUEST_CHANNEL; + msg->channel_request.channel = channel; + msg->channel_request.want_reply = want_reply; + + if (strcmp(request, "pty-req") == 0) { + rc = ssh_buffer_unpack(packet, "sddddS", + &msg->channel_request.TERM, + &msg->channel_request.width, + &msg->channel_request.height, + &msg->channel_request.pxwidth, + &msg->channel_request.pxheight, + &msg->channel_request.modes + ); + + msg->channel_request.type = SSH_CHANNEL_REQUEST_PTY; + + if (rc != SSH_OK) { + goto error; + } + goto end; + } + + if (strcmp(request, "window-change") == 0) { + msg->channel_request.type = SSH_CHANNEL_REQUEST_WINDOW_CHANGE; + rc = ssh_buffer_unpack(packet, "dddd", + &msg->channel_request.width, + &msg->channel_request.height, + &msg->channel_request.pxwidth, + &msg->channel_request.pxheight); + if (rc != SSH_OK){ + goto error; + } + goto end; + } + + if (strcmp(request, "subsystem") == 0) { + rc = ssh_buffer_unpack(packet, "s", + &msg->channel_request.subsystem); + msg->channel_request.type = SSH_CHANNEL_REQUEST_SUBSYSTEM; + if (rc != SSH_OK){ + goto error; + } + goto end; + } + + if (strcmp(request, "shell") == 0) { + msg->channel_request.type = SSH_CHANNEL_REQUEST_SHELL; + goto end; + } + + if (strcmp(request, "exec") == 0) { + rc = ssh_buffer_unpack(packet, "s", + &msg->channel_request.command); + msg->channel_request.type = SSH_CHANNEL_REQUEST_EXEC; + if (rc != SSH_OK) { + goto error; + } + goto end; + } + + if (strcmp(request, "env") == 0) { + rc = ssh_buffer_unpack(packet, "ss", + &msg->channel_request.var_name, + &msg->channel_request.var_value); + msg->channel_request.type = SSH_CHANNEL_REQUEST_ENV; + if (rc != SSH_OK) { + goto error; + } + goto end; + } + + if (strcmp(request, "x11-req") == 0) { + rc = ssh_buffer_unpack(packet, "bssd", + &msg->channel_request.x11_single_connection, + &msg->channel_request.x11_auth_protocol, + &msg->channel_request.x11_auth_cookie, + &msg->channel_request.x11_screen_number); + + msg->channel_request.type = SSH_CHANNEL_REQUEST_X11; + if (rc != SSH_OK) { + goto error; + } + + goto end; + } + + msg->channel_request.type = SSH_CHANNEL_REQUEST_UNKNOWN; +end: + ssh_message_queue(session,msg); + + return SSH_OK; +error: + SSH_MESSAGE_FREE(msg); + + return SSH_ERROR; +} + +int ssh_message_channel_request_reply_success(ssh_message msg) { + uint32_t channel; + int rc; + + if (msg == NULL) { + return SSH_ERROR; + } + + if (msg->channel_request.want_reply) { + channel = msg->channel_request.channel->remote_channel; + + SSH_LOG(SSH_LOG_PACKET, + "Sending a channel_request success to channel %d", channel); + + rc = ssh_buffer_pack(msg->session->out_buffer, + "bd", + SSH2_MSG_CHANNEL_SUCCESS, + channel); + if (rc != SSH_OK){ + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + + return ssh_packet_send(msg->session); + } + + SSH_LOG(SSH_LOG_PACKET, + "The client doesn't want to know the request succeeded"); + + return SSH_OK; +} + +#ifdef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_global_request){ + ssh_message msg = NULL; + char *request=NULL; + uint8_t want_reply; + int rc = SSH_PACKET_USED; + int r; + (void)user; + (void)type; + (void)packet; + + SSH_LOG(SSH_LOG_PROTOCOL,"Received SSH_MSG_GLOBAL_REQUEST packet"); + r = ssh_buffer_unpack(packet, "sb", + &request, + &want_reply); + if (r != SSH_OK){ + goto error; + } + + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + msg->type = SSH_REQUEST_GLOBAL; + + if (strcmp(request, "tcpip-forward") == 0) { + + /* According to RFC4254, the client SHOULD reject this message */ + if (session->client) { + goto reply_with_failure; + } + + r = ssh_buffer_unpack(packet, "sd", + &msg->global_request.bind_address, + &msg->global_request.bind_port + ); + if (r != SSH_OK){ + goto reply_with_failure; + } + 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, + 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, + want_reply, msg->global_request.bind_address, + msg->global_request.bind_port); + session->common.callbacks->global_request_function(session, msg, session->common.callbacks->userdata); + } else { + SAFE_FREE(request); + ssh_message_queue(session, msg); + return rc; + } + } else if (strcmp(request, "cancel-tcpip-forward") == 0) { + + /* According to RFC4254, the client SHOULD reject this message */ + if (session->client) { + goto reply_with_failure; + } + + r = ssh_buffer_unpack(packet, "sd", + &msg->global_request.bind_address, + &msg->global_request.bind_port); + if (r != SSH_OK){ + goto reply_with_failure; + } + 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, + msg->global_request.bind_address, + msg->global_request.bind_port); + + if(ssh_callbacks_exists(session->common.callbacks, global_request_function)) { + session->common.callbacks->global_request_function(session, msg, session->common.callbacks->userdata); + } else { + SAFE_FREE(request); + ssh_message_queue(session, msg); + return rc; + } + } 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); + 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 { + SSH_LOG(SSH_LOG_PROTOCOL, "UNKNOWN SSH_MSG_GLOBAL_REQUEST %s, " + "want_reply = %d", request, want_reply); + goto reply_with_failure; + } + + SAFE_FREE(msg); + SAFE_FREE(request); + return rc; + +reply_with_failure: + /* Only report the failure if requested */ + if (want_reply) { + r = ssh_buffer_add_u8(session->out_buffer, + SSH2_MSG_REQUEST_FAILURE); + if (r < 0) { + ssh_set_error_oom(session); + goto error; + } + + r = ssh_packet_send(session); + if (r != SSH_OK) { + goto error; + } + } else { + SSH_LOG(SSH_LOG_PACKET, + "The requester doesn't want to know the request failed!"); + } + + /* Consume the message to avoid sending UNIMPLEMENTED later */ + rc = SSH_PACKET_USED; +error: + SAFE_FREE(msg); + SAFE_FREE(request); + SSH_LOG(SSH_LOG_WARNING, "Invalid SSH_MSG_GLOBAL_REQUEST packet"); + return rc; +} + +#endif /* WITH_SERVER */ + +/** @} */ diff --git a/src/misc.c b/src/misc.c new file mode 100644 index 0000000..0f1a7d4 --- /dev/null +++ b/src/misc.c @@ -0,0 +1,1738 @@ +/* + * misc.c - useful client functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * Copyright (c) 2008-2009 by Andreas Schneider + * + * 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" + +#ifndef _WIN32 +/* This is needed for a standard getpwuid_r on opensolaris */ +#define _POSIX_PTHREAD_SEMANTICS +#include +#include +#include +#include +#include + +#endif /* _WIN32 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ + + +#ifdef _WIN32 + +#ifndef _WIN32_IE +# define _WIN32_IE 0x0501 // SHGetSpecialFolderPath +#endif + +#include // Must be the first to include +#include +#include +#include + +#ifdef HAVE_IO_H +#include +#endif /* HAVE_IO_H */ + +#endif /* _WIN32 */ + +#include "libssh/priv.h" +#include "libssh/misc.h" +#include "libssh/session.h" + +#ifdef HAVE_LIBGCRYPT +#define GCRYPT_STRING "/gnutls" +#else +#define GCRYPT_STRING "" +#endif + +#ifdef HAVE_LIBCRYPTO +#define CRYPTO_STRING "/openssl" +#else +#define CRYPTO_STRING "" +#endif + +#ifdef HAVE_LIBMBEDCRYPTO +#define MBED_STRING "/mbedtls" +#else +#define MBED_STRING "" +#endif + +#ifdef WITH_ZLIB +#define ZLIB_STRING "/zlib" +#else +#define ZLIB_STRING "" +#endif + +/** + * @defgroup libssh_misc The SSH helper functions. + * @ingroup libssh + * + * Different helper functions used in the SSH Library. + * + * @{ + */ + +#ifdef _WIN32 +char *ssh_get_user_home_dir(void) { + char tmp[MAX_PATH] = {0}; + char *szPath = NULL; + + if (SHGetSpecialFolderPathA(NULL, tmp, CSIDL_PROFILE, TRUE)) { + szPath = malloc(strlen(tmp) + 1); + if (szPath == NULL) { + return NULL; + } + + strcpy(szPath, tmp); + return szPath; + } + + return NULL; +} + +/* we have read access on file */ +int ssh_file_readaccess_ok(const char *file) { + if (_access(file, 4) < 0) { + return 0; + } + + return 1; +} + +/** + * @brief Check if the given path is an existing directory and that is + * accessible for writing. + * + * @param[in] path Path to the directory to be checked + * + * @return Return 1 if the directory exists and is accessible; 0 otherwise + * */ +int ssh_dir_writeable(const char *path) +{ + struct _stat buffer; + int rc; + + rc = _stat(path, &buffer); + if (rc < 0) { + return 0; + } + + if ((buffer.st_mode & _S_IFDIR) && (buffer.st_mode & _S_IWRITE)) { + return 1; + } + + return 0; +} + +#define SSH_USEC_IN_SEC 1000000LL +#define SSH_SECONDS_SINCE_1601 11644473600LL + +int gettimeofday(struct timeval *__p, void *__t) { + union { + unsigned long long ns100; /* time since 1 Jan 1601 in 100ns units */ + FILETIME ft; + } now; + + GetSystemTimeAsFileTime (&now.ft); + __p->tv_usec = (long) ((now.ns100 / 10LL) % SSH_USEC_IN_SEC); + __p->tv_sec = (long)(((now.ns100 / 10LL ) / SSH_USEC_IN_SEC) - SSH_SECONDS_SINCE_1601); + + return (0); +} + +char *ssh_get_local_username(void) { + DWORD size = 0; + char *user; + + /* get the size */ + GetUserName(NULL, &size); + + user = (char *) malloc(size); + if (user == NULL) { + return NULL; + } + + if (GetUserName(user, &size)) { + return user; + } + + return NULL; +} + +int ssh_is_ipaddr_v4(const char *str) { + struct sockaddr_storage ss; + int sslen = sizeof(ss); + int rc = SOCKET_ERROR; + + /* WSAStringToAddressA thinks that 0.0.0 is a valid IP */ + if (strlen(str) < 7) { + return 0; + } + + rc = WSAStringToAddressA((LPSTR) str, + AF_INET, + NULL, + (struct sockaddr*)&ss, + &sslen); + if (rc == 0) { + return 1; + } + + return 0; +} + +int ssh_is_ipaddr(const char *str) { + int rc = SOCKET_ERROR; + + if (strchr(str, ':')) { + struct sockaddr_storage ss; + int sslen = sizeof(ss); + + /* TODO link-local (IP:v6:addr%ifname). */ + rc = WSAStringToAddressA((LPSTR) str, + AF_INET6, + NULL, + (struct sockaddr*)&ss, + &sslen); + if (rc == 0) { + return 1; + } + } + + return ssh_is_ipaddr_v4(str); +} +#else /* _WIN32 */ + +#ifndef NSS_BUFLEN_PASSWD +#define NSS_BUFLEN_PASSWD 4096 +#endif /* NSS_BUFLEN_PASSWD */ + +char *ssh_get_user_home_dir(void) +{ + char *szPath = NULL; + struct passwd pwd; + struct passwd *pwdbuf = NULL; + char buf[NSS_BUFLEN_PASSWD] = {0}; + int rc; + + rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf); + if (rc != 0 || pwdbuf == NULL ) { + szPath = getenv("HOME"); + if (szPath == NULL) { + return NULL; + } + snprintf(buf, sizeof(buf), "%s", szPath); + + return strdup(buf); + } + + szPath = strdup(pwd.pw_dir); + + return szPath; +} + +/* we have read access on file */ +int ssh_file_readaccess_ok(const char *file) +{ + if (access(file, R_OK) < 0) { + return 0; + } + + return 1; +} + +/** + * @brief Check if the given path is an existing directory and that is + * accessible for writing. + * + * @param[in] path Path to the directory to be checked + * + * @return Return 1 if the directory exists and is accessible; 0 otherwise + * */ +int ssh_dir_writeable(const char *path) +{ + struct stat buffer; + int rc; + + rc = stat(path, &buffer); + if (rc < 0) { + return 0; + } + + if (S_ISDIR(buffer.st_mode) && (buffer.st_mode & S_IWRITE)) { + return 1; + } + + return 0; +} + +char *ssh_get_local_username(void) +{ + struct passwd pwd; + struct passwd *pwdbuf = NULL; + char buf[NSS_BUFLEN_PASSWD]; + char *name; + int rc; + + rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf); + if (rc != 0 || pwdbuf == NULL) { + return NULL; + } + + name = strdup(pwd.pw_name); + + if (name == NULL) { + return NULL; + } + + return name; +} + +int ssh_is_ipaddr_v4(const char *str) { + int rc = -1; + struct in_addr dest; + + rc = inet_pton(AF_INET, str, &dest); + if (rc > 0) { + return 1; + } + + return 0; +} + +int ssh_is_ipaddr(const char *str) { + int rc = -1; + + if (strchr(str, ':')) { + struct in6_addr dest6; + + /* TODO link-local (IP:v6:addr%ifname). */ + rc = inet_pton(AF_INET6, str, &dest6); + if (rc > 0) { + return 1; + } + } + + return ssh_is_ipaddr_v4(str); +} + +#endif /* _WIN32 */ + +char *ssh_lowercase(const char* str) { + char *new, *p; + + if (str == NULL) { + return NULL; + } + + new = strdup(str); + if (new == NULL) { + return NULL; + } + + for (p = new; *p; p++) { + *p = tolower(*p); + } + + return new; +} + +char *ssh_hostport(const char *host, int port) +{ + char *dest = NULL; + size_t len; + + if (host == NULL) { + return NULL; + } + + /* 3 for []:, 5 for 65536 and 1 for nul */ + len = strlen(host) + 3 + 5 + 1; + dest = malloc(len); + if (dest == NULL) { + return NULL; + } + snprintf(dest, len, "[%s]:%d", host, port); + + return dest; +} + +/** + * @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 len Length of the buffer to convert. + * + * @return The hex string or NULL on error. + * + * @see ssh_string_free_char() + */ +char *ssh_get_hexa(const unsigned char *what, size_t len) { + const char h[] = "0123456789abcdef"; + char *hexa; + size_t i; + size_t hlen = len * 3; + + if (len > (UINT_MAX - 1) / 3) { + return NULL; + } + + hexa = malloc(hlen + 1); + if (hexa == NULL) { + return NULL; + } + + for (i = 0; i < len; i++) { + hexa[i * 3] = h[(what[i] >> 4) & 0xF]; + hexa[i * 3 + 1] = h[what[i] & 0xF]; + hexa[i * 3 + 2] = ':'; + } + hexa[hlen - 1] = '\0'; + + return hexa; +} + +/** + * @deprecated Please use ssh_print_hash() instead + */ +void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len) { + char *hexa = ssh_get_hexa(what, len); + + if (hexa == NULL) { + return; + } + fprintf(stderr, "%s: %s\n", descr, hexa); + + free(hexa); +} + +/** + * @brief Log the content of a buffer in hexadecimal format, similar to the + * output of 'hexdump -C' command. + * + * The first logged line is the given description followed by the length. + * Then the content of the buffer is logged 16 bytes per line in the following + * format: + * + * (offset) (first 8 bytes) (last 8 bytes) (the 16 bytes as ASCII char values) + * + * The output for a 16 bytes array containing values from 0x00 to 0x0f would be: + * + * "Example (16 bytes):" + * " 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 '.'. + * + * @param[in] descr A description for the content to be logged + * @param[in] what The buffer to be logged + * @param[in] len The length of the buffer given in what + * + * @note If a too long description is provided (which would result in a first + * line longer than 80 bytes), the function will fail. + */ +void ssh_log_hexdump(const char *descr, const unsigned char *what, size_t len) +{ + size_t i; + char ascii[17]; + const unsigned char *pc = NULL; + size_t count = 0; + ssize_t printed = 0; + + /* The required buffer size is calculated from: + * + * 2 bytes for spaces at the beginning + * 8 bytes for the offset + * 2 bytes for spaces + * 24 bytes to print the first 8 bytes + spaces + * 1 byte for an extra space + * 24 bytes to print next 8 bytes + spaces + * 2 bytes for extra spaces + * 16 bytes for the content as ASCII characters at the end + * 1 byte for the ending '\0' + * + * Resulting in 80 bytes. + * + * Except for the first line (description + size), all lines have fixed + * length. If a too long description is used, the function will fail. + * */ + char buffer[80]; + + /* Print description */ + if (descr != NULL) { + printed = snprintf(buffer, sizeof(buffer), "%s ", descr); + if (printed < 0) { + goto error; + } + count += printed; + } else { + printed = snprintf(buffer, sizeof(buffer), "(NULL description) "); + if (printed < 0) { + goto error; + } + count += printed; + } + + if (len == 0) { + printed = snprintf(buffer + count, sizeof(buffer) - count, + "(zero length):"); + if (printed < 0) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "%s", buffer); + return; + } else { + printed = snprintf(buffer + count, sizeof(buffer) - count, + "(%zu bytes):", len); + if (printed < 0) { + goto error; + } + count += printed; + } + + if (what == NULL) { + printed = snprintf(buffer + count, sizeof(buffer) - count, + "(NULL)"); + if (printed < 0) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "%s", buffer); + return; + } + + SSH_LOG(SSH_LOG_DEBUG, "%s", buffer); + + /* Reset state */ + count = 0; + pc = what; + + for (i = 0; i < len; i++) { + /* Add one space after printing 8 bytes */ + if ((i % 8) == 0) { + if (i != 0) { + printed = snprintf(buffer + count, sizeof(buffer) - count, " "); + if (printed < 0) { + goto error; + } + count += printed; + } + } + + /* Log previous line and reset state for new line */ + if ((i % 16) == 0) { + if (i != 0) { + printed = snprintf(buffer + count, sizeof(buffer) - count, + " %s", ascii); + if (printed < 0) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "%s", buffer); + count = 0; + } + + /* Start a new line with the offset */ + printed = snprintf(buffer, sizeof(buffer), + " %08zx ", i); + if (printed < 0) { + goto error; + } + count += printed; + } + + /* Print the current byte hexadecimal representation */ + printed = snprintf(buffer + count, sizeof(buffer) - count, + " %02x", pc[i]); + if (printed < 0) { + goto error; + } + count += printed; + + /* If printable, store the ASCII character */ + if (isprint(pc[i])) { + ascii[i % 16] = pc[i]; + } else { + ascii[i % 16] = '.'; + } + ascii[(i % 16) + 1] = '\0'; + } + + /* Add padding if not exactly 16 characters */ + while ((i % 16) != 0) { + /* Add one space after printing 8 bytes */ + if ((i % 8) == 0) { + if (i != 0) { + printed = snprintf(buffer + count, sizeof(buffer) - count, " "); + if (printed < 0) { + goto error; + } + count += printed; + } + } + + printed = snprintf(buffer + count, sizeof(buffer) - count, " "); + if (printed < 0) { + goto error; + } + count += printed; + i++; + } + + /* Print the last printable part */ + printed = snprintf(buffer + count, sizeof(buffer) - count, + " %s", ascii); + if (printed < 0) { + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, "%s", buffer); + + return; + +error: + SSH_LOG(SSH_LOG_WARN, "Could not print to buffer"); + return; +} + +/** + * @brief Check if libssh is the required version or get the version + * string. + * + * @param[in] req_version The version required. + * + * @return If the version of libssh is newer than the version + * required it will return a version string. + * NULL if the version is older. + * + * Example: + * + * @code + * if (ssh_version(SSH_VERSION_INT(0,2,1)) == NULL) { + * fprintf(stderr, "libssh version is too old!\n"); + * exit(1); + * } + * + * if (debug) { + * printf("libssh %s\n", ssh_version(0)); + * } + * @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; + } + + 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; +} + +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_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; +} + +/** + * @brief Get the number of elements in the list + * + * @param[in] list The list to count. + * + * @return The number of elements in the list. + */ +size_t ssh_list_count(const struct ssh_list *list) +{ + struct ssh_iterator *it = NULL; + int count = 0; + + for (it = ssh_list_get_iterator(list); it != NULL ; it = it->next) { + count++; + } + + 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; +} + +int ssh_list_append(struct ssh_list *list,const void *data){ + struct ssh_iterator *iterator = NULL; + + if (list == NULL) { + return SSH_ERROR; + } + + iterator = ssh_iterator_new(data); + if (iterator == NULL) { + return SSH_ERROR; + } + + if(!list->end){ + /* list is empty */ + list->root=list->end=iterator; + } else { + /* put it on end of list */ + list->end->next=iterator; + list->end=iterator; + } + return SSH_OK; +} + +int ssh_list_prepend(struct ssh_list *list, const void *data){ + struct ssh_iterator *it = NULL; + + if (list == NULL) { + return SSH_ERROR; + } + + it = ssh_iterator_new(data); + if (it == NULL) { + return SSH_ERROR; + } + + if (list->end == NULL) { + /* list is empty */ + list->root = list->end = it; + } else { + /* set as new root */ + it->next = list->root; + list->root = it; + } + + return SSH_OK; +} + +void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator){ + struct ssh_iterator *ptr,*prev; + + if (list == NULL) { + return; + } + + prev=NULL; + ptr=list->root; + while(ptr && ptr != iterator){ + prev=ptr; + ptr=ptr->next; + } + if(!ptr){ + /* we did not find the element */ + return; + } + /* unlink it */ + if(prev) + prev->next=ptr->next; + /* if iterator was the head */ + if(list->root == iterator) + list->root=iterator->next; + /* if iterator was the tail */ + if(list->end == iterator) + list->end = prev; + SAFE_FREE(iterator); +} + +/** + * @internal + * + * @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. + * + * @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){ + struct ssh_iterator *iterator = NULL; + const void *data = NULL; + + if (list == NULL) { + return NULL; + } + + iterator = list->root; + if (iterator == NULL) { + return NULL; + } + data=iterator->data; + list->root=iterator->next; + if(list->end==iterator) + list->end=NULL; + SAFE_FREE(iterator); + return data; +} + +/** + * @brief Parse directory component. + * + * 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. + * + * @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 NULL or an empty string, + * "." is returned. + */ +char *ssh_dirname (const char *path) { + char *new = NULL; + size_t len; + + if (path == NULL || *path == '\0') { + return strdup("."); + } + + len = strlen(path); + + /* Remove trailing slashes */ + while(len > 0 && path[len - 1] == '/') --len; + + /* We have only slashes */ + if (len == 0) { + return strdup("/"); + } + + /* goto next slash */ + while(len > 0 && path[len - 1] != '/') --len; + + if (len == 0) { + return strdup("."); + } else if (len == 1) { + return strdup("/"); + } + + /* Remove slashes again */ + while(len > 0 && path[len - 1] == '/') --len; + + new = malloc(len + 1); + if (new == NULL) { + return NULL; + } + + strncpy(new, path, len); + new[len] = '\0'; + + return new; +} + +/** + * @brief basename - parse filename component. + * + * basename breaks a null-terminated pathname string into a filename component. + * ssh_basename() returns the component following the final '/'. Trailing '/' + * characters are not counted as part of the pathname. + * + * @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 + * the string "/". If path is NULL or an empty string, + * "." is returned. + */ +char *ssh_basename (const char *path) { + char *new = NULL; + const char *s; + size_t len; + + if (path == NULL || *path == '\0') { + return strdup("."); + } + + len = strlen(path); + /* Remove trailing slashes */ + while(len > 0 && path[len - 1] == '/') --len; + + /* We have only slashes */ + if (len == 0) { + return strdup("/"); + } + + while(len > 0 && path[len - 1] != '/') --len; + + if (len > 0) { + s = path + len; + len = strlen(s); + + while(len > 0 && s[len - 1] == '/') --len; + } else { + return strdup(path); + } + + new = malloc(len + 1); + if (new == NULL) { + return NULL; + } + + strncpy(new, s, len); + new[len] = '\0'; + + return new; +} + +/** + * @brief Attempts to create a directory with the given pathname. + * + * This is the portable version of mkdir, mode is ignored on Windows systems. + * + * @param[in] pathname The path name to create the directory. + * + * @param[in] mode The permissions to use. + * + * @return 0 on success, < 0 on error with errno set. + */ +int ssh_mkdir(const char *pathname, mode_t mode) +{ + int r; +#ifdef _WIN32 + r = _mkdir(pathname); +#else + r = mkdir(pathname, mode); +#endif + + return r; +} + +/** + * @brief Attempts to create a directory with the given pathname. The missing + * directories in the given pathname are created recursively. + * + * @param[in] pathname The path name to create the directory. + * + * @param[in] mode The permissions to use. + * + * @return 0 on success, < 0 on error with errno set. + * + * @note mode is ignored on Windows systems. + */ +int ssh_mkdirs(const char *pathname, mode_t mode) +{ + int rc = 0; + char *parent = NULL; + + if (pathname == NULL || + pathname[0] == '\0' || + !strcmp(pathname, "/") || + !strcmp(pathname, ".")) + { + errno = EINVAL; + return -1; + } + + errno = 0; + +#ifdef _WIN32 + rc = _mkdir(pathname); +#else + rc = mkdir(pathname, mode); +#endif + + if (rc < 0) { + /* If a directory was missing, try to create the parent */ + if (errno == ENOENT) { + parent = ssh_dirname(pathname); + if (parent == NULL) { + errno = ENOMEM; + return -1; + } + + rc = ssh_mkdirs(parent, mode); + if (rc < 0) { + /* We could not create the parent */ + SAFE_FREE(parent); + return -1; + } + + SAFE_FREE(parent); + + /* Try again */ + errno = 0; +#ifdef _WIN32 + rc = _mkdir(pathname); +#else + rc = mkdir(pathname, mode); +#endif + } + } + + return rc; +} + +/** + * @brief Expand a directory starting with a tilde '~' + * + * @param[in] d The directory to expand. + * + * @return The expanded directory, NULL on error. + */ +char *ssh_path_expand_tilde(const char *d) { + char *h = NULL, *r; + const char *p; + size_t ld; + size_t lh = 0; + + if (d[0] != '~') { + return strdup(d); + } + d++; + + /* handle ~user/path */ + p = strchr(d, '/'); + if (p != NULL && p > d) { +#ifdef _WIN32 + return strdup(d); +#else + struct passwd *pw; + size_t s = p - d; + char u[128]; + + if (s >= sizeof(u)) { + return NULL; + } + memcpy(u, d, s); + u[s] = '\0'; + pw = getpwnam(u); + if (pw == NULL) { + return NULL; + } + ld = strlen(p); + h = strdup(pw->pw_dir); +#endif + } else { + ld = strlen(d); + p = (char *) d; + h = ssh_get_user_home_dir(); + } + if (h == NULL) { + return NULL; + } + lh = strlen(h); + + r = malloc(ld + lh + 1); + if (r == NULL) { + SAFE_FREE(h); + return NULL; + } + + if (lh > 0) { + memcpy(r, h, lh); + } + SAFE_FREE(h); + memcpy(r + lh, p, ld + 1); + + return r; +} + +/** @internal + * @brief expands a string in function of session options + * @param[in] s Format string to expand. Known parameters: + * %d SSH configuration directory (~/.ssh) + * %h target host name + * %u local username + * %l local hostname + * %r remote username + * %p remote port + * @returns Expanded string. + */ +char *ssh_path_expand_escape(ssh_session session, const char *s) { + char host[NI_MAXHOST]; + char buf[MAX_BUF_SIZE]; + char *r, *x = NULL; + const char *p; + size_t i, l; + + r = ssh_path_expand_tilde(s); + if (r == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + if (strlen(r) > MAX_BUF_SIZE) { + ssh_set_error(session, SSH_FATAL, "string to expand too long"); + free(r); + return NULL; + } + + p = r; + buf[0] = '\0'; + + for (i = 0; *p != '\0'; p++) { + if (*p != '%') { + escape: + buf[i] = *p; + i++; + if (i >= MAX_BUF_SIZE) { + free(r); + return NULL; + } + buf[i] = '\0'; + continue; + } + + p++; + if (*p == '\0') { + break; + } + + switch (*p) { + case '%': + goto escape; + case 'd': + x = strdup(session->opts.sshdir); + break; + case 'u': + x = ssh_get_local_username(); + break; + case 'l': + if (gethostname(host, sizeof(host) == 0)) { + x = strdup(host); + } + break; + case 'h': + x = strdup(session->opts.host); + break; + case 'r': + x = strdup(session->opts.username); + 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); + } + break; + default: + ssh_set_error(session, SSH_FATAL, + "Wrong escape sequence detected"); + free(r); + return NULL; + } + + if (x == NULL) { + ssh_set_error_oom(session); + free(r); + return NULL; + } + + i += strlen(x); + if (i >= MAX_BUF_SIZE) { + ssh_set_error(session, SSH_FATAL, + "String too long"); + free(x); + free(r); + return NULL; + } + l = strlen(buf); + strncpy(buf + l, x, sizeof(buf) - l - 1); + buf[i] = '\0'; + SAFE_FREE(x); + } + + free(r); + return strdup(buf); +#undef MAX_BUF_SIZE +} + +/** + * @internal + * + * @brief Analyze the SSH banner to extract version information. + * + * @param session The session to analyze the banner from. + * @param server 0 means we are a client, 1 a server. + * + * @return 0 on success, < 0 on error. + * + * @see ssh_get_issue_banner() + */ +int ssh_analyze_banner(ssh_session session, int server) +{ + const char *banner; + const char *openssh; + + if (server) { + banner = session->clientbanner; + } else { + banner = session->serverbanner; + } + + if (banner == NULL) { + ssh_set_error(session, SSH_FATAL, "Invalid banner"); + return -1; + } + + /* + * Typical banners e.g. are: + * + * SSH-1.5-openSSH_5.4 + * SSH-1.99-openSSH_3.0 + * + * SSH-2.0-something + * 012345678901234567890 + */ + if (strlen(banner) < 6 || + strncmp(banner, "SSH-", 4) != 0) { + ssh_set_error(session, SSH_FATAL, "Protocol mismatch: %s", banner); + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Analyzing banner: %s", banner); + + switch (banner[4]) { + case '2': + break; + case '1': + if (strlen(banner) > 6) { + if (banner[6] == '9') { + break; + } + } + FALL_THROUGH; + default: + ssh_set_error(session, SSH_FATAL, "Protocol mismatch: %s", banner); + return -1; + } + + /* Make a best-effort to extract OpenSSH version numbers. */ + openssh = strstr(banner, "OpenSSH"); + if (openssh != NULL) { + char *tmp = NULL; + unsigned long int major = 0UL; + unsigned long int minor = 0UL; + + /* + * The banner is typical: + * OpenSSH_5.4 + * 012345678901234567890 + */ + if (strlen(openssh) > 9) { + 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 */ + goto done; + } + + minor = strtoul(openssh + 10, &tmp, 10); + if ((tmp == (openssh + 10)) || + ((errno == ERANGE) && (major == ULONG_MAX)) || + ((errno != 0) && (major == 0)) || + (minor > 100)) { + /* invalid minor */ + goto done; + } + + session->openssh = SSH_VERSION_INT(((int) major), ((int) minor), 0); + + SSH_LOG(SSH_LOG_PROTOCOL, + "We are talking to an OpenSSH client version: %lu.%lu (%x)", + major, minor, session->openssh); + } + } + +done: + return 0; +} + +/* try the Monotonic clock if possible for perfs reasons */ +#ifdef _POSIX_MONOTONIC_CLOCK +#define CLOCK CLOCK_MONOTONIC +#else +#define CLOCK CLOCK_REALTIME +#endif + +/** + * @internal + * @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){ +#ifdef HAVE_CLOCK_GETTIME + struct timespec tp; + clock_gettime(CLOCK, &tp); + ts->useconds = tp.tv_nsec / 1000; +#else + struct timeval tp; + gettimeofday(&tp, NULL); + ts->useconds = tp.tv_usec; +#endif + ts->seconds = tp.tv_sec; +} + +#undef CLOCK + +/** + * @internal + * @brief gets the time difference between two timestamps in ms + * @param[in] old older value + * @param[in] new newer value + * @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; +} + +/** + * @internal + * @brief turn seconds and microseconds pair (as provided by user-set options) + * into millisecond value + * @param[in] sec number of seconds + * @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; + 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; +} + +/** + * @internal + * @brief Checks if a timeout is elapsed, in function of a previous + * timestamp and an assigned timeout + * @param[in] ts pointer to an existing timestamp + * @param[in] timeout timeout in milliseconds. Negative values mean infinite + * timeout + * @returns 1 if timeout is elapsed + * 0 otherwise + */ +int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout) { + struct ssh_timestamp now; + + switch(timeout) { + case -2: /* + * -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 " + "be fixed. please set a breakpoint on misc.c:%d and " + "fix the caller\n", __LINE__); + return 0; + case -1: /* -1 means infinite timeout */ + return 0; + case 0: /* 0 means no timeout */ + return 1; + default: + break; + } + + ssh_timestamp_init(&now); + + return (ssh_timestamp_difference(ts,&now) >= timeout); +} + +/** + * @brief updates a timeout value so it reflects the remaining time + * @param[in] ts pointer to an existing timestamp + * @param[in] timeout timeout in milliseconds. Negative values mean infinite + * timeout + * @returns remaining time in milliseconds, 0 if elapsed, -1 if never. + */ +int ssh_timeout_update(struct ssh_timestamp *ts, int timeout){ + struct ssh_timestamp now; + int ms, ret; + if (timeout <= 0) { + return timeout; + } + ssh_timestamp_init(&now); + ms = ssh_timestamp_difference(ts,&now); + if(ms < 0) + ms = 0; + ret = timeout - ms; + return ret >= 0 ? ret: 0; +} + + +int ssh_match_group(const char *group, const char *object) +{ + const char *a; + const char *z; + + z = group; + do { + a = strchr(z, ','); + if (a == NULL) { + if (strcmp(z, object) == 0) { + return 1; + } + return 0; + } else { + if (strncmp(z, object, a - z) == 0) { + return 1; + } + } + z = a + 1; + } while(1); + + /* not reached */ + return 0; +} + +#if !defined(HAVE_EXPLICIT_BZERO) +void explicit_bzero(void *s, size_t n) +{ +#if defined(HAVE_MEMSET_S) + memset_s(s, n, '\0', n); +#elif defined(HAVE_SECURE_ZERO_MEMORY) + SecureZeroMemory(s, n); +#else + memset(s, '\0', n); +#if defined(HAVE_GCC_VOLATILE_MEMORY_PROTECTION) + /* See http://llvm.org/bugs/show_bug.cgi?id=15495 */ + __asm__ volatile("" : : "g"(s) : "memory"); +#endif /* HAVE_GCC_VOLATILE_MEMORY_PROTECTION */ +#endif +} +#endif /* !HAVE_EXPLICIT_BZERO */ + +#if !defined(HAVE_STRNDUP) +char *strndup(const char *s, size_t n) +{ + char *x = NULL; + + if (n + 1 < n) { + return NULL; + } + + x = malloc(n + 1); + if (x == NULL) { + return NULL; + } + + memcpy(x, s, n); + x[n] = '\0'; + + return x; +} +#endif /* ! HAVE_STRNDUP */ + +/* Increment 64b integer in network byte order */ +void +uint64_inc(unsigned char *counter) +{ + int i; + + for (i = 7; i >= 0; i--) { + counter[i]++; + if (counter[i]) + return; + } +} + +/** + * @internal + * + * @brief Quote file name to be used on shell. + * + * Try to put the given file name between single quotes. There are special + * cases: + * + * - When the '\'' char is found in the file name, it is double quoted + * - example: + * input: a'b + * output: 'a'"'"'b' + * - When the '!' char is found in the file name, it is replaced by an unquoted + * verbatim char "\!" + * - example: + * input: a!b + * output 'a'\!'b' + * + * @param[in] file_name File name string to be quoted before used on shell + * @param[out] buf Buffer to receive the final quoted file name. Must + * have room for the final quoted string. The maximum + * output length would be (3 * strlen(file_name) + 1) + * since in the worst case each character would be + * replaced by 3 characters, plus the terminating '\0'. + * @param[in] buf_len The size of the provided output buffer + * + * @returns SSH_ERROR on error; length of the resulting string not counting the + * string terminator '\0' + * */ +int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len) +{ + const char *src = NULL; + char *dst = NULL; + size_t required_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"); + 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"); + 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"); + return SSH_ERROR; + } + + src = file_name; + dst = buf; + + while ((*src != '\0')) { + switch (*src) { + + /* The '\'' char is double quoted */ + + case '\'': + switch (state) { + case NO_QUOTE: + /* Start a new double quoted string. The '\'' char will be + * copied to the beginning of it at the end of the loop. */ + *dst++ = '"'; + break; + case SINGLE_QUOTE: + /* Close the current single quoted string and start a new double + * quoted string. The '\'' char will be copied to the beginning + * of it at the end of the loop. */ + *dst++ = '\''; + *dst++ = '"'; + break; + case DOUBLE_QUOTE: + /* If already in the double quoted string, keep copying the + * sequence of chars. */ + break; + default: + /* Should never be reached */ + goto error; + } + + /* When the '\'' char is found, the resulting state will be + * DOUBLE_QUOTE in any case*/ + state = DOUBLE_QUOTE; + break; + + /* The '!' char is replaced by unquoted "\!" */ + + case '!': + switch (state) { + case NO_QUOTE: + /* The '!' char is interpreted in some shells (e.g. CSH) even + * when is quoted with single quotes. Replace it with unquoted + * "\!" which is correctly interpreted as the '!' character. */ + *dst++ = '\\'; + break; + case SINGLE_QUOTE: + /* Close the current quoted string and replace '!' for unquoted + * "\!" */ + *dst++ = '\''; + *dst++ = '\\'; + break; + case DOUBLE_QUOTE: + /* Close current quoted string and replace "!" for unquoted + * "\!" */ + *dst++ = '"'; + *dst++ = '\\'; + break; + default: + /* Should never be reached */ + goto error; + } + + /* When the '!' char is found, the resulting state will be NO_QUOTE + * in any case*/ + state = NO_QUOTE; + break; + + /* Ordinary chars are single quoted */ + + default: + switch (state) { + case NO_QUOTE: + /* Start a new single quoted string */ + *dst++ = '\''; + break; + case SINGLE_QUOTE: + /* If already in the single quoted string, keep copying the + * sequence of chars. */ + break; + case DOUBLE_QUOTE: + /* Close current double quoted string and start a new single + * quoted string. */ + *dst++ = '"'; + *dst++ = '\''; + break; + default: + /* Should never be reached */ + goto error; + } + + /* When an ordinary char is found, the resulting state will be + * SINGLE_QUOTE in any case*/ + state = SINGLE_QUOTE; + break; + } + + /* Copy the current char to output */ + *dst++ = *src++; + } + + /* Close the quoted string when necessary */ + + switch (state) { + case NO_QUOTE: + /* No open string */ + break; + case SINGLE_QUOTE: + /* Close current single quoted string */ + *dst++ = '\''; + break; + case DOUBLE_QUOTE: + /* Close current double quoted string */ + *dst++ = '"'; + break; + default: + /* Should never be reached */ + goto error; + } + + /* Put the string terminator */ + *dst = '\0'; + + return dst - buf; + +error: + return SSH_ERROR; +} + +/** + * @internal + * + * @brief Given a string, encode existing newlines as the string "\\n" + * + * @param[in] string Input string + * @param[out] buf Output buffer. This buffer must be at least (2 * + * strlen(string)) + 1 long. In the worst case, + * each character can be encoded as 2 characters plus the + * terminating '\0'. + * @param[in] buf_len Size of the provided output buffer + * + * @returns SSH_ERROR on error; length of the resulting string not counting the + * terminating '\0' otherwise + */ +int ssh_newline_vis(const char *string, char *buf, size_t buf_len) +{ + const char *in = NULL; + char *out = NULL; + + if (string == NULL || buf == NULL || buf_len == 0) { + return SSH_ERROR; + } + + if ((2 * strlen(string) + 1) > buf_len) { + SSH_LOG(SSH_LOG_WARNING, "Buffer too small"); + return SSH_ERROR; + } + + out = buf; + for (in = string; *in != '\0'; in++) { + if (*in == '\n') { + *out++ = '\\'; + *out++ = 'n'; + } else { + *out++ = *in; + } + } + *out = '\0'; + + return out - buf; +} + +/** @} */ diff --git a/src/options.c b/src/options.c new file mode 100644 index 0000000..b5f951a --- /dev/null +++ b/src/options.c @@ -0,0 +1,2130 @@ +/* + * options.c - handle pre-connection options + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * Copyright (c) 2009-2013 by Andreas Schneider + * + * 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 +#include +#include +#ifndef _WIN32 +#include +#else +#include +#endif +#include +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/options.h" +#ifdef WITH_SERVER +#include "libssh/server.h" +#include "libssh/bind.h" +#include "libssh/bind_config.h" +#endif + +/** + * @addtogroup libssh_session + * @{ + */ + +/** + * @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. + * + * @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. + * + * @returns 0 on sucess, -1 on error with errno set. + * + * @see ssh_session_connect() + */ +int ssh_options_copy(ssh_session src, ssh_session *dest) +{ + ssh_session new; + struct ssh_iterator *it = NULL; + char *id = NULL; + int i; + + if (src == NULL || dest == NULL) { + return -1; + } + + new = ssh_new(); + if (new == NULL) { + return -1; + } + + if (src->opts.username != NULL) { + new->opts.username = strdup(src->opts.username); + if (new->opts.username == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.host != NULL) { + new->opts.host = strdup(src->opts.host); + if (new->opts.host == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.bindaddr != NULL) { + new->opts.bindaddr = strdup(src->opts.bindaddr); + if (new->opts.bindaddr == NULL) { + ssh_free(new); + return -1; + } + } + + /* Remove the default identities */ + for (id = ssh_list_pop_head(char *, new->opts.identity); + id != NULL; + id = ssh_list_pop_head(char *, new->opts.identity)) { + SAFE_FREE(id); + } + /* Copy the new identities from the source list */ + if (src->opts.identity != NULL) { + it = ssh_list_get_iterator(src->opts.identity); + while (it) { + int rc; + + id = strdup((char *) it->data); + if (id == NULL) { + ssh_free(new); + return -1; + } + + rc = ssh_list_append(new->opts.identity, id); + if (rc < 0) { + free(id); + ssh_free(new); + return -1; + } + it = it->next; + } + } + + if (src->opts.sshdir != NULL) { + new->opts.sshdir = strdup(src->opts.sshdir); + if (new->opts.sshdir == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.knownhosts != NULL) { + new->opts.knownhosts = strdup(src->opts.knownhosts); + if (new->opts.knownhosts == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.global_knownhosts != NULL) { + new->opts.global_knownhosts = strdup(src->opts.global_knownhosts); + if (new->opts.global_knownhosts == NULL) { + ssh_free(new); + return -1; + } + } + + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (src->opts.wanted_methods[i] != NULL) { + new->opts.wanted_methods[i] = strdup(src->opts.wanted_methods[i]); + if (new->opts.wanted_methods[i] == NULL) { + ssh_free(new); + return -1; + } + } + } + + if (src->opts.ProxyCommand != NULL) { + new->opts.ProxyCommand = strdup(src->opts.ProxyCommand); + if (new->opts.ProxyCommand == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.pubkey_accepted_types != NULL) { + new->opts.pubkey_accepted_types = strdup(src->opts.pubkey_accepted_types); + if (new->opts.pubkey_accepted_types == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.gss_server_identity != NULL) { + new->opts.gss_server_identity = strdup(src->opts.gss_server_identity); + if (new->opts.gss_server_identity == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.gss_client_identity != NULL) { + new->opts.gss_client_identity = strdup(src->opts.gss_client_identity); + if (new->opts.gss_client_identity == NULL) { + ssh_free(new); + return -1; + } + } + + memcpy(new->opts.options_seen, src->opts.options_seen, + sizeof(new->opts.options_seen)); + + new->opts.fd = src->opts.fd; + new->opts.port = src->opts.port; + new->opts.timeout = src->opts.timeout; + new->opts.timeout_usec = src->opts.timeout_usec; + new->opts.compressionlevel = src->opts.compressionlevel; + new->opts.StrictHostKeyChecking = src->opts.StrictHostKeyChecking; + new->opts.gss_delegate_creds = src->opts.gss_delegate_creds; + new->opts.flags = src->opts.flags; + new->opts.nodelay = src->opts.nodelay; + new->opts.config_processed = src->opts.config_processed; + new->common.log_verbosity = src->common.log_verbosity; + new->common.callbacks = src->common.callbacks; + + *dest = new; + + return 0; +} + +int ssh_options_set_algo(ssh_session session, + enum ssh_kex_types_e algo, + const char *list) +{ + 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(session, SSH_REQUEST_DENIED, + "Setting method: no allowed algorithm for method \"%s\" (%s)", + ssh_kex_get_description(algo), list); + return -1; + } + + SAFE_FREE(session->opts.wanted_methods[algo]); + session->opts.wanted_methods[algo] = p; + + return 0; +} + +/** + * @brief This function can set all possible ssh options. + * + * @param session An allocated SSH session structure. + * + * @param type The option type to set. This could be one of the + * following: + * + * - SSH_OPTIONS_HOST: + * The hostname or ip address to connect to (const char *). + * + * - SSH_OPTIONS_PORT: + * The port to connect to (unsigned int *). + * + * - SSH_OPTIONS_PORT_STR: + * The port to connect to (const char *). + * + * - SSH_OPTIONS_FD: + * 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. + * + * - SSH_OPTIONS_BINDADDR: + * The address to bind the client to (const char *). + * + * - SSH_OPTIONS_USER: + * The username for authentication (const char *).\n + * \n + * If the value is NULL, the username is set to the + * default username. + * + * - SSH_OPTIONS_SSH_DIR: + * Set the ssh directory (const char *,format string).\n + * \n + * If the value is NULL, the directory is set to the + * default ssh directory.\n + * \n + * The ssh directory is used for files like known_hosts + * and identity (private and public key). It may include + * "%s" which will be replaced by the user home + * directory. + * + * - SSH_OPTIONS_KNOWNHOSTS: + * Set the known hosts file name (const char *,format string).\n + * \n + * If the value is NULL, the directory is set to the + * default known hosts file, normally + * ~/.ssh/known_hosts.\n + * \n + * The known hosts file is used to certify remote hosts + * are genuine. It may include "%d" which will be + * replaced by the user home directory. + * + * - SSH_OPTIONS_GLOBAL_KNOWNHOSTS: + * Set the global known hosts file name (const char *,format string).\n + * \n + * If the value is NULL, the directory is set to the + * default global known hosts file, normally + * /etc/ssh/ssh_known_hosts.\n + * \n + * The known hosts file is used to certify remote hosts + * are genuine. + * + * - SSH_OPTIONS_ADD_IDENTITY (or SSH_OPTIONS_IDENTITY): + * 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 + * \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_TIMEOUT: + * Set a timeout for the connection in seconds (long). + * + * - SSH_OPTIONS_TIMEOUT_USEC: + * Set a timeout for the connection in micro seconds + * (long). + * + * - SSH_OPTIONS_SSH1: + * Deprecated + * + * - SSH_OPTIONS_SSH2: + * Unused + * + * - SSH_OPTIONS_LOG_VERBOSITY: + * Set the session logging verbosity (int).\n + * \n + * The verbosity of the messages. Every log smaller or + * equal to verbosity will be shown. + * - 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_FUNCTIONS: Every function path + * + * - SSH_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_OPTIONS_LOG_VERBOSITY above (const + * char *). + * + * - SSH_OPTIONS_CIPHERS_C_S: + * Set the symmetric cipher client to server (const char *, + * comma-separated list). + * + * - SSH_OPTIONS_CIPHERS_S_C: + * Set the symmetric cipher server to client (const char *, + * comma-separated list). + * + * - 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" + * + * - SSH_OPTIONS_HMAC_C_S: + * Set the Message Authentication Code algorithm client to server + * (const char *, comma-separated list). + * + * - SSH_OPTIONS_HMAC_S_C: + * Set the Message Authentication Code algorithm server to client + * (const char *, comma-separated list). + * + * - SSH_OPTIONS_HOSTKEYS: + * Set the preferred server host key types (const char *, + * comma-separated list). ex: + * "ssh-rsa,ssh-dss,ecdh-sha2-nistp256" + * + * - 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_OPTIONS_COMPRESSION_C_S: + * Set the compression to use for client to server + * communication (const char *, "yes", "no" or a specific + * algorithm name if needed ("zlib","zlib@openssh.com","none"). + * + * - SSH_OPTIONS_COMPRESSION_S_C: + * Set the compression to use for server to client + * communication (const char *, "yes", "no" or a specific + * algorithm name if needed ("zlib","zlib@openssh.com","none"). + * + * - SSH_OPTIONS_COMPRESSION: + * Set the compression to use for both directions + * communication (const char *, "yes", "no" or a specific + * algorithm name if needed ("zlib","zlib@openssh.com","none"). + * + * - SSH_OPTIONS_COMPRESSION_LEVEL: + * Set the compression level to use for zlib functions. (int, + * value from 1 to 9, 9 being the most efficient but slower). + * + * - SSH_OPTIONS_STRICTHOSTKEYCHECK: + * Set the parameter StrictHostKeyChecking to avoid + * asking about a fingerprint (int, 0 = false). + * + * - SSH_OPTIONS_PROXYCOMMAND: + * Set the command to be executed in order to connect to + * server (const char *). + * + * - SSH_OPTIONS_GSSAPI_SERVER_IDENTITY + * Set it to specify the GSSAPI server identity that libssh + * should expect when connecting to the server (const char *). + * + * - SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY + * Set it to specify the GSSAPI client identity that libssh + * should expect when connecting to the server (const char *). + * + * - SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS + * Set it to specify that GSSAPI should delegate credentials + * to the server (int, 0 = false). + * + * - SSH_OPTIONS_PASSWORD_AUTH + * Set it if password authentication should be used + * in ssh_userauth_auto_pubkey(). (int, 0=false). + * Currently without effect (ssh_userauth_auto_pubkey doesn't use + * password authentication). + * + * - SSH_OPTIONS_PUBKEY_AUTH + * Set it if pubkey authentication should be used + * in ssh_userauth_auto_pubkey(). (int, 0=false). + * + * - SSH_OPTIONS_KBDINT_AUTH + * Set it if keyboard-interactive authentication should be used + * in ssh_userauth_auto_pubkey(). (int, 0=false). + * Currently without effect (ssh_userauth_auto_pubkey doesn't use + * keyboard-interactive authentication). + * + * - SSH_OPTIONS_GSSAPI_AUTH + * Set it if gssapi authentication should be used + * in ssh_userauth_auto_pubkey(). (int, 0=false). + * Currently without effect (ssh_userauth_auto_pubkey doesn't use + * gssapi authentication). + * + * - SSH_OPTIONS_NODELAY + * Set it to disable Nagle's Algorithm (TCP_NODELAY) on the + * session socket. (int, 0=false) + * + * - SSH_OPTIONS_PROCESS_CONFIG + * Set it to false to disable automatic processing of per-user + * and system-wide OpenSSH configuration files. LibSSH + * automatically uses these configuration files unless + * you provide it with this option or with different file (bool). + * + * - SSH_OPTIONS_REKEY_DATA + * Set the data limit that can be transferred with a single + * key in bytes. RFC 4253 Section 9 recommends 1GB of data, while + * RFC 4344 provides more specific restrictions, that are applied + * automatically. When specified, the lower value will be used. + * (uint64_t, 0=default) + * + * - SSH_OPTIONS_REKEY_TIME + * Set the time limit for a session before intializing a rekey + * in seconds. RFC 4253 Section 9 recommends one hour. + * (uint32_t, 0=off) + * + * @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. + * + * @return 0 on success, < 0 on error. + */ +int ssh_options_set(ssh_session session, enum ssh_options_e type, + const void *value) { + const char *v; + char *p, *q; + long int i; + unsigned int u; + int rc; + + if (session == NULL) { + return -1; + } + + switch (type) { + case SSH_OPTIONS_HOST: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + q = strdup(value); + if (q == NULL) { + ssh_set_error_oom(session); + 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; + } + + 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; + } + } + break; + case SSH_OPTIONS_PORT: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + if (*x <= 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.port = *x & 0xffffU; + } + break; + case SSH_OPTIONS_PORT_STR: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + q = strdup(v); + if (q == NULL) { + ssh_set_error_oom(session); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SAFE_FREE(q); + } + SAFE_FREE(q); + if (i <= 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.port = i & 0xffffU; + } + break; + case SSH_OPTIONS_FD: + if (value == NULL) { + session->opts.fd = SSH_INVALID_SOCKET; + ssh_set_error_invalid(session); + return -1; + } else { + socket_t *x = (socket_t *) value; + if (*x < 0) { + session->opts.fd = SSH_INVALID_SOCKET; + ssh_set_error_invalid(session); + return -1; + } + + session->opts.fd = *x & 0xffff; + } + break; + case SSH_OPTIONS_BINDADDR: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } + + q = strdup(v); + if (q == NULL) { + return -1; + } + SAFE_FREE(session->opts.bindaddr); + session->opts.bindaddr = q; + break; + case SSH_OPTIONS_USER: + v = value; + SAFE_FREE(session->opts.username); + if (v == NULL) { + q = ssh_get_local_username(); + if (q == NULL) { + ssh_set_error_oom(session); + return -1; + } + session->opts.username = q; + } else if (v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { /* username provided */ + session->opts.username = strdup(value); + if (session->opts.username == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_SSH_DIR: + v = value; + SAFE_FREE(session->opts.sshdir); + if (v == NULL) { + session->opts.sshdir = ssh_path_expand_tilde("~/.ssh"); + if (session->opts.sshdir == NULL) { + return -1; + } + } else if (v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + session->opts.sshdir = ssh_path_expand_tilde(v); + if (session->opts.sshdir == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_IDENTITY: + case SSH_OPTIONS_ADD_IDENTITY: + 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_prepend(session->opts.identity, q); + if (rc < 0) { + free(q); + return -1; + } + break; + case SSH_OPTIONS_KNOWNHOSTS: + v = value; + SAFE_FREE(session->opts.knownhosts); + 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.knownhosts = strdup(v); + if (session->opts.knownhosts == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_GLOBAL_KNOWNHOSTS: + v = value; + SAFE_FREE(session->opts.global_knownhosts); + if (v == NULL) { + session->opts.global_knownhosts = + strdup("/etc/ssh/ssh_known_hosts"); + if (session->opts.global_knownhosts == NULL) { + ssh_set_error_oom(session); + return -1; + } + } else if (v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + session->opts.global_knownhosts = strdup(v); + if (session->opts.global_knownhosts == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_TIMEOUT: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + long *x = (long *) value; + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.timeout = *x & 0xffffffffU; + } + break; + case SSH_OPTIONS_TIMEOUT_USEC: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + long *x = (long *) value; + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.timeout_usec = *x & 0xffffffffU; + } + break; + case SSH_OPTIONS_SSH1: + break; + case SSH_OPTIONS_SSH2: + break; + case SSH_OPTIONS_LOG_VERBOSITY: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->common.log_verbosity = *x & 0xffffU; + ssh_set_log_level(*x & 0xffffU); + } + break; + case SSH_OPTIONS_LOG_VERBOSITY_STR: + v = value; + if (v == NULL || v[0] == '\0') { + session->common.log_verbosity = 0; + ssh_set_error_invalid(session); + return -1; + } else { + q = strdup(v); + if (q == NULL) { + ssh_set_error_oom(session); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SAFE_FREE(q); + } + SAFE_FREE(q); + if (i < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->common.log_verbosity = i & 0xffffU; + ssh_set_log_level(i & 0xffffU); + } + break; + case SSH_OPTIONS_CIPHERS_C_S: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + if (ssh_options_set_algo(session, SSH_CRYPT_C_S, v) < 0) + return -1; + } + break; + case SSH_OPTIONS_CIPHERS_S_C: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + if (ssh_options_set_algo(session, SSH_CRYPT_S_C, v) < 0) + return -1; + } + break; + case SSH_OPTIONS_KEY_EXCHANGE: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + if (ssh_options_set_algo(session, SSH_KEX, v) < 0) + return -1; + } + break; + case SSH_OPTIONS_HOSTKEYS: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + if (ssh_options_set_algo(session, SSH_HOSTKEYS, v) < 0) + return -1; + } + break; + case SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: + v = value; + if (v == NULL || v[0] == '\0') { + 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); + return -1; + } + + SAFE_FREE(session->opts.pubkey_accepted_types); + session->opts.pubkey_accepted_types = p; + } + break; + case SSH_OPTIONS_HMAC_C_S: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + if (ssh_options_set_algo(session, SSH_MAC_C_S, v) < 0) + return -1; + } + break; + case SSH_OPTIONS_HMAC_S_C: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + if (ssh_options_set_algo(session, SSH_MAC_S_C, v) < 0) + return -1; + } + break; + case SSH_OPTIONS_COMPRESSION_C_S: + v = value; + if (v == NULL || v[0] == '\0') { + 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; + } + } + break; + case SSH_OPTIONS_COMPRESSION_S_C: + v = value; + if (v == NULL || v[0] == '\0') { + 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; + } + } + break; + case SSH_OPTIONS_COMPRESSION: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } + if(ssh_options_set(session,SSH_OPTIONS_COMPRESSION_C_S, v) < 0) + return -1; + if(ssh_options_set(session,SSH_OPTIONS_COMPRESSION_S_C, v) < 0) + return -1; + break; + case SSH_OPTIONS_COMPRESSION_LEVEL: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *)value; + if (*x < 1 || *x > 9) { + ssh_set_error_invalid(session); + return -1; + } + session->opts.compressionlevel = *x & 0xff; + } + break; + case SSH_OPTIONS_STRICTHOSTKEYCHECK: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + + session->opts.StrictHostKeyChecking = (*x & 0xff) > 0 ? 1 : 0; + } + session->opts.StrictHostKeyChecking = *(int*)value; + break; + case SSH_OPTIONS_PROXYCOMMAND: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + SAFE_FREE(session->opts.ProxyCommand); + /* Setting the command to 'none' disables this option. */ + rc = strcasecmp(v, "none"); + if (rc != 0) { + q = strdup(v); + if (q == NULL) { + return -1; + } + session->opts.ProxyCommand = q; + } + } + break; + case SSH_OPTIONS_GSSAPI_SERVER_IDENTITY: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + SAFE_FREE(session->opts.gss_server_identity); + session->opts.gss_server_identity = strdup(v); + if (session->opts.gss_server_identity == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + SAFE_FREE(session->opts.gss_client_identity); + session->opts.gss_client_identity = strdup(v); + if (session->opts.gss_client_identity == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int x = *(int *)value; + + session->opts.gss_delegate_creds = (x & 0xff); + } + break; + case SSH_OPTIONS_PASSWORD_AUTH: + case SSH_OPTIONS_PUBKEY_AUTH: + case SSH_OPTIONS_KBDINT_AUTH: + case SSH_OPTIONS_GSSAPI_AUTH: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int x = *(int *)value; + u = type == SSH_OPTIONS_PASSWORD_AUTH ? + SSH_OPT_FLAG_PASSWORD_AUTH: + type == SSH_OPTIONS_PUBKEY_AUTH ? + SSH_OPT_FLAG_PUBKEY_AUTH: + type == SSH_OPTIONS_KBDINT_AUTH ? + SSH_OPT_FLAG_KBDINT_AUTH: + SSH_OPT_FLAG_GSSAPI_AUTH; + if (x != 0){ + session->opts.flags |= u; + } else { + session->opts.flags &= ~u; + } + } + break; + case SSH_OPTIONS_NODELAY: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + session->opts.nodelay = (*x & 0xff) > 0 ? 1 : 0; + } + break; + case SSH_OPTIONS_PROCESS_CONFIG: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + bool *x = (bool *)value; + session->opts.config_processed = !(*x); + } + break; + case SSH_OPTIONS_REKEY_DATA: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + uint64_t *x = (uint64_t *)value; + session->opts.rekey_data = *x; + } + break; + case SSH_OPTIONS_REKEY_TIME: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + uint32_t *x = (uint32_t *)value; + if ((*x * 1000) < *x) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The provided value (%" PRIu32 ") for rekey" + " time is too large", *x); + return -1; + } + session->opts.rekey_time = (*x) * 1000; + } + break; + default: + ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); + return -1; + break; + } + + return 0; +} + +/** + * @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 + * or configuration files and one + * + * @param session An allocated SSH session structure. + * + * @param port_target An unsigned integer into which the + * port will be set from the ssh session. + * + * @return 0 on success, < 0 on error. + * + */ +int ssh_options_get_port(ssh_session session, unsigned int* port_target) { + if (session == NULL) { + return -1; + } + + if (session->opts.port == 0) { + *port_target = 22; + return 0; + } + + *port_target = session->opts.port; + + return 0; +} + +/** + * @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. + * 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. + * + * @param session An allocated SSH session structure. + * + * @param type The option type to get. This could be one of the + * following: + * + * - SSH_OPTIONS_HOST: + * The hostname or ip address to connect to (const char *). + * + * - SSH_OPTIONS_USER: + * The username for authentication (const char *).\n + * \n when not explicitly set this will be inferred from the + * ~/.ssh/config file. + * + * - SSH_OPTIONS_IDENTITY: + * Get the first identity file name (const char *).\n + * \n + * By default identity, id_dsa and id_rsa are checked. + * + * - SSH_OPTIONS_PROXYCOMMAND: + * Get the proxycommand necessary to log into the + * remote host. When not explicitly set, it will be read + * from the ~/.ssh/config file. + * + * - SSH_OPTIONS_GLOBAL_KNOWNHOSTS: + * Get the path to the global known_hosts file being used. + * + * - SSH_OPTIONS_KNOWNHOSTS: + * Get the path to the known_hosts file being used. + * + * @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 + * ssh_string_free_char(). + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value) +{ + char* src = NULL; + + if (session == NULL) { + return SSH_ERROR; + } + + if (value == NULL) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + + switch(type) + { + case SSH_OPTIONS_HOST: { + src = session->opts.host; + break; + } + case SSH_OPTIONS_USER: { + src = session->opts.username; + break; + } + case SSH_OPTIONS_IDENTITY: { + struct ssh_iterator *it = ssh_list_get_iterator(session->opts.identity); + if (it == NULL) { + return SSH_ERROR; + } + src = ssh_iterator_value(char *, it); + break; + } + case SSH_OPTIONS_PROXYCOMMAND: { + src = session->opts.ProxyCommand; + break; + } + case SSH_OPTIONS_KNOWNHOSTS: { + src = session->opts.knownhosts; + break; + } + case SSH_OPTIONS_GLOBAL_KNOWNHOSTS: { + src = session->opts.global_knownhosts; + break; + } + default: + ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); + return SSH_ERROR; + break; + } + if (src == NULL) { + return SSH_ERROR; + } + *value = strdup(src); + if (*value == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + return SSH_OK; +} + +/** + * @brief Parse command line arguments. + * + * 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 + * 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. + * + * @param session The session to configure. + * + * @param argcptr The pointer to the argument count. + * + * @param argv The arguments list pointer. + * + * @returns 0 on success, < 0 on error. + * + * @see ssh_session_new() + */ +int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) +{ +#ifdef _MSC_VER + (void)session; + (void)argcptr; + (void)argv; + /* Not supported with a Microsoft compiler */ + return -1; +#else + char *user = NULL; + char *cipher = NULL; + char *identity = NULL; + char *port = NULL; + char **save = NULL; + char **tmp = NULL; + 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; + int saveoptind = optind; /* need to save 'em */ + int saveopterr = opterr; + int opt; + + opterr = 0; /* shut up getopt */ + while((opt = getopt(argc, argv, "c:i:Cl:p:vb:rd12")) != -1) { + switch(opt) { + case 'l': + user = optarg; + break; + case 'p': + port = optarg; + break; + case 'v': + debuglevel++; + break; + case 'r': + usersa++; + break; + case 'd': + usedss++; + break; + case 'c': + cipher = optarg; + break; + case 'i': + identity = optarg; + break; + case 'C': + compress++; + break; + case '2': + break; + case '1': + break; + default: + { + char optv[3] = "- "; + optv[1] = optopt; + tmp = realloc(save, (current + 1) * sizeof(char*)); + if (tmp == NULL) { + SAFE_FREE(save); + ssh_set_error_oom(session); + return -1; + } + save = tmp; + save[current] = strdup(optv); + if (save[current] == NULL) { + SAFE_FREE(save); + ssh_set_error_oom(session); + return -1; + } + current++; + if (optarg) { + save[current++] = argv[optind + 1]; + } + } + } /* switch */ + } /* while */ + opterr = saveopterr; + tmp = realloc(save, (current + (argc - optind)) * sizeof(char*)); + if (tmp == NULL) { + SAFE_FREE(save); + ssh_set_error_oom(session); + return -1; + } + save = tmp; + while (optind < argc) { + tmp = realloc(save, (current + 1) * sizeof(char*)); + if (tmp == NULL) { + SAFE_FREE(save); + ssh_set_error_oom(session); + return -1; + } + save = tmp; + save[current] = argv[optind]; + current++; + 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; + + if(!cont) { + SAFE_FREE(save); + return -1; + } + + /* first recopy the save vector into the original's */ + for (i = 0; i < current; i++) { + /* don't erase argv[0] */ + argv[ i + 1] = save[i]; + } + argv[current + 1] = NULL; + *argcptr = current + 1; + SAFE_FREE(save); + + /* set a new option struct */ + if (compress) { + if (ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes") < 0) { + cont = 0; + } + } + + if (cont && cipher) { + if (ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher) < 0) { + cont = 0; + } + if (cont && ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher) < 0) { + cont = 0; + } + } + + if (cont && user) { + if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) { + cont = 0; + } + } + + if (cont && identity) { + if (ssh_options_set(session, SSH_OPTIONS_IDENTITY, identity) < 0) { + cont = 0; + } + } + + if (port != NULL) { + ssh_options_set(session, SSH_OPTIONS_PORT_STR, port); + } + + if (!cont) { + return SSH_ERROR; + } + + return SSH_OK; +#endif +} + +/** + * @brief Parse the ssh config file. + * + * This should be the last call of all options, it may overwrite options which + * are already set. It requires that the host name is already set with + * ssh_options_set_host(). + * + * @param session SSH session handle + * + * @param filename The options file to use, if NULL the default + * ~/.ssh/config will be used. + * + * @return 0 on success, < 0 on error. + * + * @see ssh_options_set_host() + */ +int ssh_options_parse_config(ssh_session session, const char *filename) { + char *expanded_filename; + int r; + + if (session == NULL) { + return -1; + } + if (session->opts.host == NULL) { + ssh_set_error_invalid(session); + return -1; + } + + if (session->opts.sshdir == NULL) { + r = ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL); + if (r < 0) { + ssh_set_error_oom(session); + return -1; + } + } + + /* set default filename */ + if (filename == NULL) { + expanded_filename = ssh_path_expand_escape(session, "%d/config"); + } else { + expanded_filename = ssh_path_expand_escape(session, filename); + } + if (expanded_filename == NULL) { + return -1; + } + + r = ssh_config_parse_file(session, expanded_filename); + if (r < 0) { + goto out; + } + if (filename == NULL) { + r = ssh_config_parse_file(session, GLOBAL_CLIENT_CONFIG); + } + + /* Do not process the default configuration as part of connection again */ + session->opts.config_processed = true; +out: + free(expanded_filename); + return r; +} + +int ssh_options_apply(ssh_session session) { + struct ssh_iterator *it; + char *tmp; + int rc; + + if (session->opts.sshdir == NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL); + if (rc < 0) { + return -1; + } + } + + if (session->opts.username == NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_USER, NULL); + if (rc < 0) { + return -1; + } + } + + 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; + + 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; + + if (session->opts.ProxyCommand != NULL) { + tmp = ssh_path_expand_escape(session, session->opts.ProxyCommand); + if (tmp == NULL) { + return -1; + } + 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) { + /* 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); + it->data = tmp; + } + + return 0; +} + +/** @} */ + +#ifdef WITH_SERVER +/** + * @addtogroup libssh_server + * @{ + */ +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; + } else { + SAFE_FREE(*key_loc); + *key_loc = strdup(value); + if (*key_loc == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + return 0; +} + +static int ssh_bind_set_algo(ssh_bind sshbind, + enum ssh_kex_types_e algo, + const char *list) +{ + 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; +} + +/** + * @brief Set options for an SSH server bind. + * + * @param sshbind The ssh server bind to configure. + * + * @param type The option type to set. This should be one of the + * following: + * + * - 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 + * at a time, and later calls to this function + * with this option for the same key type will + * override prior calls (const char *). + * + * - SSH_BIND_OPTIONS_BINDADDR: + * Set the IP address to bind (const char *). + * + * - SSH_BIND_OPTIONS_BINDPORT: + * Set the port to bind (unsigned int *). + * + * - SSH_BIND_OPTIONS_BINDPORT_STR: + * Set the port to bind (const char *). + * + * - SSH_BIND_OPTIONS_LOG_VERBOSITY: + * Set the session logging verbosity (int *). + * The logging verbosity should have one of the + * following values, which are listed in order + * of increasing verbosity. Every log message + * with verbosity less than or equal to the + * logging verbosity will be shown. + * - 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_FUNCTIONS: Every function path + * + * - 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_RSAKEY: + * Set the path to the ssh host rsa key, SSHv2 + * only (const char *). + * + * - SSH_BIND_OPTIONS_ECDSAKEY: + * Set the path to the ssh host ecdsa key, + * SSHv2 only (const char *). + * + * - SSH_BIND_OPTIONS_BANNER: + * Set the server banner sent to clients (const char *). + * + * - SSH_BIND_OPTIONS_IMPORT_KEY: + * Set the Private Key for the server directly (ssh_key) + * + * - SSH_BIND_OPTIONS_CIPHERS_C_S: + * 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). + * + * - SSH_BIND_OPTIONS_KEY_EXCHANGE: + * 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: + * Set the Message Authentication Code algorithm client + * to server (const char *, comma-separated list). + * + * - SSH_BIND_OPTIONS_HMAC_S_C: + * Set the Message Authentication Code algorithm server + * to client (const char *, comma-separated list). + * + * - SSH_BIND_OPTIONS_CONFIG_DIR: + * Set the directory (const char *, format string) + * to be used when the "%d" scape is used when providing + * paths of configuration files to + * ssh_bind_options_parse_config(). + * + * - SSH_BIND_OPTIONS_PROCESS_CONFIG + * Set it to false to disable automatic processing of + * system-wide configuration files. LibSSH automatically + * uses these configuration files otherwise. This + * option will only have effect if set before any call + * to ssh_bind_options_parse_config() (bool). + * + * - SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES: + * Set the public key algorithm accepted by the server + * (const char *, comma-separated list). + * + * - SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS: + * Set the list of allowed hostkey signatures algorithms + * to offer to the client, ordered by preference. This + * list is used as a filter when creating the list of + * algorithms to offer to the client: first the list of + * possible algorithms is created from the list of keys + * set and then filtered against this list. + * (const char *, comma-separated list). + * + * @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. + */ +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; + + if (sshbind == NULL) { + return -1; + } + + switch (type) { + 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: + 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_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: +#ifdef HAVE_ECC + bind_key_loc = &sshbind->ecdsa; +#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; + break; + case SSH_KEYTYPE_ED25519: + bind_key_loc = &sshbind->ed25519; + break; + default: + ssh_set_error(sshbind, + SSH_FATAL, + "Unsupported key type %d", key_type); + } + 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; + } + } + 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); + } + 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; + } + break; + case SSH_BIND_OPTIONS_RSAKEY: + rc = ssh_bind_set_key(sshbind, &sshbind->rsakey, value); + if (rc < 0) { + return -1; + } + break; + case SSH_BIND_OPTIONS_ECDSAKEY: + rc = ssh_bind_set_key(sshbind, &sshbind->ecdsakey, value); + if (rc < 0) { + return -1; + } + 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; + } + } + 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) + return -1; + } + break; + case SSH_BIND_OPTIONS_CIPHERS_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_CRYPT_S_C, v) < 0) + return -1; + } + break; + case SSH_BIND_OPTIONS_KEY_EXCHANGE: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + rc = ssh_bind_set_algo(sshbind, SSH_KEX, v); + if (rc < 0) { + return -1; + } + } + break; + case SSH_BIND_OPTIONS_HMAC_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_MAC_C_S, v) < 0) + return -1; + } + break; + 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) + return -1; + } + break; + case SSH_BIND_OPTIONS_CONFIG_DIR: + v = value; + SAFE_FREE(sshbind->config_dir); + if (v == NULL) { + break; + } else if (v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + sshbind->config_dir = ssh_path_expand_tilde(v); + if (sshbind->config_dir == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + break; + case SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES: + v = value; + if (v == NULL || v[0] == '\0') { + 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); + return -1; + } + + SAFE_FREE(sshbind->pubkey_accepted_key_types); + sshbind->pubkey_accepted_key_types = p; + } + break; + case SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + rc = ssh_bind_set_algo(sshbind, SSH_HOSTKEYS, v); + if (rc < 0) { + return -1; + } + } + break; + case SSH_BIND_OPTIONS_PROCESS_CONFIG: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + bool *x = (bool *)value; + sshbind->config_processed = !(*x); + } + break; + default: + ssh_set_error(sshbind, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); + return -1; + break; + } + + return 0; +} + +static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) +{ + char buf[MAX_BUF_SIZE]; + char *r, *x = NULL; + const char *p; + size_t i, l; + + r = ssh_path_expand_tilde(s); + if (r == NULL) { + ssh_set_error_oom(sshbind); + return NULL; + } + + if (strlen(r) > MAX_BUF_SIZE) { + ssh_set_error(sshbind, SSH_FATAL, "string to expand too long"); + free(r); + return NULL; + } + + p = r; + buf[0] = '\0'; + + for (i = 0; *p != '\0'; p++) { + if (*p != '%') { + buf[i] = *p; + i++; + if (i >= MAX_BUF_SIZE) { + free(r); + return NULL; + } + buf[i] = '\0'; + continue; + } + + p++; + if (*p == '\0') { + break; + } + + switch (*p) { + case 'd': + x = strdup(sshbind->config_dir); + break; + default: + ssh_set_error(sshbind, SSH_FATAL, + "Wrong escape sequence detected"); + free(r); + return NULL; + } + + if (x == NULL) { + ssh_set_error_oom(sshbind); + free(r); + return NULL; + } + + i += strlen(x); + if (i >= MAX_BUF_SIZE) { + ssh_set_error(sshbind, SSH_FATAL, + "String too long"); + free(x); + free(r); + return NULL; + } + l = strlen(buf); + strncpy(buf + l, x, sizeof(buf) - l - 1); + buf[i] = '\0'; + SAFE_FREE(x); + } + + free(r); + return strdup(buf); +} + +/** + * @brief Parse a ssh bind options configuration file. + * + * This parses the options file and set them to the ssh_bind handle provided. If + * an option was previously set, it is overridden. If the global configuration + * hasn't been processed yet, it is processed prior to the provided file. + * + * @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 + * processed before). + * + * @return 0 on success, < 0 on error. + */ +int ssh_bind_options_parse_config(ssh_bind sshbind, const char *filename) +{ + int rc = 0; + char *expanded_filename; + + if (sshbind == NULL) { + return -1; + } + + /* If the global default configuration hasn't been processed yet, process it + * before the provided configuration. */ + if (!(sshbind->config_processed)) { + rc = ssh_bind_config_parse_file(sshbind, GLOBAL_BIND_CONFIG); + if (rc != 0) { + return rc; + } + sshbind->config_processed = true; + } + + if (filename != NULL) { + expanded_filename = ssh_bind_options_expand_escape(sshbind, filename); + if (expanded_filename == NULL) { + return -1; + } + + /* Apply the user provided configuration */ + rc = ssh_bind_config_parse_file(sshbind, expanded_filename); + free(expanded_filename); + } + + return rc; +} + +#endif + +/** @} */ diff --git a/src/packet.c b/src/packet.c new file mode 100644 index 0000000..e9ae564 --- /dev/null +++ b/src/packet.c @@ -0,0 +1,1994 @@ +/* + * packet.c - packet building functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 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 +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/socket.h" +#include "libssh/channels.h" +#include "libssh/misc.h" +#include "libssh/session.h" +#include "libssh/messages.h" +#include "libssh/pcap.h" +#include "libssh/kex.h" +#include "libssh/auth.h" +#include "libssh/gssapi.h" +#include "libssh/bytearray.h" +#include "libssh/dh.h" + +static ssh_packet_callback default_packet_handlers[]= { + ssh_packet_disconnect_callback, // SSH2_MSG_DISCONNECT 1 + ssh_packet_ignore_callback, // SSH2_MSG_IGNORE 2 + ssh_packet_unimplemented, // SSH2_MSG_UNIMPLEMENTED 3 + ssh_packet_ignore_callback, // SSH2_MSG_DEBUG 4 +#if WITH_SERVER + ssh_packet_service_request, // SSH2_MSG_SERVICE_REQUEST 5 +#else + NULL, +#endif + ssh_packet_service_accept, // SSH2_MSG_SERVICE_ACCEPT 6 + ssh_packet_ext_info, // SSH2_MSG_EXT_INFO 7 + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, // 8-19 + ssh_packet_kexinit, // SSH2_MSG_KEXINIT 20 + ssh_packet_newkeys, // SSH2_MSG_NEWKEYS 21 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, // 22-29 +#if WITH_SERVER + ssh_packet_kexdh_init, // SSH2_MSG_KEXDH_INIT 30 + // SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30 +#else + NULL, +#endif + NULL, // SSH2_MSG_KEXDH_REPLY 31 + // SSH2_MSG_KEX_DH_GEX_GROUP 31 + NULL, // SSH2_MSG_KEX_DH_GEX_INIT 32 + NULL, // SSH2_MSG_KEX_DH_GEX_REPLY 33 + NULL, // SSH2_MSG_KEX_DH_GEX_REQUEST 34 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, // 35-49 +#if WITH_SERVER + ssh_packet_userauth_request, // SSH2_MSG_USERAUTH_REQUEST 50 +#else + NULL, +#endif + ssh_packet_userauth_failure, // SSH2_MSG_USERAUTH_FAILURE 51 + ssh_packet_userauth_success, // SSH2_MSG_USERAUTH_SUCCESS 52 + ssh_packet_userauth_banner, // SSH2_MSG_USERAUTH_BANNER 53 + NULL,NULL,NULL,NULL,NULL,NULL, // 54-59 + ssh_packet_userauth_pk_ok, // SSH2_MSG_USERAUTH_PK_OK 60 + // SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 + // SSH2_MSG_USERAUTH_INFO_REQUEST 60 + // SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60 + ssh_packet_userauth_info_response, // SSH2_MSG_USERAUTH_INFO_RESPONSE 61 + // SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61 + NULL, // 62 + NULL, // SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63 + NULL, // SSH2_MSG_USERAUTH_GSSAPI_ERROR 64 + NULL, // SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65 +#if defined(WITH_GSSAPI) && defined(WITH_SERVER) + ssh_packet_userauth_gssapi_mic, // SSH2_MSG_USERAUTH_GSSAPI_MIC 66 +#else /* WITH_GSSAPI && WITH_SERVER */ + NULL, +#endif /* WITH_GSSAPI && WITH_SERVER */ + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, // 67-79 +#ifdef WITH_SERVER + ssh_packet_global_request, // SSH2_MSG_GLOBAL_REQUEST 80 +#else /* WITH_SERVER */ + NULL, +#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 + ssh_packet_channel_open, // SSH2_MSG_CHANNEL_OPEN 90 + ssh_packet_channel_open_conf, // SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 + ssh_packet_channel_open_fail, // SSH2_MSG_CHANNEL_OPEN_FAILURE 92 + channel_rcv_change_window, // SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 + channel_rcv_data, // SSH2_MSG_CHANNEL_DATA 94 + channel_rcv_data, // SSH2_MSG_CHANNEL_EXTENDED_DATA 95 + channel_rcv_eof, // SSH2_MSG_CHANNEL_EOF 96 + channel_rcv_close, // SSH2_MSG_CHANNEL_CLOSE 97 + channel_rcv_request, // SSH2_MSG_CHANNEL_REQUEST 98 + ssh_packet_channel_success, // SSH2_MSG_CHANNEL_SUCCESS 99 + ssh_packet_channel_failure, // SSH2_MSG_CHANNEL_FAILURE 100 +}; + +/** @internal + * @brief check if the received packet is allowed for the current session state + * @param session current ssh_session + * @returns SSH_PACKET_ALLOWED if the packet is allowed; SSH_PACKET_DENIED + * if the packet arrived in wrong state; SSH_PACKET_UNKNOWN if the packet type + * is unknown + */ +static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session session) +{ + enum ssh_packet_filter_result_e rc; + +#ifdef DEBUG_PACKET + SSH_LOG(SSH_LOG_PACKET, "Filtering packet type %d", + session->in_packet.type); +#endif + + switch(session->in_packet.type) { + case SSH2_MSG_DISCONNECT: // 1 + /* + * States required: + * - None + * + * Transitions: + * - session->socket->state = SSH_SOCKET_CLOSED + * - session->session_state = SSH_SESSION_STATE_ERROR + * */ + + /* Always allowed */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_IGNORE: // 2 + /* + * States required: + * - None + * + * Transitions: + * - None + * */ + + /* Always allowed */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_UNIMPLEMENTED: // 3 + /* + * States required: + * - None + * + * Transitions: + * - None + * */ + + /* Always allowed */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_DEBUG: // 4 + /* + * States required: + * - None + * + * Transitions: + * - None + * */ + + /* Always allowed */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_SERVICE_REQUEST: // 5 + /* Server only */ + + /* + * States required: + * - session->session_state == SSH_SESSION_STATE_AUTHENTICATING + * or session->session_state == SSH_SESSION_STATE_AUTHENTICATED + * - session->dh_handshake_state == DH_STATE_FINISHED + * + * Transitions: + * - None + * */ + + /* If this is a client, reject the message */ + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if ((session->session_state != SSH_SESSION_STATE_AUTHENTICATING) && + (session->session_state != SSH_SESSION_STATE_AUTHENTICATED)) + { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_SERVICE_ACCEPT: // 6 + /* + * States required: + * - session->session_state == SSH_SESSION_STATE_AUTHENTICATING + * or session->session_state == SSH_SESSION_STATE_AUTHENTICATED + * - session->dh_handshake_state == DH_STATE_FINISHED + * - session->auth.service_state == SSH_AUTH_SERVICE_SENT + * + * Transitions: + * - auth.service_state = SSH_AUTH_SERVICE_ACCEPTED + * */ + + if ((session->session_state != SSH_SESSION_STATE_AUTHENTICATING) && + (session->session_state != SSH_SESSION_STATE_AUTHENTICATED)) + { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + /* TODO check if only auth service can be requested */ + if (session->auth.service_state != SSH_AUTH_SERVICE_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_EXT_INFO: // 7 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * or session->session_state == SSH_SESSION_STATE_AUTHENTICATED + * (re-exchange) + * - dh_handshake_state == DH_STATE_FINISHED + * + * Transitions: + * - None + * */ + + if ((session->session_state != SSH_SESSION_STATE_AUTHENTICATING) && + (session->session_state != SSH_SESSION_STATE_AUTHENTICATED)) + { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEXINIT: // 20 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * or session_state == SSH_SESSION_STATE_INITIAL_KEX + * - dh_handshake_state == DH_STATE_INIT + * or dh_handshake_state == DH_STATE_INIT_SENT (re-exchange) + * or dh_handshake_state == DH_STATE_FINISHED (re-exchange) + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_INIT + * - session->session_state = SSH_SESSION_STATE_KEXINIT_RECEIVED + * + * On server: + * - session->session_state = SSH_SESSION_STATE_DH + * */ + + if ((session->session_state != SSH_SESSION_STATE_AUTHENTICATED) && + (session->session_state != SSH_SESSION_STATE_INITIAL_KEX)) + { + rc = SSH_PACKET_DENIED; + break; + } + + if ((session->dh_handshake_state != DH_STATE_INIT) && + (session->dh_handshake_state != DH_STATE_INIT_SENT) && + (session->dh_handshake_state != DH_STATE_FINISHED)) + { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_NEWKEYS: // 21 + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_NEWKEYS_SENT + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_FINISHED + * - session->session_state = SSH_SESSION_STATE_AUTHENTICATING + * if session->flags & SSH_SESSION_FLAG_AUTHENTICATED + * - session->session_state = SSH_SESSION_STATE_AUTHENTICATED + * */ + + /* If DH has not been started, reject message */ + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + /* Only allowed if dh_handshake_state is in NEWKEYS_SENT state */ + if (session->dh_handshake_state != DH_STATE_NEWKEYS_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEXDH_INIT: // 30 + // SSH2_MSG_KEX_ECDH_INIT: // 30 + // SSH2_MSG_ECMQV_INIT: // 30 + // SSH2_MSG_KEX_DH_GEX_REQUEST_OLD: // 30 + + /* Server only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_INIT + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_INIT_SENT + * then calls dh_handshake_server which triggers: + * - session->dh_handhsake_state = DH_STATE_NEWKEYS_SENT + * */ + + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + /* Only allowed if dh_handshake_state is in initial state */ + if (session->dh_handshake_state != DH_STATE_INIT) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEXDH_REPLY: // 31 + // SSH2_MSG_KEX_ECDH_REPLY: // 31 + // SSH2_MSG_ECMQV_REPLY: // 31 + // SSH2_MSG_KEX_DH_GEX_GROUP: // 31 + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_INIT_SENT + * or dh_handshake_state == DH_STATE_REQUEST_SENT (dh-gex) + * + * Transitions: + * - session->dh_handhsake_state = DH_STATE_NEWKEYS_SENT + * */ + + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_INIT_SENT && + session->dh_handshake_state != DH_STATE_REQUEST_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEX_DH_GEX_INIT: // 32 + /* TODO Not filtered */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEX_DH_GEX_REPLY: // 33 + /* TODO Not filtered */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEX_DH_GEX_REQUEST: // 34 + /* TODO Not filtered */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_REQUEST: // 50 + /* Server only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - dh_hanshake_state == DH_STATE_FINISHED + * + * Transitions: + * - if authentication was successful: + * - session_state = SSH_SESSION_STATE_AUTHENTICATED + * */ + + /* If this is a client, reject the message */ + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_FAILURE: // 51 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - dh_hanshake_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 + * or session->auth.state == SSH_AUTH_STATE_PASSWORD_AUTH_SENT + * or session->auth.state == SSH_AUTH_STATE_GSSAPI_MIC_SENT + * + * Transitions: + * - if unpacking failed: + * - session->auth.state = SSH_AUTH_ERROR + * - if failure was partial: + * - session->auth.state = SSH_AUTH_PARTIAL + * - else: + * - session->auth.state = SSH_AUTH_STATE_FAILED + * */ + + /* If this is a server, reject the message */ + if (session->server) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_SUCCESS: // 52 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - dh_hanshake_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 + * or session->auth.state == SSH_AUTH_STATE_GSSAPI_MIC_SENT + * or session->auth.state == SSH_AUTH_STATE_AUTH_NONE_SENT + * + * Transitions: + * - session->auth.state = SSH_AUTH_STATE_SUCCESS + * - session->session_state = SSH_SESSION_STATE_AUTHENTICATED + * - session->flags |= SSH_SESSION_FLAG_AUTHENTICATED + * - sessions->auth.current_method = SSH_AUTH_METHOD_UNKNOWN + * */ + + /* If this is a server, reject the message */ + if (session->server) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + if ((session->auth.state != SSH_AUTH_STATE_KBDINT_SENT) && + (session->auth.state != SSH_AUTH_STATE_PUBKEY_AUTH_SENT) && + (session->auth.state != SSH_AUTH_STATE_PASSWORD_AUTH_SENT) && + (session->auth.state != SSH_AUTH_STATE_GSSAPI_MIC_SENT) && + (session->auth.state != SSH_AUTH_STATE_AUTH_NONE_SENT)) + { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_BANNER: // 53 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_PK_OK: // 60 + // SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // 60 + // SSH2_MSG_USERAUTH_INFO_REQUEST: // 60 + // SSH2_MSG_USERAUTH_GSSAPI_RESPONSE: // 60 + + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - session->auth.state == SSH_AUTH_STATE_KBDINT_SENT + * or + * session->auth.state == SSH_AUTH_STATE_GSSAPI_REQUEST_SENT + * or + * session->auth.state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT + * + * Transitions: + * Depending on the current state, the message is treated + * differently: + * - session->auth.state == SSH_AUTH_STATE_KBDINT_SENT + * - session->auth.state = SSH_AUTH_STATE_INFO + * - session->auth.state == SSH_AUTH_STATE_GSSAPI_REQUEST_SENT + * - session->auth.state = SSH_AUTH_STATE_GSSAPI_TOKEN + * - session->auth.state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT + * - session->auth.state = SSH_AUTH_STATE_PK_OK + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + if ((session->auth.state != SSH_AUTH_STATE_KBDINT_SENT) && + (session->auth.state != SSH_AUTH_STATE_PUBKEY_OFFER_SENT) && + (session->auth.state != SSH_AUTH_STATE_GSSAPI_REQUEST_SENT)) + { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_INFO_RESPONSE: // 61 + // SSH2_MSG_USERAUTH_GSSAPI_TOKEN: // 61 + + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - session_state->auth.state == SSH_SESSION_STATE_GSSAPI_TOKEN + * or + * session_state->auth.state == SSH_SESSION_STATE_INFO + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + if ((session->auth.state != SSH_AUTH_STATE_INFO) && + (session->auth.state != SSH_AUTH_STATE_GSSAPI_TOKEN)) + { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE: // 63 + /* TODO Not filtered */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_GSSAPI_ERROR: // 64 + /* TODO Not filtered */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_GSSAPI_ERRTOK: // 65 + /* TODO Not filtered */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_GSSAPI_MIC: // 66 + /* Server only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - session->gssapi->state == SSH_GSSAPI_STATE_RCV_MIC + * + * Transitions: + * Depending on the result of the verification, the states are + * changed: + * - SSH_AUTH_SUCCESS: + * - session->session_state = SSH_SESSION_STATE_AUTHENTICATED + * - session->flags != SSH_SESSION_FLAG_AUTHENTICATED + * - SSH_AUTH_PARTIAL: + * - None + * - any other case: + * - None + * */ + + /* If this is a client, reject the message */ + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_GLOBAL_REQUEST: // 80 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_REQUEST_SUCCESS: // 81 + /* + * 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 + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + 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 + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + 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 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: // 91 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - channel->state = SSH_CHANNEL_STATE_OPEN + * - channel->flags &= ~SSH_CHANNEL_FLAG_NOT_BOUND + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_OPEN_FAILURE: // 92 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - channel->state = SSH_CHANNEL_STATE_OPEN_DENIED + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: // 93 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_DATA: // 94 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_EXTENDED_DATA: // 95 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_EOF: // 96 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_CLOSE: // 97 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - channel->state = SSH_CHANNEL_STATE_CLOSED + * - channel->flags |= SSH_CHANNEL_FLAG_CLOSED_REMOTE + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_REQUEST: // 98 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - Depends on the request + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_SUCCESS: // 99 + /* + * 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 + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_FAILURE: // 100 + /* + * 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 + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + default: + /* Unknown message, do not filter */ + rc = SSH_PACKET_UNKNOWN; + goto end; + } + +end: +#ifdef DEBUG_PACKET + if (rc == SSH_PACKET_DENIED) { + SSH_LOG(SSH_LOG_PACKET, "REJECTED packet type %d: ", + session->in_packet.type); + } + + if (rc == SSH_PACKET_UNKNOWN) { + SSH_LOG(SSH_LOG_PACKET, "UNKNOWN packet type %d", + session->in_packet.type); + } +#endif + + return rc; +} + +/* Returns current_crypto structure from the session. + * During key exchange (or rekey), after one of the sides + * sending NEWKEYS packet, this might return next_crypto for one + * of the directions that is ahead to send already queued packets + */ +struct ssh_crypto_struct * +ssh_packet_get_current_crypto(ssh_session session, + enum ssh_crypto_direction_e direction) +{ + struct ssh_crypto_struct *crypto = NULL; + + if (session == NULL) { + return NULL; + } + + if (session->current_crypto != NULL && + session->current_crypto->used & direction) { + crypto = session->current_crypto; + } else if (session->next_crypto != NULL && + session->next_crypto->used & direction) { + crypto = session->next_crypto; + } else { + return NULL; + } + + switch (direction) { + case SSH_DIRECTION_IN: + if (crypto->in_cipher != NULL) { + return crypto; + } + break; + case SSH_DIRECTION_OUT: + if (crypto->out_cipher != NULL) { + return crypto; + } + break; + case SSH_DIRECTION_BOTH: + if (crypto->in_cipher != NULL && + crypto->out_cipher != NULL) { + return crypto; + } + } + + return NULL; +} + +#define MAX_PACKETS (1UL<<31) + +static bool ssh_packet_need_rekey(ssh_session session, + const uint32_t payloadsize) +{ + bool data_rekey_needed = false; + struct ssh_crypto_struct *crypto = NULL; + struct ssh_cipher_struct *out_cipher = NULL, *in_cipher = NULL; + uint32_t next_blocks; + + /* We can safely rekey only in authenticated state */ + if ((session->flags & SSH_SESSION_FLAG_AUTHENTICATED) == 0) { + return false; + } + + /* Do not rekey if the rekey/key-exchange is in progress */ + if (session->dh_handshake_state != DH_STATE_FINISHED) { + return false; + } + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_BOTH); + if (crypto == NULL) { + return false; + } + + out_cipher = crypto->out_cipher; + in_cipher = crypto->in_cipher; + + /* Make sure we can send at least something for very small limits */ + if ((out_cipher->packets == 0) && (in_cipher->packets == 0)) { + return false; + } + + /* Time based rekeying */ + if (session->opts.rekey_time != 0 && + ssh_timeout_elapsed(&session->last_rekey_time, + session->opts.rekey_time)) { + return true; + } + + /* RFC4344, Section 3.1 Recommends rekeying after 2^31 packets in either + * direction to avoid possible information leakage through the MAC tag + */ + if (out_cipher->packets > MAX_PACKETS || + in_cipher->packets > MAX_PACKETS) { + return true; + } + + /* Data-based rekeying: + * * For outgoing packets we can still delay them + * * Incoming packets need to be processed anyway, but we can + * signalize our intention to rekey + */ + next_blocks = payloadsize / out_cipher->blocksize; + data_rekey_needed = (out_cipher->max_blocks != 0 && + out_cipher->blocks + next_blocks > out_cipher->max_blocks) || + (in_cipher->max_blocks != 0 && + in_cipher->blocks + next_blocks > in_cipher->max_blocks); + + SSH_LOG(SSH_LOG_PACKET, + "packet: [data_rekey_needed=%d, out_blocks=%" PRIu64 ", in_blocks=%" PRIu64, + data_rekey_needed, + out_cipher->blocks + next_blocks, + in_cipher->blocks + next_blocks); + + return data_rekey_needed; +} + +/* in nonblocking mode, socket_read will read as much as it can, and return */ +/* SSH_OK if it has read at least len bytes, otherwise, SSH_AGAIN. */ +/* in blocking mode, it will read at least len bytes and will block until it's ok. */ + +/** @internal + * @handles a data received event. It then calls the handlers for the different packet types + * or and exception handler callback. + * @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. + */ +int 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; + int rc; + uint8_t *cleartext_packet = NULL; + uint8_t *packet_second_block = NULL; + uint8_t *mac = NULL; + size_t packet_remaining; + uint32_t packet_len, compsize, payloadsize; + uint8_t padding; + size_t processed = 0; /* number of byte processed from the callback */ + enum ssh_packet_filter_result_e filter_result; + struct ssh_crypto_struct *crypto = NULL; + bool etm = false; + uint32_t etm_packet_offset = 0; + bool ok; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto != NULL) { + current_macsize = hmac_digest_len(crypto->in_hmac); + blocksize = crypto->in_cipher->blocksize; + lenfield_blocksize = crypto->in_cipher->lenfield_blocksize; + etm = crypto->in_hmac_etm; + } + + if (etm) { + /* In EtM mode packet size is unencrypted. This means + * we need to use this offset and set the block size + * that is part of the encrypted part to 0. + */ + etm_packet_offset = sizeof(uint32_t); + lenfield_blocksize = 0; + } else if (lenfield_blocksize == 0) { + lenfield_blocksize = blocksize; + } + if (data == NULL) { + goto error; + } + + if (session->session_state == SSH_SESSION_STATE_ERROR) { + goto error; + } +#ifdef DEBUG_PACKET + SSH_LOG(SSH_LOG_PACKET, + "rcv packet cb (len=%zu, state=%s)", + receivedlen, + session->packet_state == PACKET_STATE_INIT ? + "INIT" : + session->packet_state == PACKET_STATE_SIZEREAD ? + "SIZE_READ" : + session->packet_state == PACKET_STATE_PROCESSING ? + "PROCESSING" : "unknown"); +#endif + switch(session->packet_state) { + case PACKET_STATE_INIT: + if (receivedlen < lenfield_blocksize + etm_packet_offset) { + /* + * We didn't receive enough data to read either at least one + * block size or the unencrypted length in EtM mode. + */ +#ifdef DEBUG_PACKET + SSH_LOG(SSH_LOG_PACKET, + "Waiting for more data (%zu < %u)", + receivedlen, + lenfield_blocksize); +#endif + return 0; + } + + session->in_packet = (struct packet_struct) { + .type = 0, + }; + + if (session->in_buffer) { + rc = ssh_buffer_reinit(session->in_buffer); + if (rc < 0) { + goto error; + } + } else { + session->in_buffer = ssh_buffer_new(); + if (session->in_buffer == NULL) { + goto error; + } + } + + if (!etm) { + 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); + to_be_read = packet_len - lenfield_blocksize + sizeof(uint32_t); + } else { + /* Length is unencrypted in case of Encrypt-then-MAC */ + packet_len = PULL_BE_U32(data, 0); + to_be_read = packet_len - etm_packet_offset; + } + + processed += lenfield_blocksize + etm_packet_offset; + if (packet_len > MAX_PACKET_LEN) { + ssh_set_error(session, + SSH_FATAL, + "read_packet(): Packet len too high(%u %.4x)", + packet_len, packet_len); + goto error; + } + if (to_be_read < 0) { + /* remote sshd sends invalid sizes? */ + ssh_set_error(session, + SSH_FATAL, + "Given numbers of bytes left to be read < 0 (%d)!", + to_be_read); + goto error; + } + + session->in_packet.len = packet_len; + session->packet_state = PACKET_STATE_SIZEREAD; + FALL_THROUGH; + case PACKET_STATE_SIZEREAD: + packet_len = session->in_packet.len; + 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) { + /* 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]", + packet_len, + (int)receivedlen, + to_be_read); + return 0; + } + + packet_second_block = (uint8_t*)data + lenfield_blocksize + etm_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); + cleartext_packet = ssh_buffer_allocate(session->in_buffer, + packet_remaining); + if (cleartext_packet == NULL) { + goto error; + } + + if (packet_second_block != NULL) { + if (crypto != NULL) { + mac = packet_second_block + packet_remaining; + + if (etm) { + rc = ssh_packet_hmac_verify(session, + data, + processed, + mac, + crypto->in_hmac); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "HMAC error"); + goto error; + } + } + /* + * 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)); + if (rc < 0) { + ssh_set_error(session, + SSH_FATAL, + "Decryption error"); + goto error; + } + } + + if (!etm) { + rc = ssh_packet_hmac_verify(session, + ssh_buffer_get(session->in_buffer), + ssh_buffer_get_len(session->in_buffer), + mac, + crypto->in_hmac); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "HMAC error"); + goto error; + } + } + processed += current_macsize; + } else { + memcpy(cleartext_packet, + packet_second_block, + packet_remaining); + } + } + +#ifdef WITH_PCAP + if (session->pcap_ctx != NULL) { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_IN, + ssh_buffer_get(session->in_buffer), + ssh_buffer_get_len(session->in_buffer), + ssh_buffer_get_len(session->in_buffer)); + } +#endif + + if (!etm) { + /* skip the size field which has been processed before */ + ssh_buffer_pass_bytes(session->in_buffer, sizeof(uint32_t)); + } + + rc = ssh_buffer_get_u8(session->in_buffer, &padding); + if (rc == 0) { + ssh_set_error(session, + SSH_FATAL, + "Packet too short to read padding"); + goto error; + } + + if (padding > ssh_buffer_get_len(session->in_buffer)) { + ssh_set_error(session, + SSH_FATAL, + "Invalid padding: %d (%d left)", + padding, + ssh_buffer_get_len(session->in_buffer)); + goto error; + } + ssh_buffer_pass_bytes_end(session->in_buffer, padding); + 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 (rc < 0) { + goto error; + } + } +#endif /* WITH_ZLIB */ + payloadsize = ssh_buffer_get_len(session->in_buffer); + session->recv_seq++; + if (crypto != NULL) { + struct ssh_cipher_struct *cipher = NULL; + + cipher = crypto->in_cipher; + cipher->packets++; + cipher->blocks += payloadsize / cipher->blocksize; + } + if (session->raw_counter != NULL) { + session->raw_counter->in_bytes += payloadsize; + session->raw_counter->in_packets++; + } + + /* + * We don't want to rewrite a new packet while still executing the + * packet callbacks + */ + 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); + + /* Check if the packet is expected */ + filter_result = ssh_packet_incoming_filter(session); + + switch(filter_result) { + case SSH_PACKET_ALLOWED: + /* Execute callbacks */ + ssh_packet_process(session, session->in_packet.type); + break; + case SSH_PACKET_DENIED: + ssh_set_error(session, + SSH_FATAL, + "Packet filter: rejected packet (type %d)", + session->in_packet.type); + goto error; + case SSH_PACKET_UNKNOWN: + ssh_packet_send_unimplemented(session, session->recv_seq - 1); + break; + } + + session->packet_state = PACKET_STATE_INIT; + if (processed < receivedlen) { + /* Handle a potential packet left in socket buffer */ + SSH_LOG(SSH_LOG_PACKET, + "Processing %" PRIdS " bytes left in socket buffer", + receivedlen-processed); + + ptr = ((uint8_t*)data) + processed; + + rc = ssh_packet_socket_callback(ptr, receivedlen - processed,user); + processed += rc; + } + + ok = ssh_packet_need_rekey(session, 0); + if (ok) { + SSH_LOG(SSH_LOG_PACKET, "Incoming packet triggered rekey"); + rc = ssh_send_rekex(session); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Rekey failed: rc = %d", rc); + return rc; + } + } + + return processed; + case PACKET_STATE_PROCESSING: + SSH_LOG(SSH_LOG_PACKET, "Nested packet processing. Delaying."); + return 0; + } + + ssh_set_error(session, + SSH_FATAL, + "Invalid state into packet_read2(): %d", + session->packet_state); + +error: + session->session_state= SSH_SESSION_STATE_ERROR; + SSH_LOG(SSH_LOG_PACKET,"Packet: processed %" PRIdS " bytes", processed); + return processed; +} + +static void ssh_packet_socket_controlflow_callback(int code, void *userdata) +{ + ssh_session session = userdata; + struct ssh_iterator *it; + ssh_channel channel; + + if (code == SSH_SOCKET_FLOW_WRITEWONTBLOCK) { + SSH_LOG(SSH_LOG_TRACE, "sending channel_write_wontblock callback"); + + /* the out pipe is empty so we can forward this to channels */ + it = ssh_list_get_iterator(session->channels); + while (it != NULL) { + channel = ssh_iterator_value(ssh_channel, it); + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_write_wontblock_function, + session, + channel, + channel->remote_window); + it = it->next; + } + } +} + +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); +} + +/** @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) { + 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){ + struct ssh_iterator *it = NULL; + it = ssh_list_find(session->packet_callbacks, callbacks); + if (it != NULL) { + ssh_list_remove(session->packet_callbacks, it); + } +} + +/** @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); +} + +/** @internal + * @brief dispatch the call of packet handlers callbacks for a received packet + * @param type type of packet + */ +void ssh_packet_process(ssh_session session, uint8_t type) +{ + struct ssh_iterator *i = NULL; + int rc = SSH_PACKET_NOT_USED; + ssh_packet_callbacks cb; + + SSH_LOG(SSH_LOG_PACKET, "Dispatching handler for packet type %d", type); + if (session->packet_callbacks == NULL) { + SSH_LOG(SSH_LOG_RARE, "Packet callback is not initialized !"); + return; + } + + i = ssh_list_get_iterator(session->packet_callbacks); + while (i != NULL) { + cb = ssh_iterator_value(ssh_packet_callbacks, i); + i = i->next; + + if (!cb) { + continue; + } + + if (cb->start > type) { + continue; + } + + if (cb->start + cb->n_callbacks <= type) { + continue; + } + + if (cb->callbacks[type - cb->start] == NULL) { + continue; + } + + rc = cb->callbacks[type - cb->start](session, type, session->in_buffer, + cb->user); + if (rc == SSH_PACKET_USED) { + break; + } + } + + if (rc == SSH_PACKET_NOT_USED) { + SSH_LOG(SSH_LOG_RARE, "Couldn't do anything with packet type %d", type); + rc = ssh_packet_send_unimplemented(session, session->recv_seq - 1); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_RARE, "Failed to send unimplemented: %s", + ssh_get_error(session)); + } + } +} + +/** @internal + * @brief sends a SSH_MSG_UNIMPLEMENTED answer to an unhandled packet + * @param session the SSH session + * @param seqnum the sequence number of the unknown packet + * @return SSH_ERROR on error, else SSH_OK + */ +int ssh_packet_send_unimplemented(ssh_session session, uint32_t seqnum){ + int rc; + + rc = ssh_buffer_pack(session->out_buffer, + "bd", + SSH2_MSG_UNIMPLEMENTED, + seqnum); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + rc = ssh_packet_send(session); + + return rc; +} + +/** @internal + * @brief handles a SSH_MSG_UNIMPLEMENTED packet + */ +SSH_PACKET_CALLBACK(ssh_packet_unimplemented){ + uint32_t seq; + int rc; + + (void)session; /* unused */ + (void)type; + (void)user; + + rc = ssh_buffer_unpack(packet, "d", &seq); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARNING, + "Could not unpack SSH_MSG_UNIMPLEMENTED packet"); + } + + SSH_LOG(SSH_LOG_RARE, + "Received SSH_MSG_UNIMPLEMENTED (sequence number %d)",seq); + + return SSH_PACKET_USED; +} + +/** @internal + * @parse the "Type" header field of a packet and updates the session + */ +int ssh_packet_parse_type(struct ssh_session_struct *session) +{ + session->in_packet = (struct packet_struct) { + .type = 0, + }; + + if (session->in_buffer == NULL) { + return SSH_ERROR; + } + + if (ssh_buffer_get_u8(session->in_buffer, &session->in_packet.type) == 0) { + ssh_set_error(session, SSH_FATAL, "Packet too short to read type"); + return SSH_ERROR; + } + + session->in_packet.valid = 1; + + return SSH_OK; +} + +/* + * This function places the outgoing packet buffer into an outgoing + * socket buffer + */ +static int ssh_packet_write(ssh_session session) { + int rc = SSH_ERROR; + + rc=ssh_socket_write(session->socket, + ssh_buffer_get(session->out_buffer), + ssh_buffer_get_len(session->out_buffer)); + + return rc; +} + +static int packet_send2(ssh_session session) +{ + unsigned int blocksize = 8; + unsigned int lenfield_blocksize = 0; + enum ssh_hmac_e hmac_type; + uint32_t currentlen = ssh_buffer_get_len(session->out_buffer); + struct ssh_crypto_struct *crypto = NULL; + unsigned char *hmac = NULL; + uint8_t padding_data[32] = { 0 }; + uint8_t padding_size; + uint32_t finallen, payloadsize, compsize; + uint8_t header[5] = {0}; + uint8_t type, *payload; + int rc = SSH_ERROR; + bool etm = false; + int etm_packet_offset = 0; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); + if (crypto) { + blocksize = crypto->out_cipher->blocksize; + lenfield_blocksize = crypto->out_cipher->lenfield_blocksize; + hmac_type = crypto->out_hmac; + etm = crypto->out_hmac_etm; + } else { + hmac_type = session->next_crypto->out_hmac; + } + + payload = (uint8_t *)ssh_buffer_get(session->out_buffer); + type = payload[0]; /* type is the first byte of the packet now */ + + payloadsize = currentlen; + if (etm) { + etm_packet_offset = sizeof(uint32_t); + lenfield_blocksize = 0; + } + +#ifdef WITH_ZLIB + if (crypto != NULL && crypto->do_compress_out && + ssh_buffer_get_len(session->out_buffer) > 0) { + rc = compress_buffer(session,session->out_buffer); + if (rc < 0) { + goto error; + } + currentlen = ssh_buffer_get_len(session->out_buffer); + } +#endif /* WITH_ZLIB */ + compsize = currentlen; + /* compressed payload + packet len (4) + padding_size len (1) */ + /* totallen - lenfield_blocksize - etm_packet_offset must be equal to 0 (mod blocksize) */ + padding_size = (blocksize - ((blocksize - lenfield_blocksize - etm_packet_offset + currentlen + 5) % blocksize)); + if (padding_size < 4) { + padding_size += blocksize; + } + + if (crypto != NULL) { + int ok; + + ok = ssh_get_random(padding_data, padding_size, 0); + if (!ok) { + ssh_set_error(session, SSH_FATAL, "PRNG error"); + goto error; + } + } + + finallen = currentlen - etm_packet_offset + padding_size + 1; + + PUSH_BE_U32(header, 0, finallen); + PUSH_BE_U8(header, 4, padding_size); + + rc = ssh_buffer_prepend_data(session->out_buffer, + header, + sizeof(header)); + if (rc < 0) { + goto error; + } + + rc = ssh_buffer_add_data(session->out_buffer, padding_data, padding_size); + if (rc < 0) { + goto error; + } + +#ifdef WITH_PCAP + if (session->pcap_ctx != NULL) { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_OUT, + ssh_buffer_get(session->out_buffer), + ssh_buffer_get_len(session->out_buffer), + ssh_buffer_get_len(session->out_buffer)); + } +#endif + + hmac = ssh_packet_encrypt(session, + ssh_buffer_get(session->out_buffer), + ssh_buffer_get_len(session->out_buffer)); + if (hmac != NULL) { + rc = ssh_buffer_add_data(session->out_buffer, + hmac, + hmac_digest_len(hmac_type)); + if (rc < 0) { + goto error; + } + } + + rc = ssh_packet_write(session); + if (rc == SSH_ERROR) { + goto error; + } + session->send_seq++; + if (crypto != NULL) { + struct ssh_cipher_struct *cipher = NULL; + + cipher = crypto->out_cipher; + cipher->packets++; + cipher->blocks += payloadsize / cipher->blocksize; + } + if (session->raw_counter != NULL) { + session->raw_counter->out_bytes += payloadsize; + session->raw_counter->out_packets++; + } + + SSH_LOG(SSH_LOG_PACKET, + "packet: wrote [type=%u, len=%u, padding_size=%hhd, comp=%u, " + "payload=%u]", + type, + finallen, + padding_size, + compsize, + payloadsize); + + rc = ssh_buffer_reinit(session->out_buffer); + if (rc < 0) { + rc = SSH_ERROR; + goto error; + } + + /* We sent the NEWKEYS so any further packet needs to be encrypted + * with the new keys. We can not switch both directions (need to decrypt + * peer NEWKEYS) and we do not want to wait for the peer NEWKEYS + * too, so we will switch only the OUT direction now. + */ + if (type == SSH2_MSG_NEWKEYS) { + rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_OUT); + } +error: + return rc; /* SSH_OK, AGAIN or ERROR */ +} + +static bool +ssh_packet_is_kex(unsigned char type) +{ + return type >= SSH2_MSG_DISCONNECT && + type <= SSH2_MSG_KEX_DH_GEX_REQUEST && + type != SSH2_MSG_SERVICE_REQUEST && + type != SSH2_MSG_SERVICE_ACCEPT && + type != SSH2_MSG_IGNORE && + type != SSH2_MSG_EXT_INFO; +} + +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 + */ + return (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) && + (session->dh_handshake_state != DH_STATE_FINISHED); +} + +int ssh_packet_send(ssh_session session) +{ + uint32_t payloadsize; + uint8_t type, *payload; + bool need_rekey, in_rekey; + int rc; + + payloadsize = ssh_buffer_get_len(session->out_buffer); + if (payloadsize < 1) { + return SSH_ERROR; + } + + payload = (uint8_t *)ssh_buffer_get(session->out_buffer); + type = payload[0]; /* type is the first byte of the packet now */ + need_rekey = ssh_packet_need_rekey(session, payloadsize); + in_rekey = ssh_packet_in_rekey(session); + + /* The rekey is triggered here. After that, only the key exchange + * packets can be sent, until we send our NEWKEYS. + */ + if (need_rekey || (in_rekey && !ssh_packet_is_kex(type))) { + if (need_rekey) { + SSH_LOG(SSH_LOG_PACKET, "Outgoing packet triggered rekey"); + } + /* Queue the current packet -- we will send it after the rekey */ + SSH_LOG(SSH_LOG_PACKET, "Queuing packet type %d", type); + rc = ssh_list_append(session->out_queue, session->out_buffer); + if (rc != SSH_OK) { + return SSH_ERROR; + } + session->out_buffer = ssh_buffer_new(); + if (session->out_buffer == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + if (need_rekey) { + /* Send the KEXINIT packet instead. + * This recursivelly 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. + */ + return ssh_send_rekex(session); + } + return SSH_OK; + } + + /* Send the packet normally */ + rc = packet_send2(session); + + /* We finished the key exchange so we can try to send our queue now */ + if (rc == SSH_OK && type == SSH2_MSG_NEWKEYS) { + struct ssh_iterator *it; + + for (it = ssh_list_get_iterator(session->out_queue); + it != NULL; + it = ssh_list_get_iterator(session->out_queue)) { + struct ssh_buffer_struct *next_buffer = NULL; + + /* Peek only -- do not remove from queue yet */ + next_buffer = (struct ssh_buffer_struct *)it->data; + payloadsize = ssh_buffer_get_len(next_buffer); + if (ssh_packet_need_rekey(session, payloadsize)) { + /* Sigh ... we still can not send this packet. Repeat. */ + SSH_LOG(SSH_LOG_PACKET, "Queued packet triggered rekey"); + return ssh_send_rekex(session); + } + SSH_BUFFER_FREE(session->out_buffer); + session->out_buffer = ssh_list_pop_head(struct ssh_buffer_struct *, + session->out_queue); + payload = (uint8_t *)ssh_buffer_get(session->out_buffer); + type = payload[0]; + SSH_LOG(SSH_LOG_PACKET, "Dequeue packet type %d", type); + rc = packet_send2(session); + if (rc != SSH_OK) { + return rc; + } + } + } + + return rc; +} + +static void +ssh_init_rekey_state(struct ssh_session_struct *session, + struct ssh_cipher_struct *cipher) +{ + /* Reset the counters: should be NOOP */ + cipher->packets = 0; + cipher->blocks = 0; + + /* Default rekey limits for ciphers as specified in RFC4344, Section 3.2 */ + if (cipher->blocksize >= 16) { + /* For larger block size (L bits) use maximum of 2**(L/4) blocks */ + cipher->max_blocks = (uint64_t)1 << (cipher->blocksize*2); + } else { + /* For smaller blocks use limit of 1 GB as recommended in RFC4253 */ + cipher->max_blocks = ((uint64_t)1 << 30) / cipher->blocksize; + } + /* If we have limit provided by user, use the smaller one */ + if (session->opts.rekey_data != 0) { + cipher->max_blocks = MIN(cipher->max_blocks, + session->opts.rekey_data / cipher->blocksize); + } + + SSH_LOG(SSH_LOG_PROTOCOL, + "Set rekey after %" PRIu64 " blocks", + cipher->max_blocks); +} + +/* + * Once we got SSH2_MSG_NEWKEYS we can switch next_crypto and + * current_crypto for our desired direction + */ +int +ssh_packet_set_newkeys(ssh_session session, + enum ssh_crypto_direction_e direction) +{ + int rc; + + SSH_LOG(SSH_LOG_TRACE, + "called, direction =%s%s", + direction & SSH_DIRECTION_IN ? " IN " : "", + direction & SSH_DIRECTION_OUT ? " OUT " : ""); + + if (session->next_crypto == NULL) { + return SSH_ERROR; + } + + 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."); + } + /* Mark the current requested direction unused */ + session->current_crypto->used &= ~direction; + } + + /* Both sides switched: do the actual switch now */ + if (session->next_crypto->used == SSH_DIRECTION_BOTH) { + size_t digest_len; + + if (session->current_crypto != NULL) { + crypto_free(session->current_crypto); + session->current_crypto = NULL; + } + + session->current_crypto = session->next_crypto; + session->current_crypto->used = SSH_DIRECTION_BOTH; + + /* Initialize the next_crypto structure */ + session->next_crypto = crypto_new(); + if (session->next_crypto == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + digest_len = session->current_crypto->digest_len; + session->next_crypto->session_id = malloc(digest_len); + if (session->next_crypto->session_id == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + memcpy(session->next_crypto->session_id, + session->current_crypto->session_id, + digest_len); + + return SSH_OK; + } + + /* Initialize common structures so the next context can be used in + * either direction */ + if (session->client) { + /* The server has this part already done */ + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + /* + * Set the cryptographic functions for the next crypto + * (it is needed for ssh_generate_session_keys for key lengths) + */ + rc = crypt_set_algorithms_client(session); + if (rc < 0) { + return SSH_ERROR; + } + } + + if (ssh_generate_session_keys(session) < 0) { + return SSH_ERROR; + } + + if (session->next_crypto->in_cipher == NULL || + session->next_crypto->out_cipher == NULL) { + return SSH_ERROR; + } + + /* Initialize rekeying states */ + ssh_init_rekey_state(session, + session->next_crypto->out_cipher); + ssh_init_rekey_state(session, + session->next_crypto->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", + session->opts.rekey_time/1000); + } + + /* Initialize the encryption and decryption keys in next_crypto */ + rc = session->next_crypto->in_cipher->set_decrypt_key( + session->next_crypto->in_cipher, + session->next_crypto->decryptkey, + session->next_crypto->decryptIV); + if (rc < 0) { + /* On error, make sure it is not used */ + session->next_crypto->used = 0; + return SSH_ERROR; + } + + rc = session->next_crypto->out_cipher->set_encrypt_key( + session->next_crypto->out_cipher, + session->next_crypto->encryptkey, + session->next_crypto->encryptIV); + if (rc < 0) { + /* On error, make sure it is not used */ + session->next_crypto->used = 0; + return SSH_ERROR; + } + + return SSH_OK; +} diff --git a/src/packet_cb.c b/src/packet_cb.c new file mode 100644 index 0000000..4e69291 --- /dev/null +++ b/src/packet_cb.c @@ -0,0 +1,250 @@ +/* + * packet.c - packet building functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2011 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 +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/dh.h" +#include "libssh/misc.h" +#include "libssh/packet.h" +#include "libssh/pki.h" +#include "libssh/session.h" +#include "libssh/socket.h" +#include "libssh/ssh2.h" +#include "libssh/curve25519.h" + +/** + * @internal + * + * @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; +} + +/** + * @internal + * + * @brief Handle a SSH_IGNORE and SSH_DEBUG packet. + */ +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; +} + +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; + } + + rc = ssh_pki_import_signature_blob(sig_blob, server_key, &sig); + if (rc != SSH_OK) { + 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)) { + 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_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"); + + /* 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; + } + } + 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; +} + +/** + * @internal + * @brief handles a SSH_SERVICE_ACCEPT packet + * + */ +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"); + + return SSH_PACKET_USED; +} + +/** + * @internal + * @brief handles a SSH2_MSG_EXT_INFO packet defined in RFC 8308 + * + */ +SSH_PACKET_CALLBACK(ssh_packet_ext_info) +{ + int rc; + uint32_t nr_extensions = 0; + uint32_t i; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_EXT_INFO"); + + rc = ssh_buffer_get_u32(packet, &nr_extensions); + if (rc == 0) { + SSH_LOG(SSH_LOG_PACKET, "Failed to read number of extensions"); + return SSH_PACKET_USED; + } + + nr_extensions = ntohl(nr_extensions); + if (nr_extensions > 128) { + SSH_LOG(SSH_LOG_PACKET, "Invalid number of extensions"); + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PACKET, "Follows %u extensions", nr_extensions); + + for (i = 0; i < nr_extensions; i++) { + char *name = NULL; + char *value = NULL; + int cmp; + + rc = ssh_buffer_unpack(packet, "ss", &name, &value); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Error reading extension name-value pair"); + return SSH_PACKET_USED; + } + + cmp = strcmp(name, "server-sig-algs"); + if (cmp == 0) { + /* TODO check for NULL bytes */ + SSH_LOG(SSH_LOG_PACKET, "Extension: %s=<%s>", name, value); + if (ssh_match_group(value, "rsa-sha2-512")) { + session->extensions |= SSH_EXT_SIG_RSA_SHA512; + } + if (ssh_match_group(value, "rsa-sha2-256")) { + session->extensions |= SSH_EXT_SIG_RSA_SHA256; + } + } + free(name); + free(value); + } + + return SSH_PACKET_USED; +} diff --git a/src/packet_crypt.c b/src/packet_crypt.c new file mode 100644 index 0000000..95d438c --- /dev/null +++ b/src/packet_crypt.c @@ -0,0 +1,283 @@ +/* + * crypt.c - blowfish-cbc code + * + * This file is part of the SSH Library + * + * Copyright (c) 2003 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 +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#ifdef OPENSSL_CRYPTO +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/wrapper.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/bytearray.h" + +/** @internal + * @brief decrypt the packet length from a raw encrypted packet, and store the first decrypted + * blocksize. + * @returns native byte-ordered decrypted length of the upcoming packet + */ +uint32_t ssh_packet_decrypt_len(ssh_session session, + uint8_t *destination, + uint8_t *source) +{ + struct ssh_crypto_struct *crypto = NULL; + uint32_t decrypted; + int rc; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto != NULL) { + if (crypto->in_cipher->aead_decrypt_length != NULL) { + rc = crypto->in_cipher->aead_decrypt_length( + crypto->in_cipher, source, destination, + crypto->in_cipher->lenfield_blocksize, + session->recv_seq); + } else { + rc = ssh_packet_decrypt( + session, + destination, + source, + 0, + crypto->in_cipher->blocksize); + } + if (rc < 0) { + return 0; + } + } else { + memcpy(destination, source, 8); + } + memcpy(&decrypted,destination,sizeof(decrypted)); + + return ntohl(decrypted); +} + +/** @internal + * @brief decrypts the content of an SSH packet. + * @param[source] source packet, including the encrypted length field + * @param[start] index in the packet that was not decrypted yet. + * @param[encrypted_size] size of the encrypted data to be decrypted after start. + */ +int ssh_packet_decrypt(ssh_session session, + uint8_t *destination, + uint8_t *source, + size_t start, + size_t encrypted_size) +{ + struct ssh_crypto_struct *crypto = NULL; + struct ssh_cipher_struct *cipher = NULL; + + if (encrypted_size <= 0) { + return SSH_ERROR; + } + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto == NULL) { + return SSH_ERROR; + } + cipher = crypto->in_cipher; + + if (encrypted_size % cipher->blocksize != 0) { + ssh_set_error(session, + SSH_FATAL, + "Cryptographic functions must be used on multiple of " + "blocksize (received %" PRIdS ")", + encrypted_size); + return SSH_ERROR; + } + + if (cipher->aead_decrypt != NULL) { + return cipher->aead_decrypt(cipher, + source, + destination, + encrypted_size, + session->recv_seq); + } else { + cipher->decrypt(cipher, source + start, destination, encrypted_size); + } + + return 0; +} + +unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_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; + uint32_t seq, lenfield_blocksize; + enum ssh_hmac_e type; + bool etm; + + assert(len); + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); + if (crypto == NULL) { + return NULL; /* nothing to do here */ + } + + blocksize = crypto->out_cipher->blocksize; + lenfield_blocksize = crypto->out_cipher->lenfield_blocksize; + + type = crypto->out_hmac; + etm = crypto->out_hmac_etm; + + if (etm) { + etm_packet_offset = sizeof(uint32_t); + } + + 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); + return NULL; + } + out = calloc(1, len); + if (out == NULL) { + return NULL; + } + + seq = ntohl(session->send_seq); + cipher = crypto->out_cipher; + + if (cipher->aead_encrypt != NULL) { + cipher->aead_encrypt(cipher, data, out, len, + crypto->hmacbuf, session->send_seq); + memcpy(data, out, len); + } else { + ctx = hmac_init(crypto->encryptMAC, hmac_digest_len(type), type); + if (ctx == NULL) { + SAFE_FREE(out); + return NULL; + } + + if (!etm) { + hmac_update(ctx, (unsigned char *)&seq, sizeof(uint32_t)); + hmac_update(ctx, data, len); + hmac_final(ctx, crypto->hmacbuf, &finallen); + } + + cipher->encrypt(cipher, (uint8_t*)data + etm_packet_offset, out, len - etm_packet_offset); + memcpy((uint8_t*)data + etm_packet_offset, out, len - etm_packet_offset); + + 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); + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("mac: ", data, len); + if (finallen != hmac_digest_len(type)) { + printf("Final len is %d\n", finallen); + } + ssh_log_hexdump("Packet hmac", crypto->hmacbuf, hmac_digest_len(type)); +#endif + } + explicit_bzero(out, len); + SAFE_FREE(out); + + 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 + * + * @brief Verify the hmac of a packet + * + * @param session The session to use. + * @param data The pointer to the data to verify the hmac from. + * @param len The length of the given data. + * @param mac The mac to compare with the hmac. + * + * @return 0 if hmac and mac are equal, < 0 if not or an error + * occurred. + */ +int ssh_packet_hmac_verify(ssh_session session, + const void *data, + size_t len, + 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; + } + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto == NULL) { + return SSH_ERROR; + } + + ctx = hmac_init(crypto->decryptMAC, hmac_digest_len(type), type); + if (ctx == NULL) { + return -1; + } + + seq = htonl(session->recv_seq); + + hmac_update(ctx, (unsigned char *) &seq, sizeof(uint32_t)); + hmac_update(ctx, data, len); + hmac_final(ctx, hmacbuf, &hmaclen); + +#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)); +#endif + if (secure_memcmp(mac, hmacbuf, hmaclen) == 0) { + return 0; + } + + return -1; +} diff --git a/src/pcap.c b/src/pcap.c new file mode 100644 index 0000000..c089854 --- /dev/null +++ b/src/pcap.c @@ -0,0 +1,560 @@ +/* + * 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. + */ + +/* pcap.c */ +#include "config.h" +#ifdef WITH_PCAP + +#include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#endif +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#include +#include + +#include "libssh/libssh.h" +#include "libssh/pcap.h" +#include "libssh/session.h" +#include "libssh/buffer.h" +#include "libssh/socket.h" + +/** + * @internal + * + * @defgroup libssh_pcap The libssh pcap functions + * @ingroup libssh + * + * The pcap file generation + * + * + * @{ + */ + +/* The header of a pcap file is the following. We are not going to make it + * very complicated. + * Just for information. + */ +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 */ + uint32_t sigfigs; /* accuracy of timestamps */ + uint32_t snaplen; /* max length of captured packets, in octets */ + uint32_t network; /* data link type */ +}; + +#define PCAP_MAGIC 0xa1b2c3d4 +#define PCAP_VERSION_MAJOR 2 +#define PCAP_VERSION_MINOR 4 + +#define DLT_RAW 12 /* raw IP */ + +/* TCP flags */ +#define TH_FIN 0x01 +#define TH_SYN 0x02 +#define TH_RST 0x04 +#define TH_PUSH 0x08 +#define TH_ACK 0x10 +#define TH_URG 0x20 + +/* The header of a pcap packet. + * Just for information. + */ +struct pcaprec_hdr_s { + uint32_t ts_sec; /* timestamp seconds */ + uint32_t ts_usec; /* timestamp microseconds */ + uint32_t incl_len; /* number of octets of packet saved in file */ + uint32_t orig_len; /* actual length of packet */ +}; + +/** @private + * @brief a pcap context expresses the state of a pcap dump + * 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 + * the dummy IP and TCP packets + */ + uint32_t ipsource; + uint32_t ipdest; + uint16_t portsource; + uint16_t portdest; + uint32_t outsequence; + uint32_t insequence; +}; + +/** @private + * @brief a pcap file expresses the state of a pcap file which may + * contain several streams. + */ +struct ssh_pcap_file_struct { + FILE *output; + uint16_t ipsequence; +}; + +/** + * @brief create a new ssh_pcap_file object + */ +ssh_pcap_file ssh_pcap_file_new(void) { + struct ssh_pcap_file_struct *pcap; + + pcap = (struct ssh_pcap_file_struct *) malloc(sizeof(struct ssh_pcap_file_struct)); + if (pcap == NULL) { + return NULL; + } + ZERO_STRUCTP(pcap); + + return pcap; +} + +/** @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; +} + +/** @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); + 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)); + if (err < 0) { + goto error; + } + 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))); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_u32(header,htonl(original_len)); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_buffer(header,packet); + if (err < 0) { + goto error; + } + err=ssh_pcap_file_write(pcap,header); +error: + SSH_BUFFER_FREE(header); + return err; +} + +/** + * @brief opens a new pcap file and create 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; + 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)); + if (err < 0) { + goto error; + } + 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)); + if (err < 0) { + goto error; + } + /* 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)); + if (err < 0) { + goto error; + } + /* 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)); + if (err < 0) { + goto error; + } + err=ssh_pcap_file_write(pcap,header); +error: + 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; +} + +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; +} + +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; +} + +/** @internal + * @brief sets the IP and port parameters in the connection + */ +static int ssh_pcap_context_connect(ssh_pcap_context ctx) +{ + ssh_session session=ctx->session; + struct sockaddr_in local = { + .sin_family = AF_UNSPEC, + }; + struct sockaddr_in remote = { + .sin_family = AF_UNSPEC, + }; + socket_t fd; + socklen_t len; + int rc; + + if (session == NULL) { + return SSH_ERROR; + } + + if (session->socket == NULL) { + return SSH_ERROR; + } + + fd = ssh_socket_get_fd(session->socket); + + /* TODO: adapt for windows */ + if (fd < 0) { + return SSH_ERROR; + } + + len = sizeof(local); + rc = getsockname(fd, (struct sockaddr *)&local, &len); + if (rc < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Getting local IP address: %s", + strerror(errno)); + return SSH_ERROR; + } + + len = sizeof(remote); + rc = getpeername(fd, (struct sockaddr *)&remote, &len); + if (rc < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Getting remote IP address: %s", + strerror(errno)); + return SSH_ERROR; + } + + if (local.sin_family != AF_INET) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Only IPv4 supported for pcap logging"); + return SSH_ERROR; + } + + memcpy(&ctx->ipsource, &local.sin_addr, sizeof(ctx->ipsource)); + memcpy(&ctx->ipdest, &remote.sin_addr, sizeof(ctx->ipdest)); + memcpy(&ctx->portsource, &local.sin_port, sizeof(ctx->portsource)); + memcpy(&ctx->portdest, &remote.sin_port, sizeof(ctx->portdest)); + + ctx->connected = 1; + return SSH_OK; +} + +#define IPHDR_LEN 20 +#define TCPHDR_LEN 20 +#define TCPIPHDR_LEN (IPHDR_LEN + TCPHDR_LEN) +/** @internal + * @brief write a SSH packet as a TCP over IP in a pcap file + * @param ctx open pcap context + * @param direction SSH_PCAP_DIRECTION_IN if the packet has been received + * @param direction SSH_PCAP_DIRECTION_OUT if the packet has been emitted + * @param data pointer to the data to write + * @param len data to write in the pcap file. May be smaller than origlen. + * @param origlen number of bytes of complete data. + * @returns SSH_OK write is successful + * @returns SSH_ERROR an error happened. + */ +int ssh_pcap_context_write(ssh_pcap_context ctx, + enum ssh_pcap_direction direction, + void *data, + uint32_t len, + uint32_t origlen) +{ + ssh_buffer ip; + int rc; + + if (ctx == NULL || ctx->file == NULL) { + return SSH_ERROR; + } + if (ctx->connected == 0) { + if (ssh_pcap_context_connect(ctx) == SSH_ERROR) { + return SSH_ERROR; + } + } + ip = ssh_buffer_new(); + if (ip == NULL) { + ssh_set_error_oom(ctx->session); + return SSH_ERROR; + } + + /* build an IP packet */ + rc = ssh_buffer_pack(ip, + "bbwwwbbw", + 4 << 4 | 5, /* V4, 20 bytes */ + 0, /* tos */ + origlen + TCPIPHDR_LEN, /* total len */ + ctx->file->ipsequence, /* IP id number */ + 0, /* fragment offset */ + 64, /* TTL */ + 6, /* protocol TCP=6 */ + 0); /* checksum */ + + ctx->file->ipsequence++; + if (rc != SSH_OK){ + goto error; + } + if (direction == SSH_PCAP_DIR_OUT) { + rc = ssh_buffer_add_u32(ip, ctx->ipsource); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u32(ip, ctx->ipdest); + if (rc < 0) { + goto error; + } + } else { + rc = ssh_buffer_add_u32(ip, ctx->ipdest); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u32(ip, ctx->ipsource); + if (rc < 0) { + goto error; + } + } + /* TCP */ + if (direction == SSH_PCAP_DIR_OUT) { + rc = ssh_buffer_add_u16(ip, ctx->portsource); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u16(ip, ctx->portdest); + if (rc < 0) { + goto error; + } + } else { + rc = ssh_buffer_add_u16(ip, ctx->portdest); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u16(ip, ctx->portsource); + if (rc < 0) { + goto error; + } + } + /* sequence number */ + if (direction == SSH_PCAP_DIR_OUT) { + rc = ssh_buffer_pack(ip, "d", ctx->outsequence); + if (rc != SSH_OK) { + goto error; + } + ctx->outsequence += origlen; + } else { + rc = ssh_buffer_pack(ip, "d", ctx->insequence); + if (rc != SSH_OK) { + goto error; + } + ctx->insequence += origlen; + } + /* ack number */ + if (direction == SSH_PCAP_DIR_OUT) { + rc = ssh_buffer_pack(ip, "d", ctx->insequence); + if (rc != SSH_OK) { + goto error; + } + } else { + rc = ssh_buffer_pack(ip, "d", ctx->outsequence); + if (rc != SSH_OK) { + goto error; + } + } + + rc = ssh_buffer_pack(ip, + "bbwwwP", + 5 << 4, /* header len = 20 = 5 * 32 bits, at offset 4*/ + TH_PUSH | TH_ACK, /* flags */ + 65535, /* window */ + 0, /* checksum */ + 0, /* urgent data ptr */ + (size_t)len, data); /* actual data */ + if (rc != SSH_OK) { + goto error; + } + rc = ssh_pcap_file_write_packet(ctx->file, ip, origlen + TCPIPHDR_LEN); + +error: + SSH_BUFFER_FREE(ip); + return rc; +} + +/** @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 + * 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; +} + + +#else /* WITH_PCAP */ + +/* Simple stub returning errors when no pcap compiled in */ + +#include "libssh/libssh.h" +#include "libssh/priv.h" + +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; +} + +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_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 + +/** @} */ diff --git a/src/pki.c b/src/pki.c new file mode 100644 index 0000000..6dcb120 --- /dev/null +++ b/src/pki.c @@ -0,0 +1,2493 @@ +/* + * pki.c + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * Copyright (c) 2011-2013 Andreas Schneider + * + * 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. + */ + +/** + * @defgroup libssh_pki The SSH Public Key Infrastructure + * @ingroup libssh + * + * Functions for the creation, importation and manipulation of public and + * private keys in the context of the SSH protocol + * + * @{ + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# ifdef HAVE_IO_H +# include +# 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" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/keys.h" +#include "libssh/buffer.h" +#include "libssh/misc.h" +#include "libssh/agent.h" + +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; + } + + start = strstr(privkey, ECDSA_HEADER_BEGIN); + if (start != 0) { + /* We don't know what the curve is at this point, so we don't actually + * know the type. We figure out the actual curve and fix things up in + * pki_private_key_from_base64 */ + return SSH_KEYTYPE_ECDSA_P256; + } + + return SSH_KEYTYPE_UNKNOWN; +} + +/** + * @brief returns the ECDSA key name ("ecdsa-sha2-nistp256" for example) + * + * @param[in] key the ssh_key whose ECDSA name to get + * + * @returns the ECDSA key name ("ecdsa-sha2-nistp256" for example) + * + * @returns "unknown" if the ECDSA key name is not known + */ +const char *ssh_pki_key_ecdsa_name(const ssh_key key) +{ + if (key == NULL) { + return NULL; + } + +#ifdef HAVE_ECC /* FIXME Better ECC check needed */ + return pki_key_ecdsa_nid_to_name(key->ecdsa_nid); +#else + return NULL; +#endif +} + +/** + * @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_dup(const ssh_key key) +{ + if (key == NULL) { + return NULL; + } + + return pki_key_dup(key, 0); +} + +/** + * @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) + 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 + if (key->ed25519_privkey != NULL){ +#ifdef HAVE_OPENSSL_ED25519 + /* 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 + SAFE_FREE(key->ed25519_privkey); + } + SAFE_FREE(key->ed25519_pubkey); + if (key->cert != NULL) { + SSH_BUFFER_FREE(key->cert); + } + key->cert_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; +} + +/** + * @brief deallocate a SSH key + * @param[in] key ssh_key handle to free + */ +void ssh_key_free (ssh_key key){ + if(key){ + ssh_key_clean(key); + SAFE_FREE(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, + * SSH_KEYTYPE_ECDSA_P256, SSH_KEYTYPE_ECDSA_P384, + * SSH_KEYTYPE_ECDSA_P521, SSH_KEYTYPE_ED25519, SSH_KEYTYPE_DSS_CERT01, + * 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){ + if (key == NULL) { + return SSH_KEYTYPE_UNKNOWN; + } + return key->type; +} + +/** + * @brief Convert a signature type to a string. + * + * @param[in] type The algorithm type to convert. + * + * @return A string for the keytype or NULL if unknown. + */ +const char * +ssh_key_signature_to_char(enum ssh_keytypes_e type, + enum ssh_digest_e hash_type) +{ + switch (type) { + case SSH_KEYTYPE_RSA: + switch (hash_type) { + case SSH_DIGEST_SHA256: + return "rsa-sha2-256"; + case SSH_DIGEST_SHA512: + return "rsa-sha2-512"; + case SSH_DIGEST_SHA1: + case SSH_DIGEST_AUTO: + return "ssh-rsa"; + default: + return NULL; + } + break; + case SSH_KEYTYPE_RSA_CERT01: + switch (hash_type) { + case SSH_DIGEST_SHA256: + return "rsa-sha2-256-cert-v01@openssh.com"; + case SSH_DIGEST_SHA512: + return "rsa-sha2-512-cert-v01@openssh.com"; + case SSH_DIGEST_SHA1: + case SSH_DIGEST_AUTO: + return "ssh-rsa-cert-v01@openssh.com"; + default: + return NULL; + } + break; + default: + return ssh_key_type_to_char(type); + } + + /* We should never reach this */ + return NULL; +} + +/** + * @brief Convert a key type to a string. + * + * @param[in] type The type to convert. + * + * @return A string for the keytype or NULL if unknown. + */ +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: + return "ssh-ecdsa"; /* deprecated. invalid value */ + 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_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: + return "ecdsa-sha2-nistp256-cert-v01@openssh.com"; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + return "ecdsa-sha2-nistp384-cert-v01@openssh.com"; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + return "ecdsa-sha2-nistp521-cert-v01@openssh.com"; + case SSH_KEYTYPE_ED25519_CERT01: + return "ssh-ed25519-cert-v01@openssh.com"; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + return NULL; + } + + /* We should never reach this */ + return NULL; +} + +enum ssh_digest_e ssh_key_hash_from_name(const char *name) +{ + if (name == NULL) { + /* TODO we should rather fail */ + return SSH_DIGEST_AUTO; + } + + 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) { + return SSH_DIGEST_SHA512; + } else if (strcmp(name, "ecdsa-sha2-nistp256") == 0) { + return SSH_DIGEST_SHA256; + } else if (strcmp(name, "ecdsa-sha2-nistp384") == 0) { + return SSH_DIGEST_SHA384; + } else if (strcmp(name, "ecdsa-sha2-nistp521") == 0) { + return SSH_DIGEST_SHA512; + } else if (strcmp(name, "ssh-ed25519") == 0) { + return SSH_DIGEST_AUTO; + } + + SSH_LOG(SSH_LOG_WARN, "Unknown signature name %s", name); + + /* TODO we should rather fail */ + return SSH_DIGEST_AUTO; +} + +/** + * @brief Checks the given key against the configured allowed + * public key algorithm types + * + * @param[in] session The SSH session + * @param[in] type The key algorithm to check + * @returns 1 if the key algorithm is allowed, 0 otherwise + */ +int ssh_key_algorithm_allowed(ssh_session session, const char *type) +{ + const char *allowed_list; + + if (session->client) { + allowed_list = session->opts.pubkey_accepted_types; + if (allowed_list == NULL) { + if (ssh_fips_mode()) { + allowed_list = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + allowed_list = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + } + } +#ifdef WITH_SERVER + 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"); + return 0; + } + } +#endif + else { + SSH_LOG(SSH_LOG_WARN, "Session invalid: not set as client nor server"); + return 0; + } + + SSH_LOG(SSH_LOG_DEBUG, "Checking %s with list <%s>", type, allowed_list); + return ssh_match_group(allowed_list, type); +} + +/** + * @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 + * negotiated during key exchange. + * + * @param[in] session SSH Session. + * + * @param[in] type The type to convert. + * + * @return A hash type to be used. + */ +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 */ + if ((session->openssh > 0) && + (session->openssh < SSH_VERSION_INT(7, 2, 0))) + { + SSH_LOG(SSH_LOG_DEBUG, + "We are talking to an old OpenSSH (%x); " + "returning SSH_DIGEST_SHA1", + session->openssh); + + return SSH_DIGEST_SHA1; + } + FALL_THROUGH; + case SSH_KEYTYPE_RSA: + if (ssh_key_algorithm_allowed(session, "rsa-sha2-512") && + (session->extensions & SSH_EXT_SIG_RSA_SHA512)) { + return SSH_DIGEST_SHA512; + } + + if (ssh_key_algorithm_allowed(session, "rsa-sha2-256") && + (session->extensions & SSH_EXT_SIG_RSA_SHA256)) { + return SSH_DIGEST_SHA256; + } + + /* Default algorithm for RSA is SHA1 */ + return SSH_DIGEST_SHA1; + + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P256: + return SSH_DIGEST_SHA256; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + return SSH_DIGEST_SHA384; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + return SSH_DIGEST_SHA512; + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_ED25519: + return SSH_DIGEST_AUTO; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_WARN, "Digest algorithm to be used with key type %u " + "is not defined", type); + } + + /* We should never reach this */ + return SSH_DIGEST_AUTO; +} + +/** + * @brief Gets signature algorithm name to be used with the given + * key type. + * + * @param[in] session SSH session. + * @param[in] type The algorithm type to convert. + * + * @return A string for the keytype or NULL if unknown. + */ +const char * +ssh_key_get_signature_algorithm(ssh_session session, + enum ssh_keytypes_e type) +{ + enum ssh_digest_e hash_type; + + if (type == SSH_KEYTYPE_RSA_CERT01) { + /* If we are talking to an old OpenSSH version which does not support + * rsa-sha2-{256,512}-cert-v01@openssh.com */ + if ((session->openssh > 0) && + (session->openssh < SSH_VERSION_INT(7, 8, 0))) + { + SSH_LOG(SSH_LOG_DEBUG, + "We are talking to an old OpenSSH (%x); " + "using old cert format", + session->openssh); + + return "ssh-rsa-cert-v01@openssh.com"; + } + } + + hash_type = ssh_key_type_to_hash(session, type); + + return ssh_key_signature_to_char(type, hash_type); +} + +/** + * @brief Convert a ssh key algorithm name to a ssh key algorithm type. + * + * @param[in] name The name to convert. + * + * @return The enum ssh key algorithm type. + */ +enum ssh_keytypes_e ssh_key_type_from_signature_name(const char *name) { + if (name == NULL) { + return SSH_KEYTYPE_UNKNOWN; + } + + if ((strcmp(name, "rsa-sha2-256") == 0) || + (strcmp(name, "rsa-sha2-512") == 0)) { + return SSH_KEYTYPE_RSA; + } + + /* Otherwise the key type matches the signature type */ + return ssh_key_type_from_name(name); +} + +/** + * @brief Convert a ssh key name to a ssh key type. + * + * @param[in] name The name to convert. + * + * @return The enum ssh key type. + */ +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) { + return SSH_KEYTYPE_ECDSA_P256; + } else if (strcmp(name, "ecdsa-sha2-nistp384") == 0) { + return SSH_KEYTYPE_ECDSA_P384; + } else if (strcmp(name, "ecdsa-sha2-nistp521") == 0) { + 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) { + return SSH_KEYTYPE_ECDSA_P256_CERT01; + } else if (strcmp(name, "ecdsa-sha2-nistp384-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_ECDSA_P384_CERT01; + } else if (strcmp(name, "ecdsa-sha2-nistp521-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_ECDSA_P521_CERT01; + } else if (strcmp(name, "ssh-ed25519-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_ED25519_CERT01; + } + + return SSH_KEYTYPE_UNKNOWN; +} + +/** + * @brief Get the pubic 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) { + 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: + return SSH_KEYTYPE_ECDSA_P256; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + return SSH_KEYTYPE_ECDSA_P384; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + return SSH_KEYTYPE_ECDSA_P521; + case SSH_KEYTYPE_ED25519_CERT01: + return SSH_KEYTYPE_ED25519; + default: + return type; + } +} + +/** + * @brief Check if the key has/is a public key. + * + * @param[in] k The key to check. + * + * @return 1 if it is a public key, 0 if not. + */ +int ssh_key_is_public(const ssh_key k) { + if (k == NULL) { + return 0; + } + + return (k->flags & SSH_KEY_FLAG_PUBLIC) == SSH_KEY_FLAG_PUBLIC; +} + +/** + * @brief Check if the key is a private key. + * + * @param[in] k The key to check. + * + * @return 1 if it is a private key, 0 if not. + */ +int ssh_key_is_private(const ssh_key k) { + if (k == NULL) { + return 0; + } + + return (k->flags & SSH_KEY_FLAG_PRIVATE) == SSH_KEY_FLAG_PRIVATE; +} + +/** + * @brief Compare keys if they are equal. + * + * @param[in] k1 The first key to compare. + * + * @param[in] k2 The second key to compare. + * + * @param[in] what What part or type of the key do you want to compare. + * + * @return 0 if equal, 1 if not. + */ +int ssh_key_cmp(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what) +{ + if (k1 == NULL || k2 == NULL) { + return 1; + } + + if (k1->type != k2->type) { + SSH_LOG(SSH_LOG_WARN, "key types don't match!"); + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (!ssh_key_is_private(k1) || + !ssh_key_is_private(k2)) { + return 1; + } + } + + if (k1->type == SSH_KEYTYPE_ED25519) { + return pki_ed25519_key_cmp(k1, k2, what); + } + + return pki_key_compare(k1, k2, what); +} + +ssh_signature ssh_signature_new(void) +{ + struct ssh_signature_struct *sig; + + sig = malloc(sizeof(struct ssh_signature_struct)); + if (sig == NULL) { + return NULL; + } + ZERO_STRUCTP(sig); + + return sig; +} + +void ssh_signature_free(ssh_signature sig) +{ + if (sig == NULL) { + return; + } + + 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 + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_GCRYPT_ECC + gcry_sexp_release(sig->ecdsa_sig); +#elif defined HAVE_LIBMBEDCRYPTO + bignum_safe_free(sig->ecdsa_sig.r); + bignum_safe_free(sig->ecdsa_sig.s); +#endif + break; + case SSH_KEYTYPE_ED25519: +#ifndef HAVE_OPENSSL_ED25519 + /* When using OpenSSL, the signature is stored in sig->raw_sig */ + SAFE_FREE(sig->ed25519_sig); +#endif + break; + case SSH_KEYTYPE_DSS_CERT01: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + break; + } + + /* Explicitly zero the signature content before free */ + ssh_string_burn(sig->raw_sig); + SSH_STRING_FREE(sig->raw_sig); + SAFE_FREE(sig); +} + +/** + * @brief import a base64 formated key from a memory c-string + * + * @param[in] b64_key The c-string holding the base64 encoded key + * + * @param[in] passphrase The passphrase to decrypt the key, or NULL + * + * @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] pkey A pointer where the allocated key can be stored. You + * need to free the memory. + * + * @return SSH_ERROR in case of error, SSH_OK otherwise. + * + * @see ssh_key_free() + */ +int ssh_pki_import_privkey_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey) +{ + ssh_key key; + char *openssh_header = NULL; + + if (b64_key == NULL || pkey == NULL) { + return SSH_ERROR; + } + + if (b64_key == NULL || !*b64_key) { + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_INFO, + "Trying to decode privkey passphrase=%s", + passphrase ? "true" : "false"); + + /* Test for OpenSSH key format first */ + openssh_header = strstr(b64_key, OPENSSH_HEADER_BEGIN); + if (openssh_header != NULL) { + key = ssh_pki_openssh_privkey_import(openssh_header, + passphrase, + auth_fn, + auth_data); + } else { + /* fallback on PEM decoder */ + key = pki_private_key_from_base64(b64_key, + passphrase, + auth_fn, + auth_data); + } + if (key == NULL) { + return SSH_ERROR; + } + + *pkey = key; + + 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. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int ssh_pki_export_privkey_base64(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key) +{ + ssh_string blob = NULL; + char *b64 = NULL; + + if (privkey == NULL || !ssh_key_is_private(privkey)) { + return SSH_ERROR; + } + + if (privkey->type == SSH_KEYTYPE_ED25519){ + blob = ssh_pki_openssh_privkey_export(privkey, + passphrase, + auth_fn, + auth_data); + } else { + blob = pki_private_key_to_pem(privkey, + passphrase, + auth_fn, + auth_data); + } + if (blob == NULL) { + return SSH_ERROR; + } + + b64 = strndup(ssh_string_data(blob), ssh_string_len(blob)); + SSH_STRING_FREE(blob); + if (b64 == NULL) { + return SSH_ERROR; + } + + *b64_key = b64; + + return SSH_OK; +} + +/** + * @brief Import a key from a file. + * + * @param[in] filename The filename of the the private key. + * + * @param[in] passphrase The passphrase to decrypt the private key. Set to NULL + * if none is needed or it is unknown. + * + * @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] pkey A pointer to store the allocated ssh_key. You need to + * free the key. + * + * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission + * denied, SSH_ERROR otherwise. + * + * @see ssh_key_free() + **/ +int ssh_pki_import_privkey_file(const char *filename, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey) { + struct stat sb; + char *key_buf; + FILE *file; + off_t size; + int rc; + + if (pkey == NULL || filename == NULL || *filename == '\0') { + return SSH_ERROR; + } + + file = fopen(filename, "rb"); + if (file == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Error opening %s: %s", + filename, + strerror(errno)); + return SSH_EOF; + } + + rc = fstat(fileno(file), &sb); + if (rc < 0) { + fclose(file); + SSH_LOG(SSH_LOG_WARN, + "Error getting stat of %s: %s", + filename, + strerror(errno)); + switch (errno) { + case ENOENT: + case EACCES: + return SSH_EOF; + } + + return SSH_ERROR; + } + + if (sb.st_size > MAX_PRIVKEY_SIZE) { + SSH_LOG(SSH_LOG_WARN, + "Private key is bigger than 4M."); + fclose(file); + return SSH_ERROR; + } + + key_buf = malloc(sb.st_size + 1); + if (key_buf == NULL) { + fclose(file); + SSH_LOG(SSH_LOG_WARN, "Out of memory!"); + return SSH_ERROR; + } + + size = fread(key_buf, 1, sb.st_size, file); + fclose(file); + + if (size != sb.st_size) { + SAFE_FREE(key_buf); + SSH_LOG(SSH_LOG_WARN, + "Error reading %s: %s", + filename, + strerror(errno)); + return SSH_ERROR; + } + key_buf[size] = 0; + + rc = ssh_pki_import_privkey_base64(key_buf, + passphrase, + auth_fn, + auth_data, + pkey); + + SAFE_FREE(key_buf); + return rc; +} + +/** + * @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) +{ + ssh_string blob; + FILE *fp; + int rc; + + if (privkey == NULL || !ssh_key_is_private(privkey)) { + return SSH_ERROR; + } + + fp = fopen(filename, "wb"); + if (fp == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "Error opening %s: %s", + filename, strerror(errno)); + return SSH_EOF; + } + + if (privkey->type == SSH_KEYTYPE_ED25519){ + blob = ssh_pki_openssh_privkey_export(privkey, + passphrase, + auth_fn, + auth_data); + } else { + blob = pki_private_key_to_pem(privkey, + passphrase, + auth_fn, + auth_data); + } + if (blob == NULL) { + fclose(fp); + return -1; + } + + rc = fwrite(ssh_string_data(blob), ssh_string_len(blob), 1, fp); + SSH_STRING_FREE(blob); + if (rc != 1 || ferror(fp)) { + fclose(fp); + unlink(filename); + return SSH_ERROR; + } + fclose(fp); + + return SSH_OK; +} + +/* temporary function to migrate seemlessly 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) { + return NULL; + } + + tmp = ssh_key_dup(key); + if (tmp == NULL) { + return NULL; + } + + pub = malloc(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; + pub->rsa_pub = tmp->rsa; + tmp->rsa = NULL; + + ssh_key_free(tmp); + + return pub; +} + +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)); + if (privkey == NULL) { + ssh_key_free(key); + return NULL; + } + + privkey->type = key->type; + privkey->dsa_priv = key->dsa; + privkey->rsa_priv = key->rsa; + + return privkey; +} + +int pki_import_privkey_buffer(enum ssh_keytypes_e type, + ssh_buffer buffer, + ssh_key *pkey) +{ + ssh_key key = NULL; + int rc; + + key = ssh_key_new(); + if (key == NULL) { + return SSH_ERROR; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(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; + ssh_string e = NULL; + ssh_string d = NULL; + ssh_string iqmp = NULL; + ssh_string p = NULL; + ssh_string q = NULL; + + rc = ssh_buffer_unpack(buffer, "SSSSSS", &n, &e, &d, + &iqmp, &p, &q); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Unpack error"); + goto fail; + } + + rc = pki_privkey_build_rsa(key, n, e, d, iqmp, p, q); +#ifdef DEBUG_CRYPTO + 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("p", ssh_string_data(p), ssh_string_len(p)); + ssh_log_hexdump("q", ssh_string_data(q), ssh_string_len(q)); +#endif + ssh_string_burn(n); + SSH_STRING_FREE(n); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); + if (rc == SSH_ERROR) { + SSH_LOG(SSH_LOG_WARN, "Failed to build RSA private key"); + goto fail; + } + } + break; +#ifdef HAVE_ECC + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + { + ssh_string e = NULL; + ssh_string exp = NULL; + ssh_string i = NULL; + int nid; + + rc = ssh_buffer_unpack(buffer, "SSS", &i, &e, &exp); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Unpack error"); + goto fail; + } + + nid = pki_key_ecdsa_nid_from_name(ssh_string_get_char(i)); + SSH_STRING_FREE(i); + if (nid == -1) { + goto fail; + } + + rc = pki_privkey_build_ecdsa(key, nid, e, exp); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(exp); + SSH_STRING_FREE(exp); + if (rc < 0) { + SSH_LOG(SSH_LOG_WARN, "Failed to build ECDSA private key"); + goto fail; + } + } + break; +#endif + 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"); + goto fail; + } + + rc = pki_privkey_build_ed25519(key, pubkey, privkey); + ssh_string_burn(privkey); + SSH_STRING_FREE(privkey); + SSH_STRING_FREE(pubkey); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "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: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_WARN, "Unknown private key type (%d)", type); + goto fail; + } + + *pkey = key; + return SSH_OK; +fail: + ssh_key_free(key); + + return SSH_ERROR; +} + +static int pki_import_pubkey_buffer(ssh_buffer buffer, + enum ssh_keytypes_e type, + ssh_key *pkey) { + ssh_key key = NULL; + int rc; + + key = ssh_key_new(); + if (key == NULL) { + return SSH_ERROR; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(type); + 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; + ssh_string n = NULL; + + rc = ssh_buffer_unpack(buffer, "SS", &e, &n); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Unpack error"); + goto fail; + } + + rc = pki_pubkey_build_rsa(key, e, n); +#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 + 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"); + goto fail; + } + } + break; +#ifdef HAVE_ECC + case SSH_KEYTYPE_ECDSA: /* deprecated */ + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + { + ssh_string e = NULL; + ssh_string i = NULL; + int nid; + + rc = ssh_buffer_unpack(buffer, "SS", &i, &e); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Unpack error"); + goto fail; + } + + nid = pki_key_ecdsa_nid_from_name(ssh_string_get_char(i)); + SSH_STRING_FREE(i); + if (nid == -1) { + goto fail; + } + + rc = pki_pubkey_build_ecdsa(key, nid, e); + ssh_string_burn(e); + SSH_STRING_FREE(e); + if (rc < 0) { + SSH_LOG(SSH_LOG_WARN, "Failed to build ECDSA public key"); + goto fail; + } + + /* Update key type */ + if (type == SSH_KEYTYPE_ECDSA) { + key->type_c = ssh_pki_key_ecdsa_name(key); + } + } + break; +#endif + case SSH_KEYTYPE_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_string_burn(pubkey); + SSH_STRING_FREE(pubkey); + goto fail; + } + + key->ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (key->ed25519_pubkey == NULL) { + ssh_string_burn(pubkey); + SSH_STRING_FREE(pubkey); + goto fail; + } + + memcpy(key->ed25519_pubkey, ssh_string_data(pubkey), ED25519_KEY_LEN); + ssh_string_burn(pubkey); + SSH_STRING_FREE(pubkey); + } + break; + case SSH_KEYTYPE_DSS_CERT01: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_WARN, "Unknown public key protocol %d", type); + goto fail; + } + + *pkey = key; + return SSH_OK; +fail: + ssh_key_free(key); + + return SSH_ERROR; +} + +static int pki_import_cert_buffer(ssh_buffer buffer, + enum ssh_keytypes_e type, + ssh_key *pkey) { + ssh_buffer cert; + ssh_string tmp_s; + const char *type_c; + ssh_key key = NULL; + int rc; + + /* + * The cert blob starts with the key type as an ssh_string, but this + * string has been read out of the buffer to identify the key type. + * Simply add it again as first element before copying the rest. + */ + cert = ssh_buffer_new(); + if (cert == NULL) { + goto fail; + } + type_c = ssh_key_type_to_char(type); + tmp_s = ssh_string_from_char(type_c); + if (tmp_s == NULL) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(cert, tmp_s); + SSH_STRING_FREE(tmp_s); + if (rc != 0) { + goto fail; + } + rc = ssh_buffer_add_buffer(cert, buffer); + if (rc != 0) { + goto fail; + } + + /* + * After the key type, comes an ssh_string nonce. Just after this comes the + * cert public key, which can be parsed out of the buffer. + */ + tmp_s = ssh_buffer_get_ssh_string(buffer); + if (tmp_s == NULL) { + goto fail; + } + 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; + case SSH_KEYTYPE_ECDSA_P256_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_ECDSA_P256, &key); + break; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_ECDSA_P384, &key); + break; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_ECDSA_P521, &key); + break; + case SSH_KEYTYPE_ED25519_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_ED25519, &key); + break; + default: + key = ssh_key_new(); + } + if (rc != 0 || key == NULL) { + goto fail; + } + + key->type = type; + key->type_c = type_c; + key->cert = (void*) cert; + + *pkey = key; + return SSH_OK; + +fail: + ssh_key_free(key); + SSH_BUFFER_FREE(cert); + return SSH_ERROR; +} + +/** + * @brief Import a base64 formated 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. + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_key_free() + */ +int ssh_pki_import_pubkey_base64(const char *b64_key, + enum ssh_keytypes_e type, + ssh_key *pkey) { + ssh_buffer buffer = NULL; + ssh_string type_s = NULL; + int rc; + + if (b64_key == NULL || pkey == NULL) { + return SSH_ERROR; + } + + buffer = base64_to_bin(b64_key); + if (buffer == NULL) { + return SSH_ERROR; + } + + type_s = ssh_buffer_get_ssh_string(buffer); + if (type_s == NULL) { + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + SSH_STRING_FREE(type_s); + + if (is_cert_type(type)) { + rc = pki_import_cert_buffer(buffer, type, pkey); + } else { + rc = pki_import_pubkey_buffer(buffer, type, pkey); + } + SSH_BUFFER_FREE(buffer); + + return rc; +} + +/** + * @internal + * + * @brief Import a public key from a ssh string. + * + * @param[in] key_blob The key blob to import as specified in RFC 4253 section + * 6.6 "Public Key Algorithms". + * + * @param[out] pkey A pointer where the allocated key can be stored. You + * need to free the memory. + * + * @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_buffer buffer = NULL; + ssh_string type_s = NULL; + enum ssh_keytypes_e type; + int rc; + + if (key_blob == NULL || pkey == NULL) { + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "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!"); + goto fail; + } + + type_s = ssh_buffer_get_ssh_string(buffer); + if (type_s == NULL) { + SSH_LOG(SSH_LOG_WARN, "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!"); + goto fail; + } + SSH_STRING_FREE(type_s); + + if (is_cert_type(type)) { + rc = pki_import_cert_buffer(buffer, type, pkey); + } else { + rc = pki_import_pubkey_buffer(buffer, type, pkey); + } + + SSH_BUFFER_FREE(buffer); + + return rc; +fail: + SSH_BUFFER_FREE(buffer); + SSH_STRING_FREE(type_s); + + return SSH_ERROR; +} + +/** + * @brief Import a public key from the given filename. + * + * @param[in] filename The path to the public key. + * + * @param[out] pkey A pointer to store the allocated public key. You need to + * free the memory. + * + * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission + * denied, SSH_ERROR otherwise. + * + * @see ssh_key_free() + */ +int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) +{ + enum ssh_keytypes_e type; + struct stat sb; + char *key_buf, *p; + size_t buflen, i; + const char *q; + FILE *file; + off_t size; + int rc, cmp; + + if (pkey == NULL || filename == NULL || *filename == '\0') { + return SSH_ERROR; + } + + file = fopen(filename, "rb"); + if (file == NULL) { + SSH_LOG(SSH_LOG_WARN, "Error opening %s: %s", + filename, strerror(errno)); + 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)); + switch (errno) { + case ENOENT: + case EACCES: + return SSH_EOF; + } + return SSH_ERROR; + } + + if (sb.st_size > MAX_PUBKEY_SIZE) { + fclose(file); + return SSH_ERROR; + } + + key_buf = malloc(sb.st_size + 1); + if (key_buf == NULL) { + fclose(file); + SSH_LOG(SSH_LOG_WARN, "Out of memory!"); + return SSH_ERROR; + } + + size = fread(key_buf, 1, sb.st_size, file); + fclose(file); + + if (size != sb.st_size) { + SAFE_FREE(key_buf); + SSH_LOG(SSH_LOG_WARN, "Error reading %s: %s", + filename, strerror(errno)); + return SSH_ERROR; + } + key_buf[size] = '\0'; + buflen = strlen(key_buf); + + /* Test for new OpenSSH key format first */ + cmp = strncmp(key_buf, OPENSSH_HEADER_BEGIN, strlen(OPENSSH_HEADER_BEGIN)); + if (cmp == 0) { + *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" + " private key file"); + return SSH_ERROR; + } + return SSH_OK; + } + + /* This the old one-line public key format */ + q = p = key_buf; + for (i = 0; i < buflen; i++) { + if (isspace((int)p[i])) { + p[i] = '\0'; + break; + } + } + + type = ssh_key_type_from_name(q); + if (type == SSH_KEYTYPE_UNKNOWN) { + SAFE_FREE(key_buf); + return SSH_ERROR; + } + + q = &p[i + 1]; + for (; i < buflen; i++) { + if (isspace((int)p[i])) { + p[i] = '\0'; + break; + } + } + + rc = ssh_pki_import_pubkey_base64(q, type, pkey); + SAFE_FREE(key_buf); + + return rc; +} + +/** + * @brief Import a base64 formated 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. + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_key_free() + */ +int ssh_pki_import_cert_base64(const char *b64_cert, + enum ssh_keytypes_e type, + ssh_key *pkey) { + return ssh_pki_import_pubkey_base64(b64_cert, type, pkey); +} + +/** + * @internal + * + * @brief Import a certificate from a ssh string. + * + * @param[in] cert_blob The cert blob to import as specified in RFC 4253 section + * 6.6 "Public Key Algorithms". + * + * @param[out] pkey A pointer where the allocated key can be stored. You + * need to free the memory. + * + * @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) { + return ssh_pki_import_pubkey_blob(cert_blob, pkey); +} + +/** + * @brief Import a certificate from the given filename. + * + * @param[in] filename The path to the certificate. + * + * @param[out] pkey A pointer to store the allocated certificate. You need to + * free the memory. + * + * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission + * denied, SSH_ERROR otherwise. + * + * @see ssh_key_free() + */ +int ssh_pki_import_cert_file(const char *filename, ssh_key *pkey) +{ + return ssh_pki_import_pubkey_file(filename, pkey); +} + +/** + * @brief Generates a keypair. + * + * @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. + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @warning Generating a key pair may take some time. + */ +int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, + ssh_key *pkey){ + int rc; + ssh_key key = ssh_key_new(); + + if (key == NULL) { + return SSH_ERROR; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + + switch(type){ + case SSH_KEYTYPE_RSA: + rc = pki_key_generate_rsa(key, 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); + if (rc == SSH_ERROR) { + goto error; + } + + /* Update key type */ + key->type_c = ssh_pki_key_ecdsa_name(key); + break; + case SSH_KEYTYPE_ECDSA_P256: + rc = pki_key_generate_ecdsa(key, 256); + if (rc == SSH_ERROR) { + goto error; + } + break; + case SSH_KEYTYPE_ECDSA_P384: + rc = pki_key_generate_ecdsa(key, 384); + if (rc == SSH_ERROR) { + goto error; + } + break; + case SSH_KEYTYPE_ECDSA_P521: + rc = pki_key_generate_ecdsa(key, 521); + if (rc == SSH_ERROR) { + goto error; + } + break; +#endif + 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: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + goto error; + } + + *pkey = key; + return SSH_OK; +error: + ssh_key_free(key); + return SSH_ERROR; +} + +/** + * @brief Create a public key from a private key. + * + * @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. + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_key_free() + */ +int ssh_pki_export_privkey_to_pubkey(const ssh_key privkey, + ssh_key *pkey) +{ + ssh_key pubkey; + + if (privkey == NULL || !ssh_key_is_private(privkey)) { + return SSH_ERROR; + } + + pubkey = pki_key_dup(privkey, 1); + if (pubkey == NULL) { + return SSH_ERROR; + } + + *pkey = pubkey; + return SSH_OK; +} + +/** + * @internal + * + * @brief Create a key_blob from a public key. + * + * The "key_blob" is encoded as per RFC 4253 section 6.6 "Public Key + * Algorithms" for any of the supported protocol 2 key types. + * Encoding of EC keys is described in RFC 5656 section 3.1 "Key + * Format". + * + * @param[in] key A public or private key to create the public ssh_string + * from. + * + * @param[out] pblob A pointer to store the newly allocated key blob. You + * NEED to free it. + * + * @return SSH_OK on success, SSH_ERROR otherwise. + * + * @see SSH_STRING_FREE() + */ +int ssh_pki_export_pubkey_blob(const ssh_key key, + ssh_string *pblob) +{ + ssh_string blob; + + if (key == NULL) { + return SSH_OK; + } + + blob = pki_publickey_to_blob(key); + if (blob == NULL) { + return SSH_ERROR; + } + + *pblob = blob; + return SSH_OK; +} + +/** + * @brief Convert a public key to a base64 encoded 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. + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see SSH_STRING_FREE_CHAR() + */ +int ssh_pki_export_pubkey_base64(const ssh_key key, + char **b64_key) +{ + ssh_string key_blob; + unsigned char *b64; + + if (key == NULL || b64_key == NULL) { + return SSH_ERROR; + } + + key_blob = pki_publickey_to_blob(key); + if (key_blob == NULL) { + return SSH_ERROR; + } + + b64 = bin_to_base64(ssh_string_data(key_blob), ssh_string_len(key_blob)); + SSH_STRING_FREE(key_blob); + if (b64 == NULL) { + return SSH_ERROR; + } + + *b64_key = (char *)b64; + + return SSH_OK; +} + +int ssh_pki_export_pubkey_file(const ssh_key key, + const char *filename) +{ + char key_buf[4096]; + char host[256]; + char *b64_key; + char *user; + FILE *fp; + int rc; + + if (key == NULL || filename == NULL || *filename == '\0') { + return SSH_ERROR; + } + + user = ssh_get_local_username(); + if (user == NULL) { + return SSH_ERROR; + } + + rc = gethostname(host, sizeof(host)); + if (rc < 0) { + free(user); + return SSH_ERROR; + } + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + if (rc < 0) { + free(user); + return SSH_ERROR; + } + + rc = snprintf(key_buf, sizeof(key_buf), + "%s %s %s@%s\n", + key->type_c, + b64_key, + user, + host); + free(user); + free(b64_key); + if (rc < 0) { + return SSH_ERROR; + } + + fp = fopen(filename, "wb+"); + if (fp == NULL) { + return SSH_ERROR; + } + rc = fwrite(key_buf, strlen(key_buf), 1, fp); + if (rc != 1 || ferror(fp)) { + fclose(fp); + unlink(filename); + return SSH_ERROR; + } + fclose(fp); + + return SSH_OK; +} + +/** + * @brief Copy the certificate part of a public key into a private key. + * + * @param[in] certkey The certificate key. + * + * @param[in] privkey The target private key to copy the certificate to. + * + * @returns SSH_OK on success, SSH_ERROR otherwise. + **/ +int ssh_pki_copy_cert_to_privkey(const ssh_key certkey, ssh_key privkey) { + ssh_buffer cert_buffer; + int rc; + + if (certkey == NULL || privkey == NULL) { + return SSH_ERROR; + } + + if (privkey->cert != NULL) { + return SSH_ERROR; + } + + if (certkey->cert == NULL) { + return SSH_ERROR; + } + + cert_buffer = ssh_buffer_new(); + if (cert_buffer == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_add_buffer(cert_buffer, certkey->cert); + if (rc != 0) { + SSH_BUFFER_FREE(cert_buffer); + return SSH_ERROR; + } + + privkey->cert = cert_buffer; + privkey->cert_type = certkey->type; + return SSH_OK; +} + +int ssh_pki_export_signature_blob(const ssh_signature sig, + ssh_string *sig_blob) +{ + ssh_buffer buf = NULL; + ssh_string str; + int rc; + + if (sig == NULL || sig_blob == NULL) { + return SSH_ERROR; + } + + buf = ssh_buffer_new(); + if (buf == NULL) { + return SSH_ERROR; + } + + str = ssh_string_from_char(sig->type_c); + if (str == NULL) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + rc = ssh_buffer_add_ssh_string(buf, str); + SSH_STRING_FREE(str); + if (rc < 0) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + str = pki_signature_to_blob(sig); + if (str == NULL) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + rc = ssh_buffer_add_ssh_string(buf, str); + SSH_STRING_FREE(str); + if (rc < 0) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + str = ssh_string_new(ssh_buffer_get_len(buf)); + if (str == NULL) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + ssh_string_fill(str, ssh_buffer_get(buf), ssh_buffer_get_len(buf)); + SSH_BUFFER_FREE(buf); + + *sig_blob = str; + + return SSH_OK; +} + +int ssh_pki_import_signature_blob(const ssh_string sig_blob, + const ssh_key pubkey, + ssh_signature *psig) +{ + ssh_signature sig = NULL; + enum ssh_keytypes_e type; + enum ssh_digest_e hash_type; + ssh_string algorithm = NULL, blob = NULL; + ssh_buffer buf; + const char *alg = NULL; + int rc; + + if (sig_blob == NULL || psig == NULL) { + return SSH_ERROR; + } + + buf = ssh_buffer_new(); + if (buf == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_add_data(buf, + ssh_string_data(sig_blob), + ssh_string_len(sig_blob)); + if (rc < 0) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + algorithm = ssh_buffer_get_ssh_string(buf); + if (algorithm == NULL) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + alg = ssh_string_get_char(algorithm); + type = ssh_key_type_from_signature_name(alg); + hash_type = ssh_key_hash_from_name(alg); + SSH_STRING_FREE(algorithm); + + blob = ssh_buffer_get_ssh_string(buf); + SSH_BUFFER_FREE(buf); + if (blob == NULL) { + return SSH_ERROR; + } + + sig = pki_signature_from_blob(pubkey, blob, type, hash_type); + SSH_STRING_FREE(blob); + if (sig == NULL) { + return SSH_ERROR; + } + + *psig = sig; + return SSH_OK; +} + +/** + * @internal + * + * @brief Check if the provided key can be used with the provided hash type for + * data signing or signature verification. + * + * @param[in] key The key to be checked. + * @param[in] hash_type The digest algorithm to be checked. + * + * @return SSH_OK if compatible; SSH_ERROR otherwise + */ +int pki_key_check_hash_compatible(ssh_key key, + enum ssh_digest_e hash_type) +{ + if (key == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Null pointer provided as key to " + "pki_key_check_hash_compatible()"); + return SSH_ERROR; + } + + 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"); + return SSH_ERROR; + } else { + return SSH_OK; + } + } + + if (hash_type == SSH_DIGEST_SHA256 || + hash_type == SSH_DIGEST_SHA512) + { + return SSH_OK; + } + break; + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P256: + if (hash_type == SSH_DIGEST_SHA256) { + return SSH_OK; + } + break; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + if (hash_type == SSH_DIGEST_SHA384) { + return SSH_OK; + } + break; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + if (hash_type == SSH_DIGEST_SHA512) { + return SSH_OK; + } + break; + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_ED25519: + if (hash_type == SSH_DIGEST_AUTO) { + return SSH_OK; + } + break; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + SSH_LOG(SSH_LOG_WARN, "Unknown key type %d", key->type); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_WARN, "Key type %d incompatible with hash type %d", + key->type, hash_type); + + return SSH_ERROR; +} + +int ssh_pki_signature_verify(ssh_session session, + ssh_signature sig, + const ssh_key key, + const unsigned char *input, + size_t input_len) +{ + int rc; + enum ssh_keytypes_e key_type; + + if (session == NULL || sig == NULL || key == NULL || input == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "ssh_pki_signature_verify()"); + return SSH_ERROR; + } + key_type = ssh_key_type_plain(key->type); + + SSH_LOG(SSH_LOG_FUNCTIONS, + "Going to verify a %s type signature", + sig->type_c); + + if (key_type != sig->type) { + SSH_LOG(SSH_LOG_WARN, + "Can not verify %s signature with %s key", + sig->type_c, key->type_c); + 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) { + return SSH_ERROR; + } + + rc = pki_verify_data_signature(sig, key, input, input_len); + + return rc; +} + +ssh_signature pki_do_sign(const ssh_key privkey, + const unsigned char *input, + size_t input_len, + enum ssh_digest_e hash_type) +{ + int rc; + + if (privkey == NULL || input == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "pki_do_sign()"); + return NULL; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(privkey, hash_type); + if (rc != SSH_OK) { + return NULL; + } + + return pki_sign_data(privkey, hash_type, input, input_len); +} + +/* + * This function signs the session id as a string then + * the content of sigbuf */ +ssh_string ssh_pki_do_sign(ssh_session session, + ssh_buffer sigbuf, + const ssh_key privkey, + enum ssh_digest_e hash_type) +{ + struct ssh_crypto_struct *crypto = NULL; + + ssh_signature sig = NULL; + ssh_string sig_blob = NULL; + + ssh_string session_id = NULL; + ssh_buffer sign_input = NULL; + + int rc; + + if (session == NULL || sigbuf == NULL || privkey == NULL || + !ssh_key_is_private(privkey)) + { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "ssh_pki_do_sign()"); + return NULL; + } + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_BOTH); + if (crypto == NULL) { + return NULL; + } + + /* Get the session ID */ + session_id = ssh_string_new(crypto->digest_len); + if (session_id == NULL) { + return NULL; + } + ssh_string_fill(session_id, crypto->session_id, crypto->digest_len); + + /* Fill the input */ + sign_input = ssh_buffer_new(); + if (sign_input == NULL) { + goto end; + } + ssh_buffer_set_secure(sign_input); + + rc = ssh_buffer_pack(sign_input, + "SP", + session_id, + ssh_buffer_get_len(sigbuf), ssh_buffer_get(sigbuf)); + if (rc != SSH_OK) { + goto end; + } + + /* Generate the signature */ + sig = pki_do_sign(privkey, + ssh_buffer_get(sign_input), + ssh_buffer_get_len(sign_input), + hash_type); + if (sig == NULL) { + goto end; + } + + /* Convert the signature to blob */ + rc = ssh_pki_export_signature_blob(sig, &sig_blob); + if (rc < 0) { + sig_blob = NULL; + } + +end: + ssh_signature_free(sig); + SSH_BUFFER_FREE(sign_input); + SSH_STRING_FREE(session_id); + + return sig_blob; +} + +#ifndef _WIN32 +ssh_string ssh_pki_do_sign_agent(ssh_session session, + struct ssh_buffer_struct *buf, + const ssh_key pubkey) +{ + struct ssh_crypto_struct *crypto = NULL; + ssh_string session_id; + ssh_string sig_blob; + ssh_buffer sig_buf; + int rc; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_BOTH); + if (crypto == NULL) { + return NULL; + } + + /* prepend session identifier */ + session_id = ssh_string_new(crypto->digest_len); + if (session_id == NULL) { + return NULL; + } + ssh_string_fill(session_id, crypto->session_id, crypto->digest_len); + + sig_buf = ssh_buffer_new(); + if (sig_buf == NULL) { + SSH_STRING_FREE(session_id); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(sig_buf, session_id); + if (rc < 0) { + SSH_STRING_FREE(session_id); + SSH_BUFFER_FREE(sig_buf); + return NULL; + } + SSH_STRING_FREE(session_id); + + /* append out buffer */ + if (ssh_buffer_add_buffer(sig_buf, buf) < 0) { + SSH_BUFFER_FREE(sig_buf); + return NULL; + } + + /* create signature */ + sig_blob = ssh_agent_sign_data(session, pubkey, sig_buf); + + SSH_BUFFER_FREE(sig_buf); + + return sig_blob; +} +#endif /* _WIN32 */ + +#ifdef WITH_SERVER +ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, + const ssh_key privkey, + const enum ssh_digest_e digest) +{ + struct ssh_crypto_struct *crypto = NULL; + + ssh_signature sig = NULL; + ssh_string sig_blob = NULL; + + ssh_buffer sign_input = NULL; + + int rc; + + if (session == NULL || privkey == NULL || !ssh_key_is_private(privkey)) { + 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"); + return NULL; + } + + /* Fill the input */ + sign_input = ssh_buffer_new(); + if (sign_input == NULL) { + goto end; + } + ssh_buffer_set_secure(sign_input); + + rc = ssh_buffer_pack(sign_input, + "P", + crypto->digest_len, + crypto->secret_hash); + if (rc != SSH_OK) { + goto end; + } + + /* Generate the signature */ + sig = pki_do_sign(privkey, + ssh_buffer_get(sign_input), + ssh_buffer_get_len(sign_input), + digest); + if (sig == NULL) { + goto end; + } + + /* Convert the signature to blob */ + rc = ssh_pki_export_signature_blob(sig, &sig_blob); + if (rc < 0) { + sig_blob = NULL; + } + +end: + ssh_signature_free(sig); + SSH_BUFFER_FREE(sign_input); + + return sig_blob; +} +#endif /* WITH_SERVER */ + +/** + * @} + */ diff --git a/src/pki_container_openssh.c b/src/pki_container_openssh.c new file mode 100644 index 0000000..ecde4cd --- /dev/null +++ b/src/pki_container_openssh.c @@ -0,0 +1,727 @@ +/* + * pki_container_openssh.c + * This file is part of the SSH Library + * + * Copyright (c) 2013,2014 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. + */ + +/** + * @ingroup libssh_pki + * * + * @{ + */ + +#include "config.h" + +#include +#include +#include + +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/buffer.h" + + +/** + * @internal + * + * @brief Import a private key from a ssh buffer. + * + * @param[in] key_blob_buffer The key blob to import as specified in + * key.c:key_private_serialize in OpenSSH source + * code. + * + * @param[out] pkey A pointer where the allocated key can be stored. You + * need to free the memory. + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_key_free() + */ +static int pki_openssh_import_privkey_blob(ssh_buffer key_blob_buffer, + ssh_key *pkey) +{ + enum ssh_keytypes_e type; + char *type_s = NULL; + ssh_key key = NULL; + int rc; + + if (pkey == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_unpack(key_blob_buffer, "s", &type_s); + if (rc == SSH_ERROR){ + SSH_LOG(SSH_LOG_WARN, "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); + 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"); + goto fail; + } + + *pkey = key; + return SSH_OK; +fail: + ssh_key_free(key); + + return SSH_ERROR; +} + +/** + * @brief decrypts an encrypted private key blob in OpenSSH format. + * + */ +static int pki_private_key_decrypt(ssh_string blob, + const char* passphrase, + const char *ciphername, + const char *kdfname, + ssh_string kdfoptions, + ssh_auth_callback auth_fn, + void *auth_data) +{ + struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); + struct ssh_cipher_struct cipher; + uint8_t key_material[128] = {0}; + char passphrase_buffer[128] = {0}; + size_t key_material_len; + ssh_buffer buffer = NULL; + ssh_string salt = NULL; + uint32_t rounds; + int cmp; + int rc; + int i; + + cmp = strcmp(ciphername, "none"); + if (cmp == 0){ + /* no decryption required */ + return SSH_OK; + } + + for (i = 0; ciphers[i].name != NULL; i++) { + cmp = strcmp(ciphername, ciphers[i].name); + if (cmp == 0){ + memcpy(&cipher, &ciphers[i], sizeof(cipher)); + break; + } + } + + if (ciphers[i].name == NULL){ + SSH_LOG(SSH_LOG_WARN, "Unsupported cipher %s", ciphername); + return SSH_ERROR; + } + + cmp = strcmp(kdfname, "bcrypt"); + if (cmp != 0) { + SSH_LOG(SSH_LOG_WARN, "Unsupported KDF %s", kdfname); + return SSH_ERROR; + } + if (ssh_string_len(blob) % cipher.blocksize != 0) { + SSH_LOG(SSH_LOG_WARN, + "Encrypted string not multiple of blocksize: %zu", + ssh_string_len(blob)); + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL){ + return SSH_ERROR; + } + rc = ssh_buffer_add_data(buffer, + ssh_string_data(kdfoptions), + ssh_string_len(kdfoptions)); + if (rc != SSH_ERROR){ + rc = ssh_buffer_unpack(buffer, "Sd", &salt, &rounds); + } + SSH_BUFFER_FREE(buffer); + if (rc == SSH_ERROR){ + 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"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Decryption: %d key, %d IV, %d rounds, %zu bytes salt", + cipher.keysize/8, + cipher.blocksize, + rounds, + ssh_string_len(salt)); + + if (passphrase == NULL) { + if (auth_fn == NULL) { + SAFE_FREE(salt); + SSH_LOG(SSH_LOG_WARN, "No passphrase provided"); + return SSH_ERROR; + } + rc = auth_fn("Passphrase", + passphrase_buffer, + sizeof(passphrase_buffer), + 0, + 0, + auth_data); + if (rc != SSH_OK) { + SAFE_FREE(salt); + return SSH_ERROR; + } + passphrase = passphrase_buffer; + } + + rc = bcrypt_pbkdf(passphrase, + strlen(passphrase), + ssh_string_data(salt), + ssh_string_len(salt), + key_material, + key_material_len, + rounds); + SAFE_FREE(salt); + if (rc < 0){ + return SSH_ERROR; + } + explicit_bzero(passphrase_buffer, sizeof(passphrase_buffer)); + + cipher.set_decrypt_key(&cipher, + key_material, + key_material + cipher.keysize/8); + cipher.decrypt(&cipher, + ssh_string_data(blob), + ssh_string_data(blob), + ssh_string_len(blob)); + ssh_cipher_clear(&cipher); + return SSH_OK; +} + + +/** @internal + * @brief Import a private key in OpenSSH (new) format. This format is + * typically used with ed25519 keys but can be used for others. + */ +static ssh_key +ssh_pki_openssh_import(const char *text_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + bool private) +{ + const char *ptr = text_key; + const char *end; + char *base64; + int cmp; + int rc; + int i; + ssh_buffer buffer = NULL, privkey_buffer=NULL; + char *magic = NULL, *ciphername = NULL, *kdfname = NULL; + uint32_t nkeys = 0, checkint1 = 0, checkint2 = 0xFFFF; + ssh_string kdfoptions = NULL; + ssh_string pubkey0 = NULL; + ssh_string privkeys = NULL; + ssh_string comment = NULL; + ssh_key key = NULL; + uint8_t padding; + + 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)"); + goto out; + } + ptr += strlen(OPENSSH_HEADER_BEGIN); + while(ptr[0] != '\0' && !isspace((int)ptr[0])) { + ptr++; + } + end = strstr(ptr, OPENSSH_HEADER_END); + if (end == NULL) { + SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (no footer)"); + goto out; + } + base64 = malloc(end - ptr + 1); + if (base64 == NULL) { + goto out; + } + for (i = 0; ptr < end; ptr++) { + if (!isspace((int)ptr[0])) { + base64[i] = ptr[0]; + i++; + } + } + base64[i] = '\0'; + buffer = base64_to_bin(base64); + SAFE_FREE(base64); + if (buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (base64 error)"); + goto out; + } + rc = ssh_buffer_unpack(buffer, "PssSdSS", + strlen(OPENSSH_AUTH_MAGIC) + 1, + &magic, + &ciphername, + &kdfname, + &kdfoptions, + &nkeys, + &pubkey0, + &privkeys); + if (rc == SSH_ERROR) { + SSH_LOG(SSH_LOG_WARN, "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)"); + goto out; + } + SSH_LOG(SSH_LOG_INFO, + "Opening OpenSSH private key: ciphername: %s, kdf: %s, nkeys: %d", + ciphername, + kdfname, + nkeys); + if (nkeys != 1) { + SSH_LOG(SSH_LOG_WARN, "Opening OpenSSH private key: only 1 key supported (%d available)", nkeys); + goto out; + } + + /* If we are interested only in public key do not progress + * to the key decryption later + */ + 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"); + } + /* in either case we clean up here */ + goto out; + } + + rc = pki_private_key_decrypt(privkeys, + passphrase, + ciphername, + kdfname, + kdfoptions, + auth_fn, + auth_data); + if (rc == SSH_ERROR) { + goto out; + } + + privkey_buffer = ssh_buffer_new(); + if (privkey_buffer == NULL) { + goto out; + } + + ssh_buffer_set_secure(privkey_buffer); + ssh_buffer_add_data(privkey_buffer, + ssh_string_data(privkeys), + ssh_string_len(privkeys)); + + 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?)"); + goto out; + } + rc = pki_openssh_import_privkey_blob(privkey_buffer, &key); + if (rc == SSH_ERROR) { + goto out; + } + comment = ssh_buffer_get_ssh_string(privkey_buffer); + SAFE_FREE(comment); + /* verify that the remaining data is correct padding */ + for (i = 1; ssh_buffer_get_len(privkey_buffer) > 0; ++i) { + ssh_buffer_get_u8(privkey_buffer, &padding); + if (padding != i) { + ssh_key_free(key); + key = NULL; + SSH_LOG(SSH_LOG_WARN, "Invalid padding"); + goto out; + } + } +out: + if (buffer != NULL) { + SSH_BUFFER_FREE(buffer); + buffer = NULL; + } + if (privkey_buffer != NULL) { + SSH_BUFFER_FREE(privkey_buffer); + privkey_buffer = NULL; + } + SAFE_FREE(magic); + SAFE_FREE(ciphername); + SAFE_FREE(kdfname); + SAFE_FREE(kdfoptions); + SAFE_FREE(pubkey0); + SAFE_FREE(privkeys); + return key; +} + +ssh_key ssh_pki_openssh_privkey_import(const char *text_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + return ssh_pki_openssh_import(text_key, passphrase, auth_fn, auth_data, true); +} + +ssh_key ssh_pki_openssh_pubkey_import(const char *text_key) +{ + return ssh_pki_openssh_import(text_key, NULL, NULL, NULL, false); +} + + +/** @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 + * + */ +static int pki_private_key_encrypt(ssh_buffer privkey_buffer, + const char* passphrase, + const char *ciphername, + const char *kdfname, + ssh_auth_callback auth_fn, + void *auth_data, + uint32_t rounds, + ssh_string salt) +{ + struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); + struct ssh_cipher_struct cipher; + uint8_t key_material[128] = {0}; + size_t key_material_len; + char passphrase_buffer[128] = {0}; + int rc; + int i; + int cmp; + + cmp = strcmp(ciphername, "none"); + if (cmp == 0){ + /* no encryption required */ + return SSH_OK; + } + + for (i = 0; ciphers[i].name != NULL; i++) { + cmp = strcmp(ciphername, ciphers[i].name); + if (cmp == 0){ + memcpy(&cipher, &ciphers[i], sizeof(cipher)); + break; + } + } + + if (ciphers[i].name == NULL){ + SSH_LOG(SSH_LOG_WARN, "Unsupported cipher %s", ciphername); + return SSH_ERROR; + } + + cmp = strcmp(kdfname, "bcrypt"); + if (cmp != 0){ + SSH_LOG(SSH_LOG_WARN, "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"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_WARN, "Encryption: %d key, %d IV, %d 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"); + return SSH_ERROR; + } + rc = auth_fn("Passphrase", + passphrase_buffer, + sizeof(passphrase_buffer), + 0, + 0, + auth_data); + if (rc != SSH_OK){ + return SSH_ERROR; + } + passphrase = passphrase_buffer; + } + + rc = bcrypt_pbkdf(passphrase, + strlen(passphrase), + ssh_string_data(salt), + ssh_string_len(salt), + key_material, + key_material_len, + rounds); + if (rc < 0){ + return SSH_ERROR; + } + + cipher.set_encrypt_key(&cipher, + key_material, + key_material + cipher.keysize/8); + cipher.encrypt(&cipher, + ssh_buffer_get(privkey_buffer), + ssh_buffer_get(privkey_buffer), + ssh_buffer_get_len(privkey_buffer)); + ssh_cipher_clear(&cipher); + explicit_bzero(passphrase_buffer, sizeof(passphrase_buffer)); + + return SSH_OK; +} + + +/** @internal + * generate an OpenSSH private key (defined in PROTOCOL.key) and output it in text format. + * @param privkey[in] private key to export + * @returns an SSH string containing the text representation of the exported key. + * @warning currently only supports ED25519 key types. + */ + +ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + ssh_buffer buffer; + ssh_string str = NULL; + ssh_string pubkey_s=NULL; + ssh_buffer privkey_buffer = NULL; + uint32_t rnd; + uint32_t rounds = 16; + ssh_string salt=NULL; + ssh_string kdf_options=NULL; + int to_encrypt=0; + unsigned char *b64; + uint32_t str_len, len; + uint8_t padding = 1; + int ok; + int rc; + + 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"); + to_encrypt = 1; + } + buffer = ssh_buffer_new(); + pubkey_s = pki_publickey_to_blob(privkey); + if(buffer == NULL || pubkey_s == NULL){ + goto error; + } + + ok = ssh_get_random(&rnd, sizeof(rnd), 0); + if (!ok) { + goto error; + } + + privkey_buffer = ssh_buffer_new(); + if (privkey_buffer == NULL) { + 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){ + goto error; + } + + /* comment */ + rc = ssh_buffer_pack(privkey_buffer, "s", "" /* comment */); + if (rc == SSH_ERROR){ + goto error; + } + + /* Add padding regardless encryption because it is expected + * by OpenSSH tools. + * XXX Using 16 B as we use only AES cipher below anyway. + */ + while (ssh_buffer_get_len(privkey_buffer) % 16 != 0) { + rc = ssh_buffer_add_u8(privkey_buffer, padding); + if (rc < 0) { + goto error; + } + padding++; + } + + if (to_encrypt){ + ssh_buffer kdf_buf; + + kdf_buf = ssh_buffer_new(); + if (kdf_buf == NULL) { + goto error; + } + + salt = ssh_string_new(16); + if (salt == NULL){ + SSH_BUFFER_FREE(kdf_buf); + goto error; + } + + ok = ssh_get_random(ssh_string_data(salt), 16, 0); + if (!ok) { + SSH_BUFFER_FREE(kdf_buf); + goto error; + } + + ssh_buffer_pack(kdf_buf, "Sd", salt, rounds); + kdf_options = ssh_string_new(ssh_buffer_get_len(kdf_buf)); + if (kdf_options == NULL){ + SSH_BUFFER_FREE(kdf_buf); + goto error; + } + memcpy(ssh_string_data(kdf_options), + ssh_buffer_get(kdf_buf), + ssh_buffer_get_len(kdf_buf)); + SSH_BUFFER_FREE(kdf_buf); + rc = pki_private_key_encrypt(privkey_buffer, + passphrase, + "aes128-cbc", + "bcrypt", + auth_fn, + auth_data, + rounds, + salt); + if (rc != SSH_OK){ + goto error; + } + } else { + kdf_options = ssh_string_new(0); + } + + rc = ssh_buffer_pack(buffer, + "PssSdSdP", + (size_t)strlen(OPENSSH_AUTH_MAGIC) + 1, OPENSSH_AUTH_MAGIC, + to_encrypt ? "aes128-cbc" : "none", /* ciphername */ + to_encrypt ? "bcrypt" : "none", /* kdfname */ + kdf_options, /* kdfoptions */ + (uint32_t) 1, /* nkeys */ + pubkey_s, + (uint32_t)ssh_buffer_get_len(privkey_buffer), + /* rest of buffer is a string */ + (size_t)ssh_buffer_get_len(privkey_buffer), ssh_buffer_get(privkey_buffer)); + if (rc != SSH_OK) { + goto error; + } + + b64 = bin_to_base64(ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); + if (b64 == NULL){ + goto error; + } + + /* we can reuse the buffer */ + ssh_buffer_reinit(buffer); + rc = ssh_buffer_pack(buffer, + "tttttt", + OPENSSH_HEADER_BEGIN, + "\n", + b64, + "\n", + OPENSSH_HEADER_END, + "\n"); + explicit_bzero(b64, strlen((char *)b64)); + SAFE_FREE(b64); + + if (rc != SSH_OK){ + goto error; + } + + str = ssh_string_new(ssh_buffer_get_len(buffer)); + if (str == NULL){ + goto error; + } + + str_len = ssh_buffer_get_len(buffer); + len = ssh_buffer_get_data(buffer, ssh_string_data(str), str_len); + if (str_len != len) { + SSH_STRING_FREE(str); + str = NULL; + } + +error: + if (privkey_buffer != NULL) { + void *bufptr = ssh_buffer_get(privkey_buffer); + explicit_bzero(bufptr, ssh_buffer_get_len(privkey_buffer)); + SSH_BUFFER_FREE(privkey_buffer); + } + SAFE_FREE(pubkey_s); + SAFE_FREE(kdf_options); + SAFE_FREE(salt); + if (buffer != NULL) { + SSH_BUFFER_FREE(buffer); + } + + return str; +} + + +/** + * @} + */ diff --git a/src/pki_crypto.c b/src/pki_crypto.c new file mode 100644 index 0000000..26d2da1 --- /dev/null +++ b/src/pki_crypto.c @@ -0,0 +1,2462 @@ +/* + * pki_crypto.c - PKI infrastructure using OpenSSL + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * Copyright (c) 2009-2013 by Andreas Schneider + * + * 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. + */ + +#ifndef _PKI_CRYPTO_H +#define _PKI_CRYPTO_H + +#include "config.h" + +#include "libssh/priv.h" + +#include +#include +#include +#include +#include "libcrypto-compat.h" + +#ifdef HAVE_OPENSSL_EC_H +#include +#endif +#ifdef HAVE_OPENSSL_ECDSA_H +#include +#endif + +#include "libssh/libssh.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/bignum.h" + +struct pem_get_password_struct { + ssh_auth_callback fn; + void *data; +}; + +static int pem_get_password(char *buf, int size, int rwflag, void *userdata) { + struct pem_get_password_struct *pgp = userdata; + + (void) rwflag; /* unused */ + + if (buf == NULL) { + return 0; + } + + memset(buf, '\0', size); + if (pgp) { + int rc; + + rc = pgp->fn("Passphrase for private key:", + buf, size, 0, 0, + pgp->data); + if (rc == 0) { + return strlen(buf); + } + } + + return 0; +} + +#ifdef HAVE_OPENSSL_ECC +static int pki_key_ecdsa_to_nid(EC_KEY *k) +{ + const EC_GROUP *g = EC_KEY_get0_group(k); + int nid; + + nid = EC_GROUP_get_curve_name(g); + if (nid) { + return nid; + } + + return -1; +} + +static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(EC_KEY *k) +{ + int nid; + + nid = pki_key_ecdsa_to_nid(k); + + switch (nid) { + case NID_X9_62_prime256v1: + return SSH_KEYTYPE_ECDSA_P256; + case NID_secp384r1: + return SSH_KEYTYPE_ECDSA_P384; + case NID_secp521r1: + return SSH_KEYTYPE_ECDSA_P521; + default: + return SSH_KEYTYPE_UNKNOWN; + } +} + +const char *pki_key_ecdsa_nid_to_name(int nid) +{ + switch (nid) { + case NID_X9_62_prime256v1: + return "ecdsa-sha2-nistp256"; + case NID_secp384r1: + return "ecdsa-sha2-nistp384"; + case NID_secp521r1: + return "ecdsa-sha2-nistp521"; + default: + break; + } + + return "unknown"; +} + +static const char *pki_key_ecdsa_nid_to_char(int nid) +{ + switch (nid) { + case NID_X9_62_prime256v1: + return "nistp256"; + case NID_secp384r1: + return "nistp384"; + case NID_secp521r1: + return "nistp521"; + default: + break; + } + + return "unknown"; +} + +int pki_key_ecdsa_nid_from_name(const char *name) +{ + if (strcmp(name, "nistp256") == 0) { + return NID_X9_62_prime256v1; + } else if (strcmp(name, "nistp384") == 0) { + return NID_secp384r1; + } else if (strcmp(name, "nistp521") == 0) { + return NID_secp521r1; + } + + return -1; +} + +static ssh_string make_ecpoint_string(const EC_GROUP *g, + const EC_POINT *p) +{ + ssh_string s; + 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_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; + + 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; + } + + g = EC_KEY_get0_group(key->ecdsa); + + p = EC_POINT_new(g); + if (p == NULL) { + return -1; + } + + ok = EC_POINT_oct2point(g, + p, + ssh_string_data(e), + ssh_string_len(e), + NULL); + if (!ok) { + EC_POINT_free(p); + return -1; + } + + /* EC_KEY_set_public_key duplicates p */ + ok = EC_KEY_set_public_key(key->ecdsa, p); + EC_POINT_free(p); + if (!ok) { + return -1; + } + + 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); + BN_free(bexp); + if (!ok) { + EC_KEY_free(key->ecdsa); + return -1; + } + + return 0; +} + +int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) +{ + EC_POINT *p = NULL; + const EC_GROUP *g = NULL; + int ok; + + 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; + } + + g = EC_KEY_get0_group(key->ecdsa); + + p = EC_POINT_new(g); + if (p == NULL) { + return -1; + } + + ok = EC_POINT_oct2point(g, + p, + ssh_string_data(e), + ssh_string_len(e), + NULL); + if (!ok) { + EC_POINT_free(p); + return -1; + } + + /* EC_KEY_set_public_key duplicates p */ + ok = EC_KEY_set_public_key(key->ecdsa, p); + EC_POINT_free(p); + if (!ok) { + return -1; + } + + return 0; +} +#endif + +ssh_key pki_key_dup(const ssh_key key, int demote) +{ + ssh_key new; + int rc; + + new = ssh_key_new(); + if (new == NULL) { + return NULL; + } + + new->type = key->type; + new->type_c = key->type_c; + if (demote) { + new->flags = SSH_KEY_FLAG_PUBLIC; + } else { + new->flags = key->flags; + } + + 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: { + const BIGNUM *n = NULL, *e = NULL, *d = NULL; + BIGNUM *nn, *ne, *nd; + new->rsa = RSA_new(); + if (new->rsa == NULL) { + goto fail; + } + + /* + * n = public modulus + * e = public exponent + * d = private exponent + * p = secret prime factor + * q = secret prime factor + * dmp1 = d mod (p-1) + * dmq1 = d mod (q-1) + * iqmp = q^-1 mod p + */ + RSA_get0_key(key->rsa, &n, &e, &d); + nn = BN_dup(n); + ne = BN_dup(e); + if (nn == NULL || ne == NULL) { + 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); + if (rc == 0) { + BN_free(nn); + BN_free(ne); + goto fail; + } + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { + const BIGNUM *p = NULL, *q = NULL, *dmp1 = NULL, + *dmq1 = NULL, *iqmp = NULL; + BIGNUM *np, *nq, *ndmp1, *ndmq1, *niqmp; + + nd = BN_dup(d); + if (nd == NULL) { + goto fail; + } + + /* Memory management of nd is transferred to RSA object */ + rc = RSA_set0_key(new->rsa, NULL, NULL, nd); + if (rc == 0) { + 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); + if (p != NULL && q != NULL) { /* need to set both of them */ + np = BN_dup(p); + nq = BN_dup(q); + if (np == NULL || nq == NULL) { + 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); + if (rc == 0) { + BN_free(np); + BN_free(nq); + goto fail; + } + } + + 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) { + BN_free(ndmp1); + BN_free(ndmq1); + BN_free(niqmp); + goto fail; + } + + /* Memory management of ndmp1, ndmq1 and niqmp is transferred + * to RSA object */ + rc = RSA_set0_crt_params(new->rsa, ndmp1, ndmq1, niqmp); + if (rc == 0) { + BN_free(ndmp1); + BN_free(ndmq1); + BN_free(niqmp); + goto fail; + } + } + } + + break; + } + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_OPENSSL_ECC + new->ecdsa_nid = key->ecdsa_nid; + + /* privkey -> pubkey */ + if (demote && ssh_key_is_private(key)) { + const EC_POINT *p; + int ok; + + 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); + if (p == NULL) { + goto fail; + } + + ok = EC_KEY_set_public_key(new->ecdsa, p); + if (!ok) { + goto fail; + } + } else { + new->ecdsa = EC_KEY_dup(key->ecdsa); + } + break; +#endif + case SSH_KEYTYPE_ED25519: + rc = pki_ed25519_key_dup(new, key); + if (rc != SSH_OK) { + goto fail; + } + break; + case SSH_KEYTYPE_UNKNOWN: + default: + ssh_key_free(new); + return NULL; + } + + return new; +fail: + ssh_key_free(new); + return NULL; +} + +int pki_key_generate_rsa(ssh_key key, int parameter){ + BIGNUM *e; + int rc; + + e = BN_new(); + key->rsa = RSA_new(); + + BN_set_word(e, 65537); + rc = RSA_generate_key_ex(key->rsa, parameter, e, NULL); + + BN_free(e); + + if (rc <= 0 || key->rsa == NULL) + return SSH_ERROR; + return SSH_OK; +} + +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) { + return SSH_ERROR; + } + rc = DSA_generate_parameters_ex(key->dsa, + parameter, + NULL, /* seed */ + 0, /* seed_len */ + NULL, /* counter_ret */ + NULL, /* h_ret */ + NULL); /* cb */ + if (rc != 1) { + DSA_free(key->dsa); + key->dsa = NULL; + return SSH_ERROR; + } +#else + key->dsa = DSA_generate_parameters(parameter, NULL, 0, NULL, NULL, + NULL, NULL); + if(key->dsa == NULL){ + return SSH_ERROR; + } +#endif + rc = DSA_generate_key(key->dsa); + if (rc != 1){ + DSA_free(key->dsa); + key->dsa=NULL; + return SSH_ERROR; + } + return SSH_OK; +} + +#ifdef HAVE_OPENSSL_ECC +int pki_key_generate_ecdsa(ssh_key key, int parameter) { + int ok; + + switch (parameter) { + case 384: + key->ecdsa_nid = NID_secp384r1; + key->type = SSH_KEYTYPE_ECDSA_P384; + 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; + break; + default: + SSH_LOG(SSH_LOG_WARN, "Invalid parameter %d for ECDSA key " + "generation", parameter); + return SSH_ERROR; + } + + key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (key->ecdsa == NULL) { + return SSH_ERROR; + } + + ok = EC_KEY_generate_key(key->ecdsa); + if (!ok) { + EC_KEY_free(key->ecdsa); + return SSH_ERROR; + } + + EC_KEY_set_asn1_flag(key->ecdsa, OPENSSL_EC_NAMED_CURVE); + + return SSH_OK; +} +#endif + +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; + } + + 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; + } + + 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; + } + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#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); + + if (p1 == NULL || p2 == NULL) { + return 1; + } + + if (EC_GROUP_cmp(g1, g2, NULL) != 0) { + return 1; + } + + if (EC_POINT_cmp(g1, p1, p2, NULL) != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (bignum_cmp(EC_KEY_get0_private_key(k1->ecdsa), + EC_KEY_get0_private_key(k2->ecdsa))) { + return 1; + } + } + + break; + } +#endif + case SSH_KEYTYPE_ED25519: + /* ed25519 keys handled globaly */ + case SSH_KEYTYPE_UNKNOWN: + default: + return 1; + } + + return 0; +} + +ssh_string pki_private_key_to_pem(const ssh_key key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + ssh_string blob = NULL; + BUF_MEM *buf = NULL; + BIO *mem = NULL; + EVP_PKEY *pkey = NULL; + int rc; + + mem = BIO_new(BIO_s_mem()); + if (mem == NULL) { + return NULL; + } + + 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) { + goto err; + } + + 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) */ + pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, + (const uint8_t *)key->ed25519_privkey, + ED25519_KEY_LEN); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create ed25519 EVP_PKEY: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto err; + } + + /* 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: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_WARN, "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"); + goto err; + } + + if (passphrase == NULL) { + struct pem_get_password_struct pgp = { auth_fn, auth_data }; + + rc = PEM_write_bio_PrivateKey(mem, + pkey, + NULL, /* cipher */ + NULL, /* kstr */ + 0, /* klen */ + pem_get_password, + &pgp); + } else { + rc = PEM_write_bio_PrivateKey(mem, + pkey, + EVP_aes_128_cbc(), + NULL, /* kstr */ + 0, /* klen */ + NULL, /* auth_fn */ + (void*) passphrase); + } + EVP_PKEY_free(pkey); + pkey = NULL; + + if (rc != 1) { + goto err; + } + + BIO_get_mem_ptr(mem, &buf); + + blob = ssh_string_new(buf->length); + if (blob == NULL) { + goto err; + } + + ssh_string_fill(blob, buf->data, buf->length); + BIO_free(mem); + + return blob; + +err: + EVP_PKEY_free(pkey); + BIO_free(mem); + return NULL; +} + +ssh_key pki_private_key_from_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + BIO *mem = NULL; + DSA *dsa = NULL; + RSA *rsa = NULL; +#ifdef HAVE_OPENSSL_ED25519 + uint8_t *ed25519 = NULL; +#else + ed25519_privkey *ed25519 = NULL; +#endif + 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); + + if (passphrase == NULL) { + if (auth_fn) { + struct pem_get_password_struct pgp = { auth_fn, auth_data }; + + pkey = PEM_read_bio_PrivateKey(mem, NULL, pem_get_password, &pgp); + } else { + /* openssl uses its own callback to get the passphrase here */ + pkey = PEM_read_bio_PrivateKey(mem, NULL, NULL, NULL); + } + } else { + pkey = PEM_read_bio_PrivateKey(mem, NULL, NULL, (void *) passphrase); + } + + BIO_free(mem); + + if (pkey == NULL) { + SSH_LOG(SSH_LOG_WARN, + "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 (ecdsa == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Parsing private key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + /* 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); + if (type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_WARN, "Invalid private key."); + goto fail; + } + + break; +#endif +#ifdef HAVE_OPENSSL_ED25519 + case EVP_PKEY_ED25519: + { + size_t key_len; + int evp_rc = 0; + + /* Get the key length */ + evp_rc = EVP_PKEY_get_raw_private_key(pkey, NULL, &key_len); + if (evp_rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw private key length: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + if (key_len != ED25519_KEY_LEN) { + goto fail; + } + + ed25519 = malloc(key_len); + if (ed25519 == NULL) { + SSH_LOG(SSH_LOG_WARN, "Out of memory"); + goto fail; + } + + evp_rc = EVP_PKEY_get_raw_private_key(pkey, (uint8_t *)ed25519, + &key_len); + if (evp_rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw private 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", + EVP_PKEY_base_id(pkey)); + return NULL; + } + EVP_PKEY_free(pkey); + + key = ssh_key_new(); + if (key == NULL) { + goto fail; + } + + 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->ed25519_privkey = ed25519; +#ifdef HAVE_OPENSSL_ECC + if (is_ecdsa_key_type(key->type)) { + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa); + } +#endif + + 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 + 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) +{ + int rc; + BIGNUM *bp, *bq, *bg, *bpub_key, *bpriv_key; + + key->dsa = DSA_new(); + if (key->dsa == NULL) { + return SSH_ERROR; + } + + 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) { + 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) { + 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) { + goto fail; + } + + 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; + } + + 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) { + goto fail; + } + + /* Memory management of bp, bq and bg is transferred to DSA object */ + rc = DSA_set0_pqg(key->dsa, bp, bq, bg); + if (rc == 0) { + goto fail; + } + + /* Memory management of npub_key is transferred to DSA object */ + rc = DSA_set0_key(key->dsa, bpub_key, NULL); + if (rc == 0) { + goto fail; + } + + return SSH_OK; +fail: + DSA_free(key->dsa); + return SSH_ERROR; +} + +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; + + key->rsa = RSA_new(); + if (key->rsa == NULL) { + return SSH_ERROR; + } + + 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) { + 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) { + goto fail; + } + + /* Memory management of bp and bq is transferred to RSA object */ + rc = RSA_set0_factors(key->rsa, bp, bq); + if (rc == 0) { + 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 */ + + return SSH_OK; +fail: + RSA_free(key->rsa); + return SSH_ERROR; +} + +int pki_pubkey_build_rsa(ssh_key key, + ssh_string e, + ssh_string n) { + int rc; + BIGNUM *be = NULL, *bn = NULL; + + key->rsa = RSA_new(); + if (key->rsa == NULL) { + return SSH_ERROR; + } + + be = ssh_make_string_bn(e); + bn = ssh_make_string_bn(n); + if (be == NULL || bn == NULL) { + goto fail; + } + + /* Memory management of bn and be is transferred to RSA object */ + rc = RSA_set0_key(key->rsa, bn, be, NULL); + if (rc == 0) { + goto fail; + } + + return SSH_OK; +fail: + RSA_free(key->rsa); + return SSH_ERROR; +} + +ssh_string pki_publickey_to_blob(const ssh_key key) +{ + ssh_buffer buffer; + ssh_string type_s; + ssh_string str = NULL; + ssh_string e = NULL; + ssh_string n = NULL; + ssh_string p = NULL; + ssh_string g = NULL; + ssh_string q = NULL; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + + if (key->cert != NULL) { + rc = ssh_buffer_add_buffer(buffer, key->cert); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + goto makestring; + } + + type_s = ssh_string_from_char(key->type_c); + 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; + } + + 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) { + goto fail; + } + + if (ssh_buffer_add_ssh_string(buffer, p) < 0) { + goto fail; + } + if (ssh_buffer_add_ssh_string(buffer, q) < 0) { + goto fail; + } + if (ssh_buffer_add_ssh_string(buffer, g) < 0) { + goto fail; + } + if (ssh_buffer_add_ssh_string(buffer, n) < 0) { + 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); + e = ssh_make_bignum_string((BIGNUM *)be); + if (e == NULL) { + goto fail; + } + + n = ssh_make_bignum_string((BIGNUM *)bn); + if (n == NULL) { + goto fail; + } + + if (ssh_buffer_add_ssh_string(buffer, e) < 0) { + goto fail; + } + if (ssh_buffer_add_ssh_string(buffer, n) < 0) { + goto fail; + } + + ssh_string_burn(e); + SSH_STRING_FREE(e); + e = NULL; + ssh_string_burn(n); + SSH_STRING_FREE(n); + n = NULL; + + break; + } + case SSH_KEYTYPE_ED25519: + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR){ + goto fail; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#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; + } + + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + 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; + } + + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(e); + SSH_STRING_FREE(e); + e = NULL; + + break; +#endif + case SSH_KEYTYPE_UNKNOWN: + default: + goto fail; + } + +makestring: + str = ssh_string_new(ssh_buffer_get_len(buffer)); + if (str == NULL) { + goto fail; + } + + rc = ssh_string_fill(str, ssh_buffer_get(buffer), ssh_buffer_get_len(buffer)); + if (rc < 0) { + goto fail; + } + SSH_BUFFER_FREE(buffer); + + return str; +fail: + SSH_BUFFER_FREE(buffer); + ssh_string_burn(str); + SSH_STRING_FREE(str); + ssh_string_burn(e); + SSH_STRING_FREE(e); + 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); + + 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; + ssh_string s = NULL; + + ssh_buffer buf = NULL; + ssh_string sig_blob = NULL; + + const BIGNUM *pr = NULL, *ps = NULL; + + const unsigned char *raw_sig_data = NULL; + size_t raw_sig_len; + + ECDSA_SIG *ecdsa_sig; + + int rc; + + 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); + + ecdsa_sig = d2i_ECDSA_SIG(NULL, &raw_sig_data, raw_sig_len); + if (ecdsa_sig == NULL) { + return NULL; + } + + ECDSA_SIG_get0(ecdsa_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; + } + + buf = ssh_buffer_new(); + if (buf == NULL) { + goto error; + } + + rc = ssh_buffer_add_ssh_string(buf, r); + if (rc < 0) { + goto error; + } + + rc = ssh_buffer_add_ssh_string(buf, s); + if (rc < 0) { + goto error; + } + + sig_blob = ssh_string_new(ssh_buffer_get_len(buf)); + if (sig_blob == NULL) { + goto error; + } + + ssh_string_fill(sig_blob, ssh_buffer_get(buf), ssh_buffer_get_len(buf)); + + SSH_STRING_FREE(r); + SSH_STRING_FREE(s); + ECDSA_SIG_free(ecdsa_sig); + SSH_BUFFER_FREE(buf); + + return sig_blob; + +error: + SSH_STRING_FREE(r); + SSH_STRING_FREE(s); + ECDSA_SIG_free(ecdsa_sig); + SSH_BUFFER_FREE(buf); + return NULL; +} + +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); + break; + case SSH_KEYTYPE_ED25519: + sig_blob = pki_ed25519_signature_to_blob(sig); + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_OPENSSL_ECC + sig_blob = pki_ecdsa_signature_to_blob(sig); + break; +#endif + default: + case SSH_KEYTYPE_UNKNOWN: + SSH_LOG(SSH_LOG_WARN, "Unknown signature key type: %s", sig->type_c); + return NULL; + } + + return sig_blob; +} + +static int pki_signature_from_rsa_blob(const ssh_key pubkey, + const ssh_string sig_blob, + ssh_signature sig) +{ + uint32_t pad_len = 0; + char *blob_orig = NULL; + char *blob_padded_data = NULL; + ssh_string sig_blob_padded = NULL; + + 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"); + goto errout; + } + + rsalen = RSA_size(pubkey->rsa); + if (len > rsalen) { + SSH_LOG(SSH_LOG_WARN, + "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_hexdump("RSA signature", ssh_string_data(sig_blob), len); +#endif + + if (len == rsalen) { + sig->raw_sig = ssh_string_copy(sig_blob); + } else { + /* pad the blob to the expected rsalen size */ + SSH_LOG(SSH_LOG_DEBUG, + "RSA signature len %lu < %lu", + (unsigned long)len, + (unsigned long)rsalen); + + pad_len = rsalen - len; + + sig_blob_padded = ssh_string_new(rsalen); + if (sig_blob_padded == NULL) { + goto errout; + } + + blob_padded_data = (char *) ssh_string_data(sig_blob_padded); + blob_orig = (char *) ssh_string_data(sig_blob); + + if (blob_padded_data == NULL || blob_orig == NULL) { + goto errout; + } + + /* front-pad the buffer with zeroes */ + explicit_bzero(blob_padded_data, pad_len); + /* fill the rest with the actual signature blob */ + memcpy(blob_padded_data + pad_len, blob_orig, len); + + sig->raw_sig = sig_blob_padded; + } + + return SSH_OK; + +errout: + SSH_STRING_FREE(sig_blob_padded); + 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; + + 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; + + raw_sig_len = i2d_DSA_SIG(dsa_sig, &raw_sig_data); + 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) +{ + ECDSA_SIG *ecdsa_sig = NULL; + BIGNUM *pr = NULL, *ps = NULL; + + ssh_string r; + ssh_string s; + + ssh_buffer buf = NULL; + uint32_t rlen; + + unsigned char *raw_sig_data = NULL; + size_t raw_sig_len = 0; + + int rc; + + /* build ecdsa signature */ + buf = ssh_buffer_new(); + if (buf == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_add_data(buf, + ssh_string_data(sig_blob), + ssh_string_len(sig_blob)); + if (rc < 0) { + goto error; + } + + r = ssh_buffer_get_ssh_string(buf); + if (r == NULL) { + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("r", ssh_string_data(r), ssh_string_len(r)); +#endif + + pr = ssh_make_string_bn(r); + ssh_string_burn(r); + SSH_STRING_FREE(r); + if (pr == NULL) { + goto error; + } + + s = ssh_buffer_get_ssh_string(buf); + rlen = ssh_buffer_get_len(buf); + SSH_BUFFER_FREE(buf); + if (s == NULL) { + goto error; + } + + if (rlen != 0) { + ssh_string_burn(s); + SSH_STRING_FREE(s); + SSH_LOG(SSH_LOG_WARN, + "Signature has remaining bytes in inner " + "sigblob: %lu", + (unsigned long)rlen); + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("s", ssh_string_data(s), ssh_string_len(s)); +#endif + + ps = ssh_make_string_bn(s); + ssh_string_burn(s); + SSH_STRING_FREE(s); + if (ps == NULL) { + goto error; + } + + ecdsa_sig = ECDSA_SIG_new(); + if (ecdsa_sig == NULL) { + goto error; + } + + /* Memory management of pr and ps is transferred to + * ECDSA signature object */ + rc = ECDSA_SIG_set0(ecdsa_sig, pr, ps); + if (rc == 0) { + goto error; + } + pr = NULL; + ps = NULL; + + rc = i2d_ECDSA_SIG(ecdsa_sig, &raw_sig_data); + if (rc < 0) { + goto error; + } + raw_sig_len = rc; + + 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); + ECDSA_SIG_free(ecdsa_sig); + return SSH_OK; + +error: + SSH_BUFFER_FREE(buf); + bignum_safe_free(ps); + bignum_safe_free(pr); + SAFE_FREE(raw_sig_data); + if (ecdsa_sig != NULL) { + ECDSA_SIG_free(ecdsa_sig); + } + return SSH_ERROR; +} + +ssh_signature pki_signature_from_blob(const ssh_key pubkey, + const ssh_string sig_blob, + enum ssh_keytypes_e type, + enum ssh_digest_e hash_type) +{ + ssh_signature sig; + int rc; + + if (ssh_key_type_plain(pubkey->type) != type) { + SSH_LOG(SSH_LOG_WARN, + "Incompatible public key provided (%d) expecting (%d)", + type, + pubkey->type); + return NULL; + } + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + + sig->type = type; + sig->type_c = ssh_key_signature_to_char(type, hash_type); + 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); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_KEYTYPE_ED25519: + rc = pki_signature_from_ed25519_blob(sig, sig_blob); + if (rc != SSH_OK){ + goto error; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: +#ifdef HAVE_OPENSSL_ECC + rc = pki_signature_from_ecdsa_blob(pubkey, sig_blob, sig); + if (rc != SSH_OK) { + goto error; + } + break; +#endif + default: + case SSH_KEYTYPE_UNKNOWN: + SSH_LOG(SSH_LOG_WARN, "Unknown signature type"); + goto error; + } + + return sig; + +error: + ssh_signature_free(sig); + return NULL; +} + +static const EVP_MD *pki_digest_to_md(enum ssh_digest_e hash_type) +{ + const EVP_MD *md = NULL; + + switch (hash_type) { + case SSH_DIGEST_SHA256: + md = EVP_sha256(); + break; + case SSH_DIGEST_SHA384: + md = EVP_sha384(); + break; + case SSH_DIGEST_SHA512: + md = EVP_sha512(); + break; + case SSH_DIGEST_SHA1: + md = EVP_sha1(); + break; + case SSH_DIGEST_AUTO: + md = NULL; + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown hash algorithm for type: %d", + hash_type); + return NULL; + } + + return md; +} + +static EVP_PKEY *pki_key_to_pkey(ssh_key key) +{ + EVP_PKEY *pkey = NULL; + + 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; + 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: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: +# if defined(HAVE_OPENSSL_ECC) + if (key->ecdsa == NULL) { + SSH_LOG(SSH_LOG_TRACE, "NULL key->ecdsa"); + goto error; + } + pkey = EVP_PKEY_new(); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + return NULL; + } + + EVP_PKEY_set1_EC_KEY(pkey, key->ecdsa); + break; +# endif + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_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"); + goto error; + } + /* 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). Both keys have the same length (32 bytes) */ + pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, + (const uint8_t *)key->ed25519_privkey, + ED25519_KEY_LEN); + } else { + if (key->ed25519_pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "NULL key->ed25519_pubkey"); + goto error; + } + pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, + (const uint8_t *)key->ed25519_pubkey, + ED25519_KEY_LEN); + } + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create ed25519 EVP_PKEY: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NULL; + } + break; +#endif + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown private key algorithm for type: %d", + key->type); + goto error; + } + + return pkey; + +error: + EVP_PKEY_free(pkey); + return NULL; +} + +/** + * @internal + * + * @brief Sign the given input data. The digest of to be signed is calculated + * internally as necessary. + * + * @param[in] privkey The private key to be used for signing. + * @param[in] hash_type The digest algorithm to be used. + * @param[in] input The data to be signed. + * @param[in] input_len The length of the data to be signed. + * + * @return a newly allocated ssh_signature or NULL on error. + */ +ssh_signature pki_sign_data(const ssh_key privkey, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len) +{ + const EVP_MD *md = NULL; + EVP_MD_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + + unsigned char *raw_sig_data = NULL; + size_t raw_sig_len; + + ssh_signature sig = NULL; + + int rc; + + if (privkey == NULL || !ssh_key_is_private(privkey) || input == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "pki_sign_data()"); + return NULL; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(privkey, hash_type); + if (rc != SSH_OK) { + 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) { + if (hash_type != SSH_DIGEST_AUTO) { + return NULL; + } + } + + /* Setup private key EVP_PKEY */ + pkey = pki_key_to_pkey(privkey); + if (pkey == NULL) { + return NULL; + } + + /* Allocate buffer for signature */ + raw_sig_len = (size_t)EVP_PKEY_size(pkey); + raw_sig_data = (unsigned char *)malloc(raw_sig_len); + if (raw_sig_data == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto out; + } + + /* Create the context */ + ctx = EVP_MD_CTX_create(); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto out; + } + + /* Sign the data */ + rc = EVP_DigestSignInit(ctx, NULL, md, NULL, pkey); + if (rc != 1){ + SSH_LOG(SSH_LOG_TRACE, + "EVP_DigestSignInit() failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + 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, + "EVP_DigestSign() failed: %s", + 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); +#endif + + /* Allocate and fill output signature */ + sig = ssh_signature_new(); + if (sig == NULL) { + goto out; + } + + sig->raw_sig = ssh_string_new(raw_sig_len); + if (sig->raw_sig == NULL) { + ssh_signature_free(sig); + sig = NULL; + goto out; + } + + rc = ssh_string_fill(sig->raw_sig, raw_sig_data, raw_sig_len); + if (rc < 0) { + ssh_signature_free(sig); + sig = NULL; + goto out; + } + + sig->type = privkey->type; + sig->hash_type = hash_type; + sig->type_c = ssh_key_signature_to_char(privkey->type, hash_type); + +out: + if (ctx != NULL) { + EVP_MD_CTX_free(ctx); + } + if (raw_sig_data != NULL) { + explicit_bzero(raw_sig_data, raw_sig_len); + } + SAFE_FREE(raw_sig_data); + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + return sig; +} + +/** + * @internal + * + * @brief Verify the signature of a given input. The digest of the input is + * calculated internally as necessary. + * + * @param[in] signature The signature to be verified. + * @param[in] pubkey The public key used to verify the signature. + * @param[in] input The signed data. + * @param[in] input_len The length of the signed data. + * + * @return SSH_OK if the signature is valid; SSH_ERROR otherwise. + */ +int pki_verify_data_signature(ssh_signature signature, + const ssh_key pubkey, + const unsigned char *input, + size_t input_len) +{ + const EVP_MD *md = NULL; + EVP_MD_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + + unsigned char *raw_sig_data = NULL; + unsigned int raw_sig_len; + + int rc = SSH_ERROR; + int evp_rc; + + 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 + )) + { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "pki_verify_data_signature()"); + return SSH_ERROR; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(pubkey, signature->hash_type); + if (rc != SSH_OK) { + return SSH_ERROR; + } + +#ifndef HAVE_OPENSSL_ED25519 + if (pubkey->type == SSH_KEYTYPE_ED25519 || + pubkey->type == SSH_KEYTYPE_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); + if (raw_sig_data == NULL) { + return SSH_ERROR; + } + + /* Set hash algorithm to be used */ + md = pki_digest_to_md(signature->hash_type); + if (md == NULL) { + if (signature->hash_type != SSH_DIGEST_AUTO) { + return SSH_ERROR; + } + } + + /* Setup public key EVP_PKEY */ + pkey = pki_key_to_pkey(pubkey); + if (pkey == NULL) { + return SSH_ERROR; + } + + /* Create the context */ + ctx = EVP_MD_CTX_create(); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create EVP_MD_CTX: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + /* Verify the signature */ + evp_rc = EVP_DigestVerifyInit(ctx, NULL, md, NULL, pkey); + if (evp_rc != 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) { + SSH_LOG(SSH_LOG_TRACE, + "EVP_DigestVerifyUpdate() failed: %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; + } + +out: + if (ctx != NULL) { + EVP_MD_CTX_free(ctx); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + return rc; +} + +#ifdef HAVE_OPENSSL_ED25519 +int pki_key_generate_ed25519(ssh_key key) +{ + int evp_rc; + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY *pkey = NULL; + size_t privkey_len = ED25519_KEY_LEN; + size_t pubkey_len = ED25519_KEY_LEN; + + if (key == NULL) { + return SSH_ERROR; + } + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL); + if (pctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create ed25519 EVP_PKEY_CTX: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + + evp_rc = EVP_PKEY_keygen_init(pctx); + if (evp_rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize ed25519 key generation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + + evp_rc = EVP_PKEY_keygen(pctx, &pkey); + if (evp_rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate ed25519 key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + + key->ed25519_privkey = malloc(ED25519_KEY_LEN); + if (key->ed25519_privkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to allocate memory for ed25519 private key"); + goto error; + } + + key->ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (key->ed25519_pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to allocate memory for ed25519 public key"); + goto error; + } + + evp_rc = EVP_PKEY_get_raw_private_key(pkey, (uint8_t *)key->ed25519_privkey, + &privkey_len); + if (evp_rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw private key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + + evp_rc = EVP_PKEY_get_raw_public_key(pkey, (uint8_t *)key->ed25519_pubkey, + &pubkey_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 error; + } + + EVP_PKEY_CTX_free(pctx); + EVP_PKEY_free(pkey); + return SSH_OK; + +error: + if (pctx != NULL) { + EVP_PKEY_CTX_free(pctx); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + SAFE_FREE(key->ed25519_privkey); + SAFE_FREE(key->ed25519_pubkey); + + 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 */ + +#endif /* _PKI_CRYPTO_H */ diff --git a/src/pki_ed25519.c b/src/pki_ed25519.c new file mode 100644 index 0000000..fdf94b4 --- /dev/null +++ b/src/pki_ed25519.c @@ -0,0 +1,150 @@ +/* + * pki_ed25519 .c - PKI infrastructure using ed25519 + * + * This file is part of the SSH Library + * + * Copyright (c) 2014 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/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/ed25519.h" +#include "libssh/buffer.h" + +int pki_key_generate_ed25519(ssh_key key) +{ + int rc; + + key->ed25519_privkey = malloc(sizeof (ed25519_privkey)); + if (key->ed25519_privkey == NULL) { + goto error; + } + + key->ed25519_pubkey = malloc(sizeof (ed25519_pubkey)); + if (key->ed25519_pubkey == NULL) { + goto error; + } + + rc = crypto_sign_ed25519_keypair(*key->ed25519_pubkey, + *key->ed25519_privkey); + if (rc != 0) { + goto error; + } + + return SSH_OK; +error: + SAFE_FREE(key->ed25519_privkey); + SAFE_FREE(key->ed25519_pubkey); + + return SSH_ERROR; +} + +int pki_ed25519_sign(const ssh_key privkey, + ssh_signature sig, + const unsigned char *hash, + size_t hlen) +{ + int rc; + uint8_t *buffer; + uint64_t dlen = 0; + + buffer = malloc(hlen + ED25519_SIG_LEN); + if (buffer == NULL) { + return SSH_ERROR; + } + + rc = crypto_sign_ed25519(buffer, + &dlen, + hash, + hlen, + *privkey->ed25519_privkey); + if (rc != 0) { + goto error; + } + + /* This shouldn't happen */ + if (dlen - hlen != ED25519_SIG_LEN) { + goto error; + } + + sig->ed25519_sig = malloc(ED25519_SIG_LEN); + if (sig->ed25519_sig == NULL) { + goto error; + } + + memcpy(sig->ed25519_sig, buffer, ED25519_SIG_LEN); + SAFE_FREE(buffer); + + return SSH_OK; +error: + SAFE_FREE(buffer); + return SSH_ERROR; +} + +int pki_ed25519_verify(const ssh_key pubkey, + ssh_signature sig, + const unsigned char *hash, + size_t hlen) +{ + uint64_t mlen = 0; + uint8_t *buffer; + uint8_t *buffer2; + int rc; + + if (pubkey == NULL || sig == NULL || + hash == NULL || sig->ed25519_sig == NULL) { + return SSH_ERROR; + } + + buffer = malloc(hlen + ED25519_SIG_LEN); + if (buffer == NULL) { + return SSH_ERROR; + } + + buffer2 = malloc(hlen + ED25519_SIG_LEN); + if (buffer2 == NULL) { + goto error; + } + + memcpy(buffer, sig->ed25519_sig, ED25519_SIG_LEN); + memcpy(buffer + ED25519_SIG_LEN, hash, hlen); + + rc = crypto_sign_ed25519_open(buffer2, + &mlen, + buffer, + hlen + ED25519_SIG_LEN, + *pubkey->ed25519_pubkey); + + explicit_bzero(buffer, hlen + ED25519_SIG_LEN); + explicit_bzero(buffer2, hlen); + SAFE_FREE(buffer); + SAFE_FREE(buffer2); + if (rc == 0) { + return SSH_OK; + } else { + return SSH_ERROR; + } +error: + SAFE_FREE(buffer); + SAFE_FREE(buffer2); + + return SSH_ERROR; +} + diff --git a/src/pki_ed25519_common.c b/src/pki_ed25519_common.c new file mode 100644 index 0000000..738825f --- /dev/null +++ b/src/pki_ed25519_common.c @@ -0,0 +1,280 @@ +/* + * pki_ed25519_common.c - Common ed25519 functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2014 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/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/buffer.h" + +int pki_privkey_build_ed25519(ssh_key key, + ssh_string pubkey, + ssh_string privkey) +{ + 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"); + return SSH_ERROR; + } + +#ifdef HAVE_OPENSSL_ED25519 + /* In OpenSSL implementation, the private key is the original private seed, + * without the public key. */ + 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. */ + key->ed25519_privkey = malloc(2 * ED25519_KEY_LEN); +#endif + if (key->ed25519_privkey == NULL) { + goto error; + } + + key->ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (key->ed25519_pubkey == NULL) { + goto error; + } + +#ifdef HAVE_OPENSSL_ED25519 + memcpy(key->ed25519_privkey, ssh_string_data(privkey), + ED25519_KEY_LEN); +#else + memcpy(key->ed25519_privkey, ssh_string_data(privkey), + 2 * ED25519_KEY_LEN); +#endif + memcpy(key->ed25519_pubkey, ssh_string_data(pubkey), + ED25519_KEY_LEN); + + return SSH_OK; + +error: + SAFE_FREE(key->ed25519_privkey); + SAFE_FREE(key->ed25519_pubkey); + + return SSH_ERROR; +} + +/** + * @internal + * + * @brief Compare ed25519 keys if they are equal. + * + * @param[in] k1 The first key to compare. + * + * @param[in] k2 The second key to compare. + * + * @param[in] what What part or type of the key do you want to compare. + * + * @return 0 if equal, 1 if not. + */ +int pki_ed25519_key_cmp(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what) +{ + int cmp; + + switch(what) { + case SSH_KEY_CMP_PRIVATE: + if (k1->ed25519_privkey == NULL || k2->ed25519_privkey == NULL) { + return 1; + } +#ifdef HAVE_OPENSSL_ED25519 + /* 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); +#else + /* In the internal implementation, the private key is the concatenation + * of the private seed with the public key. */ + cmp = memcmp(k1->ed25519_privkey, k2->ed25519_privkey, + 2 * ED25519_KEY_LEN); +#endif + if (cmp != 0) { + return 1; + } + /* FALL THROUGH */ + case SSH_KEY_CMP_PUBLIC: + if (k1->ed25519_pubkey == NULL || k2->ed25519_pubkey == NULL) { + return 1; + } + cmp = memcmp(k1->ed25519_pubkey, k2->ed25519_pubkey, ED25519_KEY_LEN); + if (cmp != 0) { + return 1; + } + } + + return 0; +} + +/** + * @internal + * + * @brief Duplicate an Ed25519 key + * + * @param[out] new Pre-initialized ssh_key structure + * + * @param[in] key Key to copy + * + * @return SSH_ERROR on error, SSH_OK on success + */ +int pki_ed25519_key_dup(ssh_key new, 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 + /* In OpenSSL implementation, the private key is the original private + * seed, without the public key. */ + new->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); +#endif + if (new->ed25519_privkey == NULL) { + return SSH_ERROR; + } +#ifdef HAVE_OPENSSL_ED25519 + memcpy(new->ed25519_privkey, key->ed25519_privkey, ED25519_KEY_LEN); +#else + memcpy(new->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); + return SSH_ERROR; + } + memcpy(new->ed25519_pubkey, key->ed25519_pubkey, ED25519_KEY_LEN); + } + + return SSH_OK; +} + +/** + * @internal + * + * @brief Outputs an Ed25519 public key in a blob buffer. + * + * @param[out] buffer Output buffer + * + * @param[in] key Key to output + * + * @return SSH_ERROR on error, SSH_OK on success + */ +int pki_ed25519_public_key_to_blob(ssh_buffer buffer, ssh_key key) +{ + int rc; + + if (key->ed25519_pubkey == NULL){ + return SSH_ERROR; + } + + rc = ssh_buffer_pack(buffer, + "dP", + (uint32_t)ED25519_KEY_LEN, + (size_t)ED25519_KEY_LEN, key->ed25519_pubkey); + + return rc; +} + +/** + * @internal + * + * @brief output a signature blob from an ed25519 signature + * + * @param[in] sig signature to convert + * + * @return Signature blob in SSH string, or NULL on error + */ +ssh_string pki_ed25519_signature_to_blob(ssh_signature sig) +{ + ssh_string sig_blob; + +#ifdef HAVE_OPENSSL_ED25519 + /* When using the OpenSSL implementation, the signature is stored in raw_sig + * which is shared by all algorithms.*/ + if (sig->raw_sig == NULL) { + return NULL; + } +#else + /* When using the internal implementation, the signature is stored in an + * algorithm specific field. */ + if (sig->ed25519_sig == NULL) { + return NULL; + } +#endif + + sig_blob = ssh_string_new(ED25519_SIG_LEN); + if (sig_blob == NULL) { + return NULL; + } + +#ifdef HAVE_OPENSSL_ED25519 + 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); +#endif + + return sig_blob; +} + +/** + * @internal + * + * @brief Convert a signature blob in an ed25519 signature. + * + * @param[out] sig a preinitialized signature + * + * @param[in] sig_blob a signature blob + * + * @return SSH_ERROR on error, SSH_OK on success + */ +int pki_signature_from_ed25519_blob(ssh_signature sig, ssh_string sig_blob) +{ + size_t len; + + len = ssh_string_len(sig_blob); + if (len != ED25519_SIG_LEN){ + SSH_LOG(SSH_LOG_WARN, "Invalid ssh-ed25519 signature len: %zu", len); + return SSH_ERROR; + } + +#ifdef HAVE_OPENSSL_ED25519 + sig->raw_sig = ssh_string_copy(sig_blob); +#else + sig->ed25519_sig = malloc(ED25519_SIG_LEN); + if (sig->ed25519_sig == NULL){ + return SSH_ERROR; + } + memcpy(sig->ed25519_sig, ssh_string_data(sig_blob), ED25519_SIG_LEN); +#endif + + return SSH_OK; +} + diff --git a/src/pki_gcrypt.c b/src/pki_gcrypt.c new file mode 100644 index 0000000..43d6089 --- /dev/null +++ b/src/pki_gcrypt.c @@ -0,0 +1,2462 @@ +/* + * pki_gcrypt.c private and public key handling using gcrypt. + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 Aris Adamantiadis + * Copyright (c) 2009-2011 Andreas Schneider + * 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" + +#ifdef HAVE_LIBGCRYPT + +#include +#include +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/wrapper.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" + +#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-----" + +#define MAX_KEY_SIZE 32 +#define MAX_PASSPHRASE_SIZE 1024 +#define ASN1_INTEGER 2 +#define ASN1_BIT_STRING 3 +#define ASN1_OCTET_STRING 4 +#define ASN1_OBJECT_IDENTIFIER 6 +#define ASN1_SEQUENCE 48 +#define PKCS5_SALT_LEN 8 + +static int load_iv(const char *header, unsigned char *iv, int iv_len) { + int i; + int j; + int k; + + memset(iv, 0, iv_len); + for (i = 0; i < iv_len; i++) { + if ((header[2*i] >= '0') && (header[2*i] <= '9')) + j = header[2*i] - '0'; + else if ((header[2*i] >= 'A') && (header[2*i] <= 'F')) + j = header[2*i] - 'A' + 10; + else if ((header[2*i] >= 'a') && (header[2*i] <= 'f')) + j = header[2*i] - 'a' + 10; + else + return -1; + if ((header[2*i+1] >= '0') && (header[2*i+1] <= '9')) + k = header[2*i+1] - '0'; + else if ((header[2*i+1] >= 'A') && (header[2*i+1] <= 'F')) + k = header[2*i+1] - 'A' + 10; + else if ((header[2*i+1] >= 'a') && (header[2*i+1] <= 'f')) + k = header[2*i+1] - 'a' + 10; + else + return -1; + iv[i] = (j << 4) + k; + } + return 0; +} + +static uint32_t char_to_u32(unsigned char *data, uint32_t size) { + uint32_t ret; + uint32_t i; + + for (i = 0, ret = 0; i < size; ret = ret << 8, ret += data[i++]) + ; + return ret; +} + +static uint32_t asn1_get_len(ssh_buffer buffer) { + uint32_t len; + unsigned char tmp[4]; + + if (ssh_buffer_get_data(buffer,tmp,1) == 0) { + return 0; + } + + if (tmp[0] > 127) { + len = tmp[0] & 127; + if (len > 4) { + return 0; /* Length doesn't fit in u32. Can this really happen? */ + } + if (ssh_buffer_get_data(buffer,tmp,len) == 0) { + return 0; + } + len = char_to_u32(tmp, len); + } else { + len = char_to_u32(tmp, 1); + } + + return len; +} + +static ssh_string asn1_get(ssh_buffer buffer, unsigned char want) { + ssh_string str; + unsigned char type; + uint32_t size; + + if (ssh_buffer_get_data(buffer, &type, 1) == 0 || type != want) { + return NULL; + } + size = asn1_get_len(buffer); + if (size == 0) { + return NULL; + } + + str = ssh_string_new(size); + if (str == NULL) { + return NULL; + } + + if (ssh_buffer_get_data(buffer, ssh_string_data(str), size) == 0) { + SSH_STRING_FREE(str); + return NULL; + } + + return str; +} + +static ssh_string asn1_get_int(ssh_buffer buffer) { + return asn1_get(buffer, ASN1_INTEGER); +} + +static ssh_string asn1_get_bit_string(ssh_buffer buffer) +{ + ssh_string str; + unsigned char type; + uint32_t size; + unsigned char unused, last, *p; + uint32_t len; + + len = ssh_buffer_get_data(buffer, &type, 1); + if (len == 0 || type != ASN1_BIT_STRING) { + return NULL; + } + size = asn1_get_len(buffer); + if (size == 0) { + return NULL; + } + + /* The first octet encodes the number of unused bits. */ + size -= 1; + + str = ssh_string_new(size); + if (str == NULL) { + return NULL; + } + + len = ssh_buffer_get_data(buffer, &unused, 1); + if (len == 0) { + SSH_STRING_FREE(str); + return NULL; + } + + if (unused == 0) { + len = ssh_buffer_get_data(buffer, ssh_string_data(str), size); + if (len == 0) { + SSH_STRING_FREE(str); + return NULL; + } + return str; + } + + /* The bit string is padded at the end, we must shift the whole + string by UNUSED bits. */ + for (p = ssh_string_data(str), last = 0; size; size--, p++) { + unsigned char c; + + len = ssh_buffer_get_data(buffer, &c, 1); + if (len == 0) { + SSH_STRING_FREE(str); + return NULL; + } + *p = last | (c >> unused); + last = c << (8 - unused); + } + + return str; +} + +static int asn1_check_sequence(ssh_buffer buffer) { + unsigned char *j = NULL; + unsigned char tmp; + int i; + uint32_t size; + uint32_t padding; + + if (ssh_buffer_get_data(buffer, &tmp, 1) == 0 || tmp != ASN1_SEQUENCE) { + return 0; + } + + size = asn1_get_len(buffer); + if ((padding = ssh_buffer_get_len(buffer) - size) > 0) { + for (i = ssh_buffer_get_len(buffer) - size, + j = (unsigned char*)ssh_buffer_get(buffer) + size; + i; + i--, j++) + { + if (*j != padding) { /* padding is allowed */ + return 0; /* but nothing else */ + } + } + } + + return 1; +} + +static int asn1_check_tag(ssh_buffer buffer, unsigned char tag) { + unsigned char tmp; + uint32_t len; + + len = ssh_buffer_get_data(buffer, &tmp, 1); + if (len == 0 || tmp != tag) { + return 0; + } + + (void) asn1_get_len(buffer); + return 1; +} + +static int passphrase_to_key(char *data, unsigned int datalen, + unsigned char *salt, unsigned char *key, unsigned int keylen) { + MD5CTX md; + unsigned char digest[MD5_DIGEST_LEN] = {0}; + unsigned int i; + unsigned int j; + unsigned int md_not_empty; + + for (j = 0, md_not_empty = 0; j < keylen; ) { + md = md5_init(); + if (md == NULL) { + return -1; + } + + if (md_not_empty) { + md5_update(md, digest, MD5_DIGEST_LEN); + } else { + md_not_empty = 1; + } + + md5_update(md, data, datalen); + if (salt) { + md5_update(md, salt, PKCS5_SALT_LEN); + } + md5_final(digest, md); + + for (i = 0; j < keylen && i < MD5_DIGEST_LEN; j++, i++) { + if (key) { + key[j] = digest[i]; + } + } + } + + return 0; +} + +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, + void *userdata, + const char *desc) +{ + char passphrase[MAX_PASSPHRASE_SIZE] = {0}; + unsigned char key[MAX_KEY_SIZE] = {0}; + unsigned char *tmp = NULL; + gcry_cipher_hd_t cipher; + int rc = -1; + + if (!algo) { + return -1; + } + + if (cb) { + rc = (*cb)(desc, passphrase, MAX_PASSPHRASE_SIZE, 0, 0, userdata); + if (rc < 0) { + return -1; + } + } else if (cb == NULL && userdata != NULL) { + snprintf(passphrase, MAX_PASSPHRASE_SIZE, "%s", (char *) userdata); + } + + if (passphrase_to_key(passphrase, strlen(passphrase), iv, key, key_len) < 0) { + return -1; + } + + if (gcry_cipher_open(&cipher, algo, mode, 0) + || gcry_cipher_setkey(cipher, key, key_len) + || gcry_cipher_setiv(cipher, iv, iv_len) + || (tmp = calloc(ssh_buffer_get_len(data), sizeof(unsigned char))) == NULL + || gcry_cipher_decrypt(cipher, tmp, ssh_buffer_get_len(data), + ssh_buffer_get(data), ssh_buffer_get_len(data))) { + gcry_cipher_close(cipher); + return -1; + } + + memcpy(ssh_buffer_get(data), tmp, ssh_buffer_get_len(data)); + + SAFE_FREE(tmp); + gcry_cipher_close(cipher); + + return 0; +} + +static int privatekey_dek_header(const char *header, unsigned int header_len, + int *algo, int *mode, unsigned int *key_len, unsigned char **iv, + unsigned int *iv_len) { + unsigned int iv_pos; + + if (header_len > 13 && !strncmp("DES-EDE3-CBC", header, 12)) + { + *algo = GCRY_CIPHER_3DES; + iv_pos = 13; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 24; + *iv_len = 8; + } + else if (header_len > 8 && !strncmp("DES-CBC", header, 7)) + { + *algo = GCRY_CIPHER_DES; + iv_pos = 8; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 8; + *iv_len = 8; + } + else if (header_len > 12 && !strncmp("AES-128-CBC", header, 11)) + { + *algo = GCRY_CIPHER_AES128; + iv_pos = 12; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 16; + *iv_len = 16; + } + else if (header_len > 12 && !strncmp("AES-192-CBC", header, 11)) + { + *algo = GCRY_CIPHER_AES192; + iv_pos = 12; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 24; + *iv_len = 16; + } + else if (header_len > 12 && !strncmp("AES-256-CBC", header, 11)) + { + *algo = GCRY_CIPHER_AES256; + iv_pos = 12; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 32; + *iv_len = 16; + } else { + return -1; + } + + *iv = malloc(*iv_len); + if (*iv == NULL) { + return -1; + } + + return load_iv(header + iv_pos, *iv, *iv_len); +} + +#define get_next_line(p, len) { \ + while(p[len] == '\n' || p[len] == '\r') /* skip empty lines */ \ + len++; \ + if(p[len] == '\0') /* EOL */ \ + eol = true; \ + else /* calculate length */ \ + for(p += len, len = 0; p[len] && p[len] != '\n' \ + && p[len] != '\r'; len++); \ + } + +static ssh_buffer privatekey_string_to_buffer(const char *pkey, int type, + ssh_auth_callback cb, void *userdata, const char *desc) { + ssh_buffer buffer = NULL; + ssh_buffer out = NULL; + const char *p; + unsigned char *iv = NULL; + const char *header_begin; + const char *header_end; + unsigned int header_begin_size; + unsigned int header_end_size; + unsigned int key_len = 0; + unsigned int iv_len = 0; + int algo = 0; + int mode = 0; + bool eol = false; + size_t len; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + + 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; + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + header_begin = ECDSA_HEADER_BEGIN; + header_end = ECDSA_HEADER_END; + break; + default: + SSH_BUFFER_FREE(buffer); + return NULL; + } + + header_begin_size = strlen(header_begin); + header_end_size = strlen(header_end); + + p = pkey; + len = 0; + get_next_line(p, len); + + while(!eol && strncmp(p, header_begin, header_begin_size)) { + /* skip line */ + get_next_line(p, len); + } + if (eol) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + /* skip header line */ + get_next_line(p, len); + if (eol) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + if (len > 11 && strncmp("Proc-Type: 4,ENCRYPTED", p, 11) == 0) { + /* skip line */ + get_next_line(p, len); + if (eol) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + if (len > 10 && strncmp("DEK-Info: ", p, 10) == 0) { + p += 10; + len = 0; + get_next_line(p, len); + if (eol) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + if (privatekey_dek_header(p, len, &algo, &mode, &key_len, + &iv, &iv_len) < 0) { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + } else { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + } else { + if(len > 0) { + if (ssh_buffer_add_data(buffer, p, len) < 0) { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + } + } + + get_next_line(p, len); + while(!eol && strncmp(p, header_end, header_end_size) != 0) { + if (ssh_buffer_add_data(buffer, p, len) < 0) { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + get_next_line(p, len); + } + + if (eol || strncmp(p, header_end, header_end_size) != 0) { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + + if (ssh_buffer_add_data(buffer, "\0", 1) < 0) { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + + out = base64_to_bin(ssh_buffer_get(buffer)); + SSH_BUFFER_FREE(buffer); + if (out == NULL) { + SAFE_FREE(iv); + return NULL; + } + + if (algo) { + if (privatekey_decrypt(algo, mode, key_len, iv, iv_len, out, + cb, userdata, desc) < 0) { + SSH_BUFFER_FREE(out); + SAFE_FREE(iv); + return NULL; + } + } + SAFE_FREE(iv); + + return out; +} + +static int b64decode_rsa_privatekey(const char *pkey, gcry_sexp_t *r, + ssh_auth_callback cb, void *userdata, const char *desc) { + const unsigned char *data; + ssh_string n = NULL; + ssh_string e = NULL; + ssh_string d = NULL; + ssh_string p = NULL; + ssh_string q = NULL; + ssh_string unused1 = NULL; + ssh_string unused2 = NULL; + ssh_string u = NULL; + ssh_string v = NULL; + ssh_buffer buffer = NULL; + int rc = 1; + + buffer = privatekey_string_to_buffer(pkey, SSH_KEYTYPE_RSA, 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; + } + + n = asn1_get_int(buffer); + e = asn1_get_int(buffer); + d = asn1_get_int(buffer); + q = asn1_get_int(buffer); + p = asn1_get_int(buffer); + unused1 = asn1_get_int(buffer); + unused2 = asn1_get_int(buffer); + u = asn1_get_int(buffer); + + SSH_BUFFER_FREE(buffer); + + if (n == NULL || e == NULL || d == NULL || p == NULL || q == NULL || + unused1 == NULL || unused2 == NULL|| u == NULL) { + rc = 0; + goto error; + } + + if (gcry_sexp_build(r, NULL, + "(private-key(rsa(n %b)(e %b)(d %b)(p %b)(q %b)(u %b)))", + ssh_string_len(n), ssh_string_data(n), + ssh_string_len(e), ssh_string_data(e), + ssh_string_len(d), ssh_string_data(d), + ssh_string_len(p), ssh_string_data(p), + ssh_string_len(q), ssh_string_data(q), + ssh_string_len(u), ssh_string_data(u))) { + rc = 0; + } + +error: + ssh_string_burn(n); + SSH_STRING_FREE(n); + ssh_string_burn(e); + SSH_STRING_FREE(e); + 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_FREE(unused1); + SSH_STRING_FREE(unused2); + ssh_string_burn(u); + SSH_STRING_FREE(u); + SSH_STRING_FREE(v); + + 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) +{ + gcry_sexp_t sexp; + const char *tmp; + size_t size; + + sexp = gcry_sexp_find_token(k, "curve", 0); + if (sexp == NULL) { + return -1; + } + + tmp = gcry_sexp_nth_data(sexp, 1, &size); + + if (size == 10) { + int cmp; + + cmp = memcmp("NIST P-256", tmp, size); + if (cmp == 0) { + gcry_sexp_release(sexp); + return NID_gcrypt_nistp256; + } + + cmp = memcmp("NIST P-384", tmp, size); + if (cmp == 0) { + gcry_sexp_release(sexp); + return NID_gcrypt_nistp384; + } + + cmp = memcmp("NIST P-521", tmp, size); + if (cmp == 0) { + gcry_sexp_release(sexp); + return NID_gcrypt_nistp521; + } + } + + gcry_sexp_release(sexp); + return -1; +} + +static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(gcry_sexp_t k) +{ + int nid; + + nid = pki_key_ecdsa_to_nid(k); + + switch (nid) { + case NID_gcrypt_nistp256: + return SSH_KEYTYPE_ECDSA_P256; + case NID_gcrypt_nistp384: + return SSH_KEYTYPE_ECDSA_P384; + case NID_gcrypt_nistp521: + return SSH_KEYTYPE_ECDSA_P521; + default: + return SSH_KEYTYPE_UNKNOWN; + } +} + +static const char *pki_key_ecdsa_nid_to_gcrypt_name(int nid) +{ + switch (nid) { + case NID_gcrypt_nistp256: + return "NIST P-256"; + case NID_gcrypt_nistp384: + return "NIST P-384"; + case NID_gcrypt_nistp521: + return "NIST P-521"; + } + + return "unknown"; +} + + +const char *pki_key_ecdsa_nid_to_name(int nid) +{ + switch (nid) { + case NID_gcrypt_nistp256: + return "ecdsa-sha2-nistp256"; + case NID_gcrypt_nistp384: + return "ecdsa-sha2-nistp384"; + case NID_gcrypt_nistp521: + return "ecdsa-sha2-nistp521"; + } + + return "unknown"; +} + +static const char *pki_key_ecdsa_nid_to_char(int nid) +{ + switch (nid) { + case NID_gcrypt_nistp256: + return "nistp256"; + case NID_gcrypt_nistp384: + return "nistp384"; + case NID_gcrypt_nistp521: + return "nistp521"; + default: + break; + } + + return "unknown"; +} + +int pki_key_ecdsa_nid_from_name(const char *name) +{ + int cmp; + + cmp = strcmp(name, "nistp256"); + if (cmp == 0) { + return NID_gcrypt_nistp256; + } + + cmp = strcmp(name, "nistp384"); + if (cmp == 0) { + return NID_gcrypt_nistp384; + } + + cmp = strcmp(name, "nistp521"); + if (cmp == 0) { + return NID_gcrypt_nistp521; + } + + return -1; +} + +static int asn1_oi_to_nid(const ssh_string oi) +{ + static const struct { + int nid; + size_t length; + const char *identifier; + } *e, mapping[] = { + {NID_gcrypt_nistp256, 8, "\x2a\x86\x48\xce\x3d\x03\x01\x07"}, + {NID_gcrypt_nistp384, 5, "\x2b\x81\x04\x00\x22"}, + {NID_gcrypt_nistp521, 5, "\x2b\x81\x04\x00\x23"}, + {0}, + }; + size_t len = ssh_string_len(oi); + for (e = mapping; e->length; e++) { + if (len == e->length + && memcmp(ssh_string_data(oi), e->identifier, len) == 0) { + return e->nid; + } + } + return -1; +} + +static int b64decode_ecdsa_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; + gcry_error_t err = 0; + ssh_string v = NULL; + ssh_string d = NULL; + ssh_string oi = NULL; + int nid; + ssh_string q = NULL; + int valid = 0; + int ok; + + buffer = privatekey_string_to_buffer(pkey, + SSH_KEYTYPE_ECDSA_P256, + cb, + userdata, + desc); + if (buffer == NULL) { + goto error; + } + + ok = asn1_check_sequence(buffer); + if (!ok) { + goto error; + } + + /* RFC5915 specifies version 1. */ + v = asn1_get_int(buffer); + if (v == NULL) { + goto error; + } + + data = ssh_string_data(v); + if (ssh_string_len(v) != 1 || data[0] != 1) { + goto error; + } + + d = asn1_get(buffer, ASN1_OCTET_STRING); + if (!asn1_check_tag(buffer, 0xa0)) { + goto error; + } + oi = asn1_get(buffer, ASN1_OBJECT_IDENTIFIER); + nid = asn1_oi_to_nid(oi); + ok = asn1_check_tag(buffer, 0xa1); + if (!ok) { + goto error; + } + q = asn1_get_bit_string(buffer); + + if (d == NULL || oi == NULL || nid == -1 || q == NULL) { + goto error; + } + + err = gcry_sexp_build(r, + NULL, + "(private-key(ecdsa(curve %s)(d %b)(q %b)))", + pki_key_ecdsa_nid_to_gcrypt_name(nid), + ssh_string_len(d), + ssh_string_data(d), + ssh_string_len(q), + ssh_string_data(q)); + if (err == 0) { + valid = 1; + } + + error: + SSH_BUFFER_FREE(buffer); + SSH_STRING_FREE(v); + ssh_string_burn(d); + SSH_STRING_FREE(d); + SSH_STRING_FREE(oi); + ssh_string_burn(q); + SSH_STRING_FREE(q); + + return valid; +} +#endif + +ssh_string pki_private_key_to_pem(const ssh_key key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + (void) key; + (void) passphrase; + (void) auth_fn; + (void) auth_data; + + SSH_LOG(SSH_LOG_WARN, "PEM export not supported by gcrypt backend!"); + + return NULL; +} + +ssh_key pki_private_key_from_base64(const char *b64_key, + const char *passphrase, + 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; + enum ssh_keytypes_e type; + int valid; + + 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; + } + + 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) { + valid = b64decode_rsa_privatekey(b64_key, &rsa, auth_fn, + auth_data, "Passphrase for private key:"); + } else { + valid = b64decode_rsa_privatekey(b64_key, &rsa, NULL, NULL, + NULL); + } + } else { + valid = b64decode_rsa_privatekey(b64_key, &rsa, NULL, + (void *)passphrase, NULL); + } + + if (!valid) { + SSH_LOG(SSH_LOG_WARN, "Parsing private key"); + goto fail; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#if HAVE_GCRYPT_ECC + if (passphrase == NULL) { + if (auth_fn != NULL) { + valid = b64decode_ecdsa_privatekey(b64_key, + &ecdsa, + auth_fn, + auth_data, + "Passphrase for private key:"); + } else { + valid = b64decode_ecdsa_privatekey(b64_key, + &ecdsa, + NULL, + NULL, + NULL); + } + } else { + valid = b64decode_ecdsa_privatekey(b64_key, + &ecdsa, + NULL, + (void *)passphrase, + NULL); + } + + if (!valid) { + SSH_LOG(SSH_LOG_WARN, "Parsing private key"); + goto fail; + } + + /* 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); + if (type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_WARN, "Invalid private key."); + goto fail; + } + break; +#endif + case SSH_KEYTYPE_ED25519: + /* Cannot open ed25519 keys with libgcrypt */ + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_WARN, "Unknown or invalid private key type %d", type); + return NULL; + } + + key = ssh_key_new(); + if (key == NULL) { + goto fail; + } + + 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 + if (is_ecdsa_key_type(key->type)) { + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa); + } +#endif + + 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, + ssh_string d, + ssh_string iqmp, + ssh_string p, + ssh_string q) +{ + /* in gcrypt, there is no iqmp (inverse of q mod p) argument, + * but it is ipmq (inverse of p mod q) so we need to swap + * the p and q arguments */ + gcry_sexp_build(&key->rsa, NULL, + "(private-key(rsa(n %b)(e %b)(d %b)(p %b)(q %b)(u %b)))", + ssh_string_len(n), ssh_string_data(n), + ssh_string_len(e), ssh_string_data(e), + ssh_string_len(d), ssh_string_data(d), + ssh_string_len(q), ssh_string_data(q), + ssh_string_len(p), ssh_string_data(p), + ssh_string_len(iqmp), ssh_string_data(iqmp)); + if (key->rsa == NULL) { + return SSH_ERROR; + } + + return SSH_OK; +} + +int pki_pubkey_build_rsa(ssh_key key, + ssh_string e, + ssh_string n) { + gcry_sexp_build(&key->rsa, NULL, + "(public-key(rsa(n %b)(e %b)))", + ssh_string_len(n), ssh_string_data(n), + ssh_string_len(e),ssh_string_data(e)); + if (key->rsa == NULL) { + return SSH_ERROR; + } + + return SSH_OK; +} + +#ifdef HAVE_GCRYPT_ECC +int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) +{ + gpg_error_t err; + + key->ecdsa_nid = nid; + key->type_c = pki_key_ecdsa_nid_to_name(nid); + + err = gcry_sexp_build(&key->ecdsa, NULL, + "(private-key(ecdsa(curve %s)(d %b)(q %b)))", + pki_key_ecdsa_nid_to_gcrypt_name(nid), + ssh_string_len(exp), ssh_string_data(exp), + ssh_string_len(e), ssh_string_data(e)); + if (err) { + return SSH_ERROR; + } + + return SSH_OK; +} + +int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) +{ + gpg_error_t err; + + key->ecdsa_nid = nid; + key->type_c = pki_key_ecdsa_nid_to_name(nid); + + err = gcry_sexp_build(&key->ecdsa, NULL, + "(public-key(ecdsa(curve %s)(q %b)))", + pki_key_ecdsa_nid_to_gcrypt_name(nid), + ssh_string_len(e), ssh_string_data(e)); + if (err) { + return SSH_ERROR; + } + + return SSH_OK; +} +#endif + +ssh_key pki_key_dup(const ssh_key key, int demote) +{ + ssh_key new; + gcry_error_t err = 0; + int rc; + + gcry_mpi_t p = NULL; + gcry_mpi_t q = NULL; + gcry_mpi_t g = NULL; + gcry_mpi_t y = NULL; + gcry_mpi_t x = NULL; + + gcry_mpi_t e = NULL; + gcry_mpi_t n = NULL; + gcry_mpi_t d = NULL; + gcry_mpi_t u = NULL; + + gcry_sexp_t curve = NULL; + + new = ssh_key_new(); + if (new == NULL) { + return NULL; + } + new->type = key->type; + new->type_c = key->type_c; + if (demote) { + new->flags = SSH_KEY_FLAG_PUBLIC; + } else { + new->flags = key->flags; + } + + 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, + "ned?p?q?u?", + &n, + &e, + &d, + &p, + &q, + &u, + NULL); + if (err != 0) { + break; + } + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { + err = gcry_sexp_build(&new->rsa, + NULL, + "(private-key(rsa(n %m)(e %m)(d %m)(p %m)(q %m)(u %m)))", + n, e, d, p, q, u); + } else { + err = gcry_sexp_build(&new->rsa, + NULL, + "(public-key(rsa(n %m)(e %m)))", + n, e); + } + break; + case SSH_KEYTYPE_ED25519: + rc = pki_ed25519_key_dup(new, key); + if (rc != SSH_OK) { + ssh_key_free(new); + return NULL; + } + break; + + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_GCRYPT_ECC + new->ecdsa_nid = key->ecdsa_nid; + + err = gcry_sexp_extract_param(key->ecdsa, + NULL, + "qd?", + &q, + &d, + NULL); + if (err) { + break; + } + + curve = gcry_sexp_find_token(key->ecdsa, "curve", 0); + if (curve == NULL) { + break; + } + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { + err = gcry_sexp_build(&new->ecdsa, + NULL, + "(private-key(ecdsa %S (d %m)(q %m)))", + curve, + d, + q); + } else { + err = gcry_sexp_build(&new->ecdsa, + NULL, + "(private-key(ecdsa %S (q %m)))", + curve, + q); + } + break; +#endif + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + ssh_key_free(new); + return NULL; + } + + if (err) { + ssh_key_free(new); + new = NULL; + } + + gcry_mpi_release(p); + gcry_mpi_release(q); + gcry_mpi_release(g); + gcry_mpi_release(y); + gcry_mpi_release(x); + + gcry_mpi_release(e); + gcry_mpi_release(n); + gcry_mpi_release(d); + gcry_mpi_release(u); + + gcry_sexp_release(curve); + + return new; +} + +static int pki_key_generate(ssh_key key, int parameter, const char *type_s, int type){ + gcry_sexp_t parms; + int rc; + rc = gcry_sexp_build(&parms, + NULL, + "(genkey(%s(nbits %d)(transient-key)))", + type_s, + parameter); + if (rc != 0) + 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); + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + rc = gcry_pk_genkey(&key->ecdsa, parms); + break; + default: + assert (! "reached"); + } + gcry_sexp_release(parms); + if (rc != 0) + return SSH_ERROR; + return SSH_OK; +} + +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) { + switch (parameter) { + case 384: + key->ecdsa_nid = NID_gcrypt_nistp384; + key->type = SSH_KEYTYPE_ECDSA_P384; + return pki_key_generate(key, parameter, "ecdsa", + SSH_KEYTYPE_ECDSA_P384); + case 521: + key->ecdsa_nid = NID_gcrypt_nistp521; + key->type = SSH_KEYTYPE_ECDSA_P521; + return pki_key_generate(key, parameter, "ecdsa", + SSH_KEYTYPE_ECDSA_P521); + case 256: + default: + key->ecdsa_nid = NID_gcrypt_nistp256; + key->type = SSH_KEYTYPE_ECDSA_P256; + return pki_key_generate(key, parameter, "ecdsa", + SSH_KEYTYPE_ECDSA_P256); + } +} +#endif + +static int _bignum_cmp(const gcry_sexp_t s1, + const gcry_sexp_t s2, + const char *what) +{ + gcry_sexp_t sexp; + bignum b1; + bignum b2; + int result; + + sexp = gcry_sexp_find_token(s1, what, 0); + if (sexp == NULL) { + return 1; + } + b1 = gcry_sexp_nth_mpi(sexp, 1, GCRYMPI_FMT_USG); + gcry_sexp_release(sexp); + if (b1 == NULL) { + return 1; + } + + sexp = gcry_sexp_find_token(s2, what, 0); + if (sexp == NULL) { + bignum_safe_free(b1); + return 1; + } + b2 = gcry_sexp_nth_mpi(sexp, 1, GCRYMPI_FMT_USG); + gcry_sexp_release(sexp); + if (b2 == NULL) { + bignum_safe_free(b1); + return 1; + } + + result = !! bignum_cmp(b1, b2); + bignum_safe_free(b1); + bignum_safe_free(b2); + return result; +} + +int pki_key_compare(const ssh_key k1, + const ssh_key k2, + 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: + if (_bignum_cmp(k1->rsa, k2->rsa, "e") != 0) { + return 1; + } + + if (_bignum_cmp(k1->rsa, k2->rsa, "n") != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (_bignum_cmp(k1->rsa, k2->rsa, "d") != 0) { + return 1; + } + + if (_bignum_cmp(k1->rsa, k2->rsa, "p") != 0) { + return 1; + } + + if (_bignum_cmp(k1->rsa, k2->rsa, "q") != 0) { + return 1; + } + + if (_bignum_cmp(k1->rsa, k2->rsa, "u") != 0) { + return 1; + } + } + break; + case SSH_KEYTYPE_ED25519: + /* ed25519 keys handled globaly */ + return 0; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_GCRYPT_ECC + if (k1->ecdsa_nid != k2->ecdsa_nid) { + return 1; + } + + if (_bignum_cmp(k1->ecdsa, k2->ecdsa, "q") != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (_bignum_cmp(k1->ecdsa, k2->ecdsa, "d") != 0) { + return 1; + } + } + 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_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + return 1; + } + + return 0; +} + +ssh_string pki_publickey_to_blob(const ssh_key key) +{ + ssh_buffer buffer; + ssh_string type_s; + ssh_string str = NULL; + ssh_string e = NULL; + ssh_string n = NULL; + ssh_string p = NULL; + ssh_string g = NULL; + ssh_string q = NULL; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + + if (key->cert != NULL) { + rc = ssh_buffer_add_buffer(buffer, key->cert); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + goto makestring; + } + + type_s = ssh_string_from_char(key->type_c); + 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; + } + + 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", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (e == NULL) { + goto fail; + } + + n = ssh_sexp_extract_mpi(key->rsa, + "n", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (n == NULL) { + 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; + } + + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(n); + SSH_STRING_FREE(n); + + break; + case SSH_KEYTYPE_ED25519: + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc != SSH_OK){ + goto fail; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_GCRYPT_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; + } + + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + e = ssh_sexp_extract_mpi(key->ecdsa, "q", GCRYMPI_FMT_STD, + GCRYMPI_FMT_STD); + if (e == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(e); + SSH_STRING_FREE(e); + e = NULL; + break; +#endif + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + goto fail; + } + +makestring: + str = ssh_string_new(ssh_buffer_get_len(buffer)); + if (str == NULL) { + goto fail; + } + + rc = ssh_string_fill(str, ssh_buffer_get(buffer), ssh_buffer_get_len(buffer)); + if (rc < 0) { + goto fail; + } + SSH_BUFFER_FREE(buffer); + + return str; +fail: + SSH_BUFFER_FREE(buffer); + ssh_string_burn(str); + SSH_STRING_FREE(str); + ssh_string_burn(e); + SSH_STRING_FREE(e); + 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); + + return NULL; +} + +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; + + gcry_sexp_t sexp; + size_t size = 0; + ssh_string sig_blob = NULL; + + 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) { + return NULL; + } + s = gcry_sexp_nth_data(sexp, 1, &size); + if (*s == 0) { + size--; + s++; + } + + sig_blob = ssh_string_new(size); + if (sig_blob == NULL) { + return NULL; + } + ssh_string_fill(sig_blob, discard_const_p(char, s), size); + + gcry_sexp_release(sexp); + break; + case SSH_KEYTYPE_ED25519: + sig_blob = pki_ed25519_signature_to_blob(sig); + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_GCRYPT_ECC + { + ssh_string R; + ssh_string S; + ssh_buffer b; + int rc; + + b = ssh_buffer_new(); + if (b == NULL) { + return NULL; + } + + R = ssh_sexp_extract_mpi(sig->ecdsa_sig, "r", + GCRYMPI_FMT_USG, GCRYMPI_FMT_STD); + if (R == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(b, R); + SSH_STRING_FREE(R); + if (rc < 0) { + SSH_BUFFER_FREE(b); + return NULL; + } + + S = ssh_sexp_extract_mpi(sig->ecdsa_sig, "s", + GCRYMPI_FMT_USG, GCRYMPI_FMT_STD); + if (S == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(b, S); + SSH_STRING_FREE(S); + if (rc < 0) { + SSH_BUFFER_FREE(b); + return NULL; + } + + sig_blob = ssh_string_new(ssh_buffer_get_len(b)); + if (sig_blob == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + ssh_string_fill(sig_blob, + ssh_buffer_get(b), ssh_buffer_get_len(b)); + SSH_BUFFER_FREE(b); + break; + } +#endif + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_WARN, "Unknown signature key type: %d", sig->type); + return NULL; + break; + } + + return sig_blob; +} + +ssh_signature pki_signature_from_blob(const ssh_key pubkey, + const ssh_string sig_blob, + enum ssh_keytypes_e type, + enum ssh_digest_e hash_type) +{ + ssh_signature sig; + gcry_error_t err; + size_t len; + size_t rsalen; + int rc; + + if (ssh_key_type_plain(pubkey->type) != type) { + SSH_LOG(SSH_LOG_WARN, + "Incompatible public key provided (%d) expecting (%d)", + type, + pubkey->type); + return NULL; + } + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + + sig->type = type; + sig->type_c = ssh_key_signature_to_char(type, hash_type); + sig->hash_type = hash_type; + + 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_signature_free(sig); + return NULL; + } + + if (len < rsalen) { + SSH_LOG(SSH_LOG_DEBUG, + "RSA signature len %lu < %lu", + (unsigned long)len, + (unsigned long)rsalen); + } + +#ifdef DEBUG_CRYPTO + SSH_LOG(SSH_LOG_DEBUG, "RSA signature len: %lu", (unsigned long)len); + ssh_log_hexdump("RSA signature", ssh_string_data(sig_blob), len); +#endif + + err = gcry_sexp_build(&sig->rsa_sig, + NULL, + "(sig-val(rsa(s %b)))", + ssh_string_len(sig_blob), + ssh_string_data(sig_blob)); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ED25519: + rc = pki_signature_from_ed25519_blob(sig, sig_blob); + if (rc != SSH_OK){ + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_GCRYPT_ECC + { /* build ecdsa siganature */ + ssh_buffer b; + ssh_string r, s; + uint32_t rlen; + + b = ssh_buffer_new(); + if (b == NULL) { + ssh_signature_free(sig); + return NULL; + } + + rc = ssh_buffer_add_data(b, + ssh_string_data(sig_blob), + ssh_string_len(sig_blob)); + if (rc < 0) { + SSH_BUFFER_FREE(b); + ssh_signature_free(sig); + return NULL; + } + + r = ssh_buffer_get_ssh_string(b); + if (r == NULL) { + SSH_BUFFER_FREE(b); + ssh_signature_free(sig); + return NULL; + } + + s = ssh_buffer_get_ssh_string(b); + rlen = ssh_buffer_get_len(b); + SSH_BUFFER_FREE(b); + if (s == NULL) { + ssh_string_burn(r); + SSH_STRING_FREE(r); + ssh_signature_free(sig); + return NULL; + } + + if (rlen != 0) { + SSH_LOG(SSH_LOG_WARN, + "Signature has remaining bytes in inner " + "sigblob: %lu", + (unsigned long)rlen); + ssh_string_burn(r); + SSH_STRING_FREE(r); + ssh_string_burn(s); + SSH_STRING_FREE(s); + ssh_signature_free(sig); + return NULL; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("r", ssh_string_data(r), ssh_string_len(r)); + ssh_log_hexdump("s", ssh_string_data(s), ssh_string_len(s)); +#endif + + err = gcry_sexp_build(&sig->ecdsa_sig, + NULL, + "(sig-val(ecdsa(r %b)(s %b)))", + ssh_string_len(r), + ssh_string_data(r), + ssh_string_len(s), + ssh_string_data(s)); + ssh_string_burn(r); + SSH_STRING_FREE(r); + ssh_string_burn(s); + SSH_STRING_FREE(s); + if (err) { + ssh_signature_free(sig); + return NULL; + } + } + break; +#endif + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_WARN, "Unknown signature type"); + return NULL; + } + + return sig; +} + +ssh_signature pki_do_sign_hash(const ssh_key privkey, + const unsigned char *hash, + 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; + gcry_error_t err; + + 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_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: + hash_c = "sha1"; + break; + case SSH_DIGEST_SHA256: + hash_c = "sha256"; + break; + case SSH_DIGEST_SHA512: + hash_c = "sha512"; + break; + case SSH_DIGEST_AUTO: + default: + SSH_LOG(SSH_LOG_WARN, "Incompatible key algorithm"); + return NULL; + } + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags pkcs1)(hash %s %b))", + hash_c, + hlen, + hash); + if (err) { + ssh_signature_free(sig); + return NULL; + } + + err = gcry_pk_sign(&sig->rsa_sig, sexp, privkey->rsa); + gcry_sexp_release(sexp); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ED25519: + err = pki_ed25519_sign(privkey, sig, hash, hlen); + if (err != SSH_OK){ + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_GCRYPT_ECC + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags raw)(value %b))", + hlen, + hash); + if (err) { + ssh_signature_free(sig); + return NULL; + } + + err = gcry_pk_sign(&sig->ecdsa_sig, sexp, privkey->ecdsa); + gcry_sexp_release(sexp); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; +#endif + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + ssh_signature_free(sig); + return NULL; + } + + return sig; +} + +/** + * @internal + * + * @brief Sign the given input data. The digest of to be signed is calculated + * internally as necessary. + * + * @param[in] privkey The private key to be used for signing. + * @param[in] hash_type The digest algorithm to be used. + * @param[in] input The data to be signed. + * @param[in] input_len The length of the data to be signed. + * + * @return a newly allocated ssh_signature or NULL on error. + */ +ssh_signature pki_sign_data(const ssh_key privkey, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len) +{ + unsigned char hash[SHA512_DIGEST_LEN] = {0}; + const unsigned char *sign_input = NULL; + uint32_t hlen = 0; + int rc; + + if (privkey == NULL || !ssh_key_is_private(privkey) || input == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "pki_sign_data()"); + return NULL; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(privkey, hash_type); + if (rc != SSH_OK) { + return NULL; + } + + switch (hash_type) { + case SSH_DIGEST_SHA256: + sha256(input, input_len, hash); + hlen = SHA256_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA384: + sha384(input, input_len, hash); + hlen = SHA384_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA512: + sha512(input, input_len, hash); + hlen = SHA512_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA1: + sha1(input, input_len, hash); + hlen = SHA_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_AUTO: + if (privkey->type == SSH_KEYTYPE_ED25519) { + /* SSH_DIGEST_AUTO should only be used with ed25519 */ + sign_input = input; + hlen = input_len; + break; + } + FALL_THROUGH; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown hash algorithm for type: %d", + hash_type); + return NULL; + } + + return pki_do_sign_hash(privkey, sign_input, hlen, hash_type); +} + +/** + * @internal + * + * @brief Verify the signature of a given input. The digest of the input is + * calculated internally as necessary. + * + * @param[in] signature The signature to be verified. + * @param[in] pubkey The public key used to verify the signature. + * @param[in] input The signed data. + * @param[in] input_len The length of the signed data. + * + * @return SSH_OK if the signature is valid; SSH_ERROR otherwise. + */ +int pki_verify_data_signature(ssh_signature signature, + const ssh_key pubkey, + const unsigned char *input, + size_t input_len) +{ + const char *hash_type = NULL; + gcry_sexp_t sexp; + gcry_error_t err; + + unsigned char ghash[SHA512_DIGEST_LEN + 1] = {0}; + unsigned char *hash = ghash + 1; + uint32_t hlen = 0; + + const unsigned char *verify_input = NULL; + + int rc; + + if (pubkey == NULL || ssh_key_is_private(pubkey) || input == NULL || + signature == NULL) + { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "pki_verify_data_signature()"); + return SSH_ERROR; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(pubkey, signature->hash_type); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + switch (signature->hash_type) { + case SSH_DIGEST_SHA256: + sha256(input, input_len, hash); + hlen = SHA256_DIGEST_LEN; + hash_type = "sha256"; + verify_input = hash; + break; + case SSH_DIGEST_SHA384: + sha384(input, input_len, hash); + hlen = SHA384_DIGEST_LEN; + hash_type = "sha384"; + verify_input = hash; + break; + case SSH_DIGEST_SHA512: + sha512(input, input_len, hash); + hlen = SHA512_DIGEST_LEN; + hash_type = "sha512"; + verify_input = hash; + break; + case SSH_DIGEST_SHA1: + sha1(input, input_len, hash); + hlen = SHA_DIGEST_LEN; + hash_type = "sha1"; + verify_input = hash; + break; + case SSH_DIGEST_AUTO: + if (pubkey->type == SSH_KEYTYPE_ED25519 || + pubkey->type == SSH_KEYTYPE_ED25519_CERT01) + { + verify_input = input; + hlen = input_len; + break; + } + FALL_THROUGH; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown sig->hash_type: %d", signature->hash_type); + return SSH_ERROR; + } + + 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, + NULL, + "(data(flags pkcs1)(hash %s %b))", + hash_type, hlen, hash); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "RSA hash error: %s", + gcry_strerror(err)); + return SSH_ERROR; + } + err = gcry_pk_verify(signature->rsa_sig, sexp, pubkey->rsa); + gcry_sexp_release(sexp); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "Invalid RSA signature"); + if (gcry_err_code(err) != GPG_ERR_BAD_SIGNATURE) { + SSH_LOG(SSH_LOG_TRACE, + "RSA verify error: %s", + gcry_strerror(err)); + } + return SSH_ERROR; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: +#ifdef HAVE_GCRYPT_ECC + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags raw)(value %b))", + hlen, + hash); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "ECDSA hash error: %s", + gcry_strerror(err)); + return SSH_ERROR; + } + err = gcry_pk_verify(signature->ecdsa_sig, sexp, pubkey->ecdsa); + gcry_sexp_release(sexp); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "Invalid ECDSA signature"); + if (gcry_err_code(err) != GPG_ERR_BAD_SIGNATURE) { + SSH_LOG(SSH_LOG_TRACE, + "ECDSA verify error: %s", + gcry_strerror(err)); + } + return SSH_ERROR; + } + break; +#endif + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + rc = pki_ed25519_verify(pubkey, signature, verify_input, hlen); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "ED25519 error: Signature invalid"); + return SSH_ERROR; + } + break; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown public key type"); + return SSH_ERROR; + } + + return SSH_OK; +} + +#endif /* HAVE_LIBGCRYPT */ diff --git a/src/pki_mbedcrypto.c b/src/pki_mbedcrypto.c new file mode 100644 index 0000000..6e3d425 --- /dev/null +++ b/src/pki_mbedcrypto.c @@ -0,0 +1,1594 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * 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" + +#ifdef HAVE_LIBMBEDCRYPTO +#include +#include + +#include "libssh/priv.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/buffer.h" +#include "libssh/bignum.h" +#include "libssh/misc.h" + +#define MAX_PASSPHRASE_SIZE 1024 +#define MAX_KEY_SIZE 32 + +ssh_string pki_private_key_to_pem(const ssh_key key, const char *passphrase, + ssh_auth_callback auth_fn, void *auth_data) +{ + (void) key; + (void) passphrase; + (void) auth_fn; + (void) auth_data; return NULL; +} + +static int pki_key_ecdsa_to_nid(mbedtls_ecdsa_context *ecdsa) +{ + mbedtls_ecp_group_id id; + + id = ecdsa->grp.id; + if (id == MBEDTLS_ECP_DP_SECP256R1) { + return NID_mbedtls_nistp256; + } else if (id == MBEDTLS_ECP_DP_SECP384R1) { + return NID_mbedtls_nistp384; + } else if (id == MBEDTLS_ECP_DP_SECP521R1) { + return NID_mbedtls_nistp521; + } + + return -1; +} + +static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(mbedtls_ecdsa_context *ecdsa) +{ + int nid; + + nid = pki_key_ecdsa_to_nid(ecdsa); + + switch (nid) { + case NID_mbedtls_nistp256: + return SSH_KEYTYPE_ECDSA_P256; + case NID_mbedtls_nistp384: + return SSH_KEYTYPE_ECDSA_P384; + case NID_mbedtls_nistp521: + return SSH_KEYTYPE_ECDSA_P521; + default: + return SSH_KEYTYPE_UNKNOWN; + } +} + +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; + 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}; + + 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; + } + + 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); + 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; + } + + key = ssh_key_new(); + if (key == NULL) { + goto fail; + } + + if (ecdsa != NULL) { + mbedtls_ecp_keypair *keypair = mbedtls_pk_ec(*ecdsa); + + key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); + if (key->ecdsa == NULL) { + goto fail; + } + + mbedtls_ecdsa_init(key->ecdsa); + mbedtls_ecdsa_from_keypair(key->ecdsa, keypair); + mbedtls_pk_free(ecdsa); + SAFE_FREE(ecdsa); + + 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."); + goto fail; + } + } else { + key->ecdsa = NULL; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(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); + } + return NULL; +} + +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) +{ + mbedtls_rsa_context *rsa = NULL; + const mbedtls_pk_info_t *pk_info = NULL; + int rc; + + key->rsa = malloc(sizeof(mbedtls_pk_context)); + if (key->rsa == NULL) { + return SSH_ERROR; + } + + mbedtls_pk_init(key->rsa); + pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); + mbedtls_pk_setup(key->rsa, pk_info); + + rc = mbedtls_pk_can_do(key->rsa, MBEDTLS_PK_RSA); + if (rc == 0) { + goto fail; + } + + rsa = mbedtls_pk_rsa(*key->rsa); + rc = mbedtls_rsa_import_raw(rsa, + ssh_string_data(n), ssh_string_len(n), + ssh_string_data(p), ssh_string_len(p), + ssh_string_data(q), ssh_string_len(q), + 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"); + goto fail; + } + + rc = mbedtls_rsa_complete(rsa); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARN, "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"); + goto fail; + } + + return SSH_OK; + +fail: + mbedtls_pk_free(key->rsa); + SAFE_FREE(key->rsa); + return SSH_ERROR; +} + +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; + int rc; + + key->rsa = malloc(sizeof(mbedtls_pk_context)); + if (key->rsa == NULL) { + return SSH_ERROR; + } + + mbedtls_pk_init(key->rsa); + pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); + mbedtls_pk_setup(key->rsa, pk_info); + + rc = mbedtls_pk_can_do(key->rsa, MBEDTLS_PK_RSA); + if (rc == 0) { + goto fail; + } + + rsa = mbedtls_pk_rsa(*key->rsa); + rc = mbedtls_mpi_read_binary(&rsa->N, ssh_string_data(n), + ssh_string_len(n)); + if (rc != 0) { + goto fail; + } + rc = mbedtls_mpi_read_binary(&rsa->E, ssh_string_data(e), + ssh_string_len(e)); + if (rc != 0) { + goto fail; + } + + rsa->len = (mbedtls_mpi_bitlen(&rsa->N) + 7) >> 3; + + return SSH_OK; + +fail: + mbedtls_pk_free(key->rsa); + SAFE_FREE(key->rsa); + return SSH_ERROR; +} + +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; + + + new = ssh_key_new(); + if (new == NULL) { + return NULL; + } + + new->type = key->type; + new->type_c = key->type_c; + if (demote) { + new->flags = SSH_KEY_FLAG_PUBLIC; + } else { + new->flags = key->flags; + } + + + switch(key->type) { + case SSH_KEYTYPE_RSA: { + mbedtls_rsa_context *rsa, *new_rsa; + + new->rsa = malloc(sizeof(mbedtls_pk_context)); + if (new->rsa == NULL) { + goto fail; + } + + mbedtls_pk_init(new->rsa); + 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); + + 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; + + 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->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->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->QP, &rsa->QP); + if (rc != 0) { + goto fail; + } + } + } else { + goto fail; + } + + break; + } + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + new->ecdsa_nid = key->ecdsa_nid; + + new->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); + + if (new->ecdsa == NULL) { + goto fail; + } + + mbedtls_ecdsa_init(new->ecdsa); + + if (demote && ssh_key_is_private(key)) { + rc = mbedtls_ecp_copy(&new->ecdsa->Q, &key->ecdsa->Q); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_group_copy(&new->ecdsa->grp, &key->ecdsa->grp); + if (rc != 0) { + goto fail; + } + } else { + mbedtls_ecdsa_from_keypair(new->ecdsa, key->ecdsa); + } + + break; + case SSH_KEYTYPE_ED25519: + rc = pki_ed25519_key_dup(new, key); + if (rc != SSH_OK) { + goto fail; + } + break; + default: + goto fail; + } + + return new; +fail: + ssh_key_free(new); + return NULL; +} + +int pki_key_generate_rsa(ssh_key key, int parameter) +{ + int rc; + const mbedtls_pk_info_t *info = NULL; + + key->rsa = malloc(sizeof(mbedtls_pk_context)); + if (key->rsa == NULL) { + return SSH_ERROR; + } + + mbedtls_pk_init(key->rsa); + + info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); + rc = mbedtls_pk_setup(key->rsa, info); + if (rc != 0) { + return SSH_ERROR; + } + + if (mbedtls_pk_can_do(key->rsa, MBEDTLS_PK_RSA)) { + rc = mbedtls_rsa_gen_key(mbedtls_pk_rsa(*key->rsa), + mbedtls_ctr_drbg_random, + ssh_get_mbedtls_ctr_drbg_context(), + parameter, + 65537); + if (rc != 0) { + mbedtls_pk_free(key->rsa); + return SSH_ERROR; + } + } + + return SSH_OK; +} + +int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) +{ + switch (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; + } + + rsa1 = mbedtls_pk_rsa(*k1->rsa); + rsa2 = mbedtls_pk_rsa(*k2->rsa); + if (mbedtls_mpi_cmp_mpi(&rsa1->N, &rsa2->N) != 0) { + return 1; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->E, &rsa2->E) != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (mbedtls_mpi_cmp_mpi(&rsa1->P, &rsa2->P) != 0) { + return 1; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->Q, &rsa2->Q) != 0) { + return 1; + } + } + } + break; + } + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: { + mbedtls_ecp_keypair *ecdsa1 = k1->ecdsa; + mbedtls_ecp_keypair *ecdsa2 = k2->ecdsa; + + if (ecdsa1->grp.id != ecdsa2->grp.id) { + return 1; + } + + if (mbedtls_mpi_cmp_mpi(&ecdsa1->Q.X, &ecdsa2->Q.X)) { + return 1; + } + + if (mbedtls_mpi_cmp_mpi(&ecdsa1->Q.Y, &ecdsa2->Q.Y)) { + return 1; + } + + if (mbedtls_mpi_cmp_mpi(&ecdsa1->Q.Z, &ecdsa2->Q.Z)) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (mbedtls_mpi_cmp_mpi(&ecdsa1->d, &ecdsa2->d)) { + return 1; + } + } + + break; + } + case SSH_KEYTYPE_ED25519: + /* ed25519 keys handled globally */ + return 0; + default: + return 1; + } + + return 0; +} + +ssh_string make_ecpoint_string(const mbedtls_ecp_group *g, const + mbedtls_ecp_point *p) +{ + ssh_string s = NULL; + size_t len = 1; + int rc; + + s = ssh_string_new(len); + if (s == NULL) { + return NULL; + } + + rc = mbedtls_ecp_point_write_binary(g, p, MBEDTLS_ECP_PF_UNCOMPRESSED, + &len, ssh_string_data(s), ssh_string_len(s)); + if (rc == MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL) { + SSH_STRING_FREE(s); + + s = ssh_string_new(len); + if (s == NULL) { + return NULL; + } + + rc = mbedtls_ecp_point_write_binary(g, p, MBEDTLS_ECP_PF_UNCOMPRESSED, + &len, ssh_string_data(s), ssh_string_len(s)); + } + + if (rc != 0) { + SSH_STRING_FREE(s); + return NULL; + } + + if (len != ssh_string_len(s)) { + SSH_STRING_FREE(s); + return NULL; + } + + return s; +} + +static const char* pki_key_ecdsa_nid_to_char(int nid) +{ + switch (nid) { + case NID_mbedtls_nistp256: + return "nistp256"; + case NID_mbedtls_nistp384: + return "nistp384"; + case NID_mbedtls_nistp521: + return "nistp521"; + default: + break; + } + + return "unknown"; +} + +ssh_string pki_publickey_to_blob(const ssh_key key) +{ + ssh_buffer buffer = NULL; + ssh_string type_s = NULL; + ssh_string e = NULL; + ssh_string n = NULL; + ssh_string str = NULL; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + + if (key->cert != NULL) { + rc = ssh_buffer_add_buffer(buffer, key->cert); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + goto makestring; + } + + type_s = ssh_string_from_char(key->type_c); + 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; + } + + switch (key->type) { + case SSH_KEYTYPE_RSA: { + mbedtls_rsa_context *rsa; + if (mbedtls_pk_can_do(key->rsa, MBEDTLS_PK_RSA) == 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rsa = mbedtls_pk_rsa(*key->rsa); + + e = ssh_make_bignum_string(&rsa->E); + if (e == NULL) { + goto fail; + } + + n = ssh_make_bignum_string(&rsa->N); + if (n == NULL) { + goto fail; + } + + if (ssh_buffer_add_ssh_string(buffer, e) < 0) { + goto fail; + } + + if (ssh_buffer_add_ssh_string(buffer, n) < 0) { + goto fail; + } + + 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: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + 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; + } + + e = make_ecpoint_string(&key->ecdsa->grp, &key->ecdsa->Q); + + if (e == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(e); + SSH_STRING_FREE(e); + e = NULL; + + break; + case SSH_KEYTYPE_ED25519: + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc != SSH_OK) { + goto fail; + } + break; + default: + goto fail; + } +makestring: + str = ssh_string_new(ssh_buffer_get_len(buffer)); + if (str == NULL) { + goto fail; + } + + rc = ssh_string_fill(str, ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); + if (rc < 0) { + goto fail; + } + + SSH_BUFFER_FREE(buffer); + return str; +fail: + SSH_BUFFER_FREE(buffer); + ssh_string_burn(str); + SSH_STRING_FREE(str); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(n); + SSH_STRING_FREE(n); + + return NULL; +} + +ssh_string pki_signature_to_blob(const ssh_signature sig) +{ + ssh_string sig_blob = NULL; + + switch(sig->type) { + case SSH_KEYTYPE_RSA: + sig_blob = ssh_string_copy(sig->rsa_sig); + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: { + ssh_string r; + ssh_string s; + ssh_buffer b; + int rc; + + b = ssh_buffer_new(); + if (b == NULL) { + return NULL; + } + + r = ssh_make_bignum_string(sig->ecdsa_sig.r); + if (r == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(b, r); + SSH_STRING_FREE(r); + if (rc < 0) { + SSH_BUFFER_FREE(b); + return NULL; + } + + s = ssh_make_bignum_string(sig->ecdsa_sig.s); + if (s == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(b, s); + SSH_STRING_FREE(s); + if (rc < 0) { + SSH_BUFFER_FREE(b); + return NULL; + } + + sig_blob = ssh_string_new(ssh_buffer_get_len(b)); + if (sig_blob == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + ssh_string_fill(sig_blob, ssh_buffer_get(b), ssh_buffer_get_len(b)); + SSH_BUFFER_FREE(b); + 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", + sig->type_c); + return NULL; + } + + return sig_blob; +} + +static ssh_signature pki_signature_from_rsa_blob(const ssh_key pubkey, const + ssh_string sig_blob, ssh_signature sig) +{ + size_t pad_len = 0; + char *blob_orig = NULL; + char *blob_padded_data = NULL; + ssh_string sig_blob_padded = NULL; + + 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"); + goto errout; + } + + rsalen = mbedtls_pk_get_bitlen(pubkey->rsa) / 8; + if (len > rsalen) { + SSH_LOG(SSH_LOG_WARN, + "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_hexdump("RSA signature", ssh_string_data(sig_blob), len); +#endif + + if (len == rsalen) { + sig->rsa_sig = ssh_string_copy(sig_blob); + } else { + SSH_LOG(SSH_LOG_DEBUG, "RSA signature len %lu < %lu", + (unsigned long) len, + (unsigned long) rsalen); + pad_len = rsalen - len; + + sig_blob_padded = ssh_string_new(rsalen); + if (sig_blob_padded == NULL) { + goto errout; + } + + blob_padded_data = (char *) ssh_string_data(sig_blob_padded); + blob_orig = (char *) ssh_string_data(sig_blob); + + explicit_bzero(blob_padded_data, pad_len); + memcpy(blob_padded_data + pad_len, blob_orig, len); + + sig->rsa_sig = sig_blob_padded; + } + + return sig; + +errout: + ssh_signature_free(sig); + return NULL; +} +ssh_signature pki_signature_from_blob(const ssh_key pubkey, + const ssh_string sig_blob, + enum ssh_keytypes_e type, + enum ssh_digest_e hash_type) +{ + ssh_signature sig = NULL; + int rc; + + if (ssh_key_type_plain(pubkey->type) != type) { + SSH_LOG(SSH_LOG_WARN, + "Incompatible public key provided (%d) expecting (%d)", + type, + pubkey->type); + return NULL; + } + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + + sig->type = type; + sig->type_c = ssh_key_signature_to_char(type, hash_type); + sig->hash_type = hash_type; + + switch(type) { + case SSH_KEYTYPE_RSA: + sig = pki_signature_from_rsa_blob(pubkey, sig_blob, sig); + if (sig == NULL) { + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: { + ssh_buffer b; + ssh_string r; + ssh_string s; + size_t rlen; + + b = ssh_buffer_new(); + if (b == NULL) { + ssh_signature_free(sig); + return NULL; + } + + rc = ssh_buffer_add_data(b, ssh_string_data(sig_blob), + ssh_string_len(sig_blob)); + + if (rc < 0) { + SSH_BUFFER_FREE(b); + ssh_signature_free(sig); + return NULL; + } + + r = ssh_buffer_get_ssh_string(b); + if (r == NULL) { + SSH_BUFFER_FREE(b); + ssh_signature_free(sig); + return NULL; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("r", ssh_string_data(r), ssh_string_len(r)); +#endif + sig->ecdsa_sig.r = ssh_make_string_bn(r); + ssh_string_burn(r); + SSH_STRING_FREE(r); + if (sig->ecdsa_sig.r == NULL) { + SSH_BUFFER_FREE(b); + ssh_signature_free(sig); + return NULL; + } + + s = ssh_buffer_get_ssh_string(b); + rlen = ssh_buffer_get_len(b); + SSH_BUFFER_FREE(b); + if (s == NULL) { + ssh_signature_free(sig); + return NULL; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("s", ssh_string_data(s), ssh_string_len(s)); +#endif + sig->ecdsa_sig.s = ssh_make_string_bn(s); + ssh_string_burn(s); + SSH_STRING_FREE(s); + if (sig->ecdsa_sig.s == NULL) { + ssh_signature_free(sig); + return NULL; + } + + if (rlen != 0) { + SSH_LOG(SSH_LOG_WARN, "Signature has remaining bytes in inner " + "sigblob: %lu", + (unsigned long)rlen); + ssh_signature_free(sig); + return NULL; + } + + break; + } + case SSH_KEYTYPE_ED25519: + rc = pki_signature_from_ed25519_blob(sig, sig_blob); + if (rc == SSH_ERROR) { + ssh_signature_free(sig); + return NULL; + } + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unknown signature type"); + return NULL; + } + + return sig; +} + +static ssh_string rsa_do_sign_hash(const unsigned char *digest, + int dlen, + mbedtls_pk_context *privkey, + enum ssh_digest_e hash_type) +{ + ssh_string sig_blob = NULL; + mbedtls_md_type_t md = 0; + unsigned char *sig = NULL; + size_t slen; + int ok; + + switch (hash_type) { + case SSH_DIGEST_SHA1: + md = MBEDTLS_MD_SHA1; + break; + case SSH_DIGEST_SHA256: + md = MBEDTLS_MD_SHA256; + break; + case SSH_DIGEST_SHA512: + md = MBEDTLS_MD_SHA512; + break; + case SSH_DIGEST_AUTO: + default: + SSH_LOG(SSH_LOG_WARN, "Incompatible key algorithm"); + return NULL; + } + + sig = malloc(mbedtls_pk_get_bitlen(privkey) / 8); + if (sig == NULL) { + return NULL; + } + + ok = mbedtls_pk_sign(privkey, + md, + digest, + dlen, + sig, + &slen, + mbedtls_ctr_drbg_random, + ssh_get_mbedtls_ctr_drbg_context()); + + if (ok != 0) { + SAFE_FREE(sig); + return NULL; + } + + sig_blob = ssh_string_new(slen); + if (sig_blob == NULL) { + SAFE_FREE(sig); + return NULL; + } + + ssh_string_fill(sig_blob, sig, slen); + explicit_bzero(sig, slen); + SAFE_FREE(sig); + + return sig_blob; +} + + +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_RSA: + sig->rsa_sig = rsa_do_sign_hash(hash, hlen, privkey->rsa, hash_type); + if (sig->rsa_sig == NULL) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + sig->ecdsa_sig.r = bignum_new(); + if (sig->ecdsa_sig.r == NULL) { + return NULL; + } + + sig->ecdsa_sig.s = bignum_new(); + if (sig->ecdsa_sig.s == NULL) { + bignum_safe_free(sig->ecdsa_sig.r); + return NULL; + } + + rc = mbedtls_ecdsa_sign(&privkey->ecdsa->grp, + sig->ecdsa_sig.r, + sig->ecdsa_sig.s, + &privkey->ecdsa->d, + hash, + hlen, + mbedtls_ctr_drbg_random, + ssh_get_mbedtls_ctr_drbg_context()); + if (rc != 0) { + ssh_signature_free(sig); + return NULL; + } + break; + 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; +} + +/** + * @internal + * + * @brief Sign the given input data. The digest of to be signed is calculated + * internally as necessary. + * + * @param[in] privkey The private key to be used for signing. + * @param[in] hash_type The digest algorithm to be used. + * @param[in] input The data to be signed. + * @param[in] input_len The length of the data to be signed. + * + * @return a newly allocated ssh_signature or NULL on error. + */ +ssh_signature pki_sign_data(const ssh_key privkey, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len) +{ + unsigned char hash[SHA512_DIGEST_LEN] = {0}; + const unsigned char *sign_input = NULL; + uint32_t hlen = 0; + int rc; + + if (privkey == NULL || !ssh_key_is_private(privkey) || input == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "pki_sign_data()"); + return NULL; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(privkey, hash_type); + if (rc != SSH_OK) { + return NULL; + } + + switch (hash_type) { + case SSH_DIGEST_SHA256: + sha256(input, input_len, hash); + hlen = SHA256_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA384: + sha384(input, input_len, hash); + hlen = SHA384_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA512: + sha512(input, input_len, hash); + hlen = SHA512_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA1: + sha1(input, input_len, hash); + hlen = SHA_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_AUTO: + if (privkey->type == SSH_KEYTYPE_ED25519) { + /* SSH_DIGEST_AUTO should only be used with ed25519 */ + sign_input = input; + hlen = input_len; + break; + } + FALL_THROUGH; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown hash algorithm for type: %d", + hash_type); + return NULL; + } + + return pki_do_sign_hash(privkey, sign_input, hlen, hash_type); +} + +/** + * @internal + * + * @brief Verify the signature of a given input. The digest of the input is + * calculated internally as necessary. + * + * @param[in] signature The signature to be verified. + * @param[in] pubkey The public key used to verify the signature. + * @param[in] input The signed data. + * @param[in] input_len The length of the signed data. + * + * @return SSH_OK if the signature is valid; SSH_ERROR otherwise. + */ +int pki_verify_data_signature(ssh_signature signature, + const ssh_key pubkey, + const unsigned char *input, + size_t input_len) +{ + + unsigned char hash[SHA512_DIGEST_LEN] = {0}; + const unsigned char *verify_input = NULL; + uint32_t hlen = 0; + + mbedtls_md_type_t md = 0; + + int rc; + + if (pubkey == NULL || ssh_key_is_private(pubkey) || input == NULL || + signature == NULL) + { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "pki_verify_data_signature()"); + return SSH_ERROR; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(pubkey, signature->hash_type); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + switch (signature->hash_type) { + case SSH_DIGEST_SHA256: + sha256(input, input_len, hash); + hlen = SHA256_DIGEST_LEN; + md = MBEDTLS_MD_SHA256; + verify_input = hash; + break; + case SSH_DIGEST_SHA384: + sha384(input, input_len, hash); + hlen = SHA384_DIGEST_LEN; + md = MBEDTLS_MD_SHA384; + verify_input = hash; + break; + case SSH_DIGEST_SHA512: + sha512(input, input_len, hash); + hlen = SHA512_DIGEST_LEN; + md = MBEDTLS_MD_SHA512; + verify_input = hash; + break; + case SSH_DIGEST_SHA1: + sha1(input, input_len, hash); + hlen = SHA_DIGEST_LEN; + md = MBEDTLS_MD_SHA1; + verify_input = hash; + break; + case SSH_DIGEST_AUTO: + if (pubkey->type == SSH_KEYTYPE_ED25519 || + pubkey->type == SSH_KEYTYPE_ED25519_CERT01) + { + verify_input = input; + hlen = input_len; + break; + } + FALL_THROUGH; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown sig->hash_type: %d", + signature->hash_type); + return SSH_ERROR; + } + + switch (pubkey->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + rc = mbedtls_pk_verify(pubkey->rsa, md, hash, hlen, + ssh_string_data(signature->rsa_sig), + ssh_string_len(signature->rsa_sig)); + if (rc != 0) { + char error_buf[100]; + mbedtls_strerror(rc, error_buf, 100); + SSH_LOG(SSH_LOG_TRACE, "RSA error: %s", error_buf); + return SSH_ERROR; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + rc = mbedtls_ecdsa_verify(&pubkey->ecdsa->grp, hash, hlen, + &pubkey->ecdsa->Q, signature->ecdsa_sig.r, + signature->ecdsa_sig.s); + if (rc != 0) { + char error_buf[100]; + mbedtls_strerror(rc, error_buf, 100); + SSH_LOG(SSH_LOG_TRACE, "ECDSA error: %s", error_buf); + return SSH_ERROR; + + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + rc = pki_ed25519_verify(pubkey, signature, verify_input, hlen); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "ED25519 error: Signature invalid"); + return SSH_ERROR; + } + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown public key type"); + return SSH_ERROR; + } + + return SSH_OK; +} + +const char *pki_key_ecdsa_nid_to_name(int nid) +{ + switch (nid) { + case NID_mbedtls_nistp256: + return "ecdsa-sha2-nistp256"; + case NID_mbedtls_nistp384: + return "ecdsa-sha2-nistp384"; + case NID_mbedtls_nistp521: + return "ecdsa-sha2-nistp521"; + default: + break; + } + + return "unknown"; +} + +int pki_key_ecdsa_nid_from_name(const char *name) +{ + if (strcmp(name, "nistp256") == 0) { + return NID_mbedtls_nistp256; + } else if (strcmp(name, "nistp384") == 0) { + return NID_mbedtls_nistp384; + } else if (strcmp(name, "nistp521") == 0) { + return NID_mbedtls_nistp521; + } + + return -1; +} + +static mbedtls_ecp_group_id pki_key_ecdsa_nid_to_mbed_gid(int nid) +{ + switch (nid) { + case NID_mbedtls_nistp256: + return MBEDTLS_ECP_DP_SECP256R1; + case NID_mbedtls_nistp384: + return MBEDTLS_ECP_DP_SECP384R1; + case NID_mbedtls_nistp521: + return MBEDTLS_ECP_DP_SECP521R1; + } + + return MBEDTLS_ECP_DP_NONE; +} + +int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) +{ + int rc; + mbedtls_ecp_keypair keypair; + mbedtls_ecp_group group; + mbedtls_ecp_point Q; + + key->ecdsa_nid = nid; + key->type_c = pki_key_ecdsa_nid_to_name(nid); + + key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); + if (key->ecdsa == NULL) { + return SSH_ERROR; + } + + mbedtls_ecdsa_init(key->ecdsa); + mbedtls_ecp_keypair_init(&keypair); + mbedtls_ecp_group_init(&group); + mbedtls_ecp_point_init(&Q); + + rc = mbedtls_ecp_group_load(&group, + pki_key_ecdsa_nid_to_mbed_gid(nid)); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_point_read_binary(&group, &Q, ssh_string_data(e), + ssh_string_len(e)); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_copy(&keypair.Q, &Q); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_group_copy(&keypair.grp, &group); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_read_binary(&keypair.d, ssh_string_data(exp), + ssh_string_len(exp)); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecdsa_from_keypair(key->ecdsa, &keypair); + if (rc != 0) { + goto fail; + } + + mbedtls_ecp_point_free(&Q); + mbedtls_ecp_group_free(&group); + mbedtls_ecp_keypair_free(&keypair); + return SSH_OK; + +fail: + mbedtls_ecdsa_free(key->ecdsa); + mbedtls_ecp_point_free(&Q); + mbedtls_ecp_group_free(&group); + mbedtls_ecp_keypair_free(&keypair); + SAFE_FREE(key->ecdsa); + return SSH_ERROR; +} + +int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) +{ + int rc; + mbedtls_ecp_keypair keypair; + mbedtls_ecp_group group; + mbedtls_ecp_point Q; + + key->ecdsa_nid = nid; + key->type_c = pki_key_ecdsa_nid_to_name(nid); + + key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); + if (key->ecdsa == NULL) { + return SSH_ERROR; + } + + mbedtls_ecdsa_init(key->ecdsa); + mbedtls_ecp_keypair_init(&keypair); + mbedtls_ecp_group_init(&group); + mbedtls_ecp_point_init(&Q); + + rc = mbedtls_ecp_group_load(&group, + pki_key_ecdsa_nid_to_mbed_gid(nid)); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_point_read_binary(&group, &Q, ssh_string_data(e), + ssh_string_len(e)); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_copy(&keypair.Q, &Q); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_group_copy(&keypair.grp, &group); + if (rc != 0) { + goto fail; + } + + mbedtls_mpi_init(&keypair.d); + + rc = mbedtls_ecdsa_from_keypair(key->ecdsa, &keypair); + if (rc != 0) { + goto fail; + } + + mbedtls_ecp_point_free(&Q); + mbedtls_ecp_group_free(&group); + mbedtls_ecp_keypair_free(&keypair); + return SSH_OK; +fail: + mbedtls_ecdsa_free(key->ecdsa); + mbedtls_ecp_point_free(&Q); + mbedtls_ecp_group_free(&group); + mbedtls_ecp_keypair_free(&keypair); + SAFE_FREE(key->ecdsa); + return SSH_ERROR; +} + +int pki_key_generate_ecdsa(ssh_key key, int parameter) +{ + int ok; + + switch (parameter) { + case 384: + key->ecdsa_nid = NID_mbedtls_nistp384; + key->type = SSH_KEYTYPE_ECDSA_P384; + break; + case 521: + key->ecdsa_nid = NID_mbedtls_nistp521; + key->type = SSH_KEYTYPE_ECDSA_P521; + break; + case 256: + default: + key->ecdsa_nid = NID_mbedtls_nistp256; + key->type = SSH_KEYTYPE_ECDSA_P256; + break; + } + + key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); + if (key->ecdsa == NULL) { + return SSH_ERROR; + } + + mbedtls_ecdsa_init(key->ecdsa); + + ok = mbedtls_ecdsa_genkey(key->ecdsa, + pki_key_ecdsa_nid_to_mbed_gid(key->ecdsa_nid), + mbedtls_ctr_drbg_random, + ssh_get_mbedtls_ctr_drbg_context()); + + if (ok != 0) { + mbedtls_ecdsa_free(key->ecdsa); + SAFE_FREE(key->ecdsa); + } + + 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) +{ + (void) key; + (void) parameter; + return SSH_ERROR; +} +#endif /* HAVE_LIBMBEDCRYPTO */ diff --git a/src/poll.c b/src/poll.c new file mode 100644 index 0000000..4620694 --- /dev/null +++ b/src/poll.c @@ -0,0 +1,1120 @@ +/* + * poll.c - poll wrapper + * + * This file is part of the SSH Library + * + * Copyright (c) 2009-2013 by Andreas Schneider + * Copyright (c) 2003-2013 by Aris Adamantiadis + * Copyright (c) 2009 Aleksandar Kanchev + * + * 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 +#include + +#include "libssh/priv.h" +#include "libssh/libssh.h" +#include "libssh/poll.h" +#include "libssh/socket.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#ifdef WITH_SERVER +#include "libssh/server.h" +#endif + + +#ifndef SSH_POLL_CTX_CHUNK +#define SSH_POLL_CTX_CHUNK 5 +#endif + +/** + * @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. + * + * 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 + * be done within the main loop of an application. + * + * @{ + */ + +struct ssh_poll_handle_struct { + ssh_poll_ctx ctx; + ssh_session session; + union { + socket_t fd; + size_t idx; + } x; + short events; + int lock; + ssh_poll_callback cb; + void *cb_data; +}; + +struct ssh_poll_ctx_struct { + ssh_poll_handle *pollptrs; + ssh_pollfd_t *pollfds; + size_t polls_allocated; + size_t polls_used; + size_t chunk_size; +}; + +#ifdef HAVE_POLL +#include + +void ssh_poll_init(void) { + return; +} + +void ssh_poll_cleanup(void) { + return; +} + +int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) { + return poll((struct pollfd *) fds, nfds, timeout); +} + +#else /* HAVE_POLL */ + +typedef int (*poll_fn)(ssh_pollfd_t *, nfds_t, int); +static poll_fn ssh_poll_emu; + +#include +#include + +#ifdef _WIN32 +#ifndef STRICT +#define STRICT +#endif /* STRICT */ + +#include +#include +#include +#else /* _WIN32 */ +#include +#include + +# ifdef HAVE_SYS_TIME_H +# include +# endif + +#endif /* _WIN32 */ + +#ifdef HAVE_UNISTD_H +#include +#endif + +static bool bsd_socket_not_connected(int sock_err) +{ + switch (sock_err) { +#ifdef _WIN32 + case WSAENOTCONN: +#else + case ENOTCONN: +#endif + return true; + default: + return false; + } + + return false; +} + +static bool bsd_socket_reset(int sock_err) +{ + switch (sock_err) { +#ifdef _WIN32 + case WSAECONNABORTED: + case WSAECONNRESET: + case WSAENETRESET: + case WSAESHUTDOWN: + case WSAECONNREFUSED: + case WSAETIMEDOUT: +#else + case ECONNABORTED: + case ECONNRESET: + case ENETRESET: + case ESHUTDOWN: +#endif + return true; + default: + return false; + } + + return false; +} + +static short bsd_socket_compute_revents(int fd, short events) +{ + int save_errno = errno; + int sock_errno = errno; + char data[64] = {0}; + short revents = 0; + int flags = MSG_PEEK; + int ret; + +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + + /* support for POLLHUP */ +#ifdef _WIN32 + WSASetLastError(0); +#endif + + ret = recv(fd, data, 64, flags); + + errno = save_errno; + +#ifdef _WIN32 + sock_errno = WSAGetLastError(); + WSASetLastError(0); +#endif + + if (ret > 0 || bsd_socket_not_connected(sock_errno)) { + revents = (POLLIN | POLLRDNORM) & events; + } else if (ret == 0 || bsd_socket_reset(sock_errno)) { + errno = sock_errno; + revents = POLLHUP; + } else { + revents = POLLERR; + } + + return revents; +} + +/* + * This is a poll(2)-emulation using select for systems not providing a native + * 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. + * poll() will be orders of magnitude faster. + */ +static int bsd_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) +{ + fd_set readfds, writefds, exceptfds; + struct timeval tv, *ptv = NULL; + socket_t max_fd; + int rc; + nfds_t i; + + if (fds == NULL) { + errno = EFAULT; + return -1; + } + + ZERO_STRUCT(readfds); + FD_ZERO(&readfds); + ZERO_STRUCT(writefds); + FD_ZERO(&writefds); + ZERO_STRUCT(exceptfds); + FD_ZERO(&exceptfds); + + /* compute fd_sets and find largest descriptor */ + for (rc = -1, max_fd = 0, i = 0; i < nfds; i++) { + if (fds[i].fd == SSH_INVALID_SOCKET) { + continue; + } +#ifndef _WIN32 + if (fds[i].fd >= FD_SETSIZE) { + rc = -1; + break; + } +#endif + + if (fds[i].events & (POLLIN | POLLRDNORM)) { + 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))) { + max_fd = fds[i].fd; + rc = 0; + } + } + + if (max_fd == SSH_INVALID_SOCKET || rc == -1) { + errno = EINVAL; + return -1; + } + + if (timeout < 0) { + ptv = NULL; + } else { + ptv = &tv; + if (timeout == 0) { + tv.tv_sec = 0; + tv.tv_usec = 0; + } else { + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + } + } + + rc = select(max_fd + 1, &readfds, &writefds, &exceptfds, ptv); + if (rc < 0) { + return -1; + } + /* A timeout occured */ + if (rc == 0) { + return 0; + } + + for (rc = 0, i = 0; i < nfds; i++) { + if (fds[i].fd >= 0) { + fds[i].revents = 0; + + if (FD_ISSET(fds[i].fd, &readfds)) { + fds[i].revents = bsd_socket_compute_revents(fds[i].fd, + fds[i].events); + } + if (FD_ISSET(fds[i].fd, &writefds)) { + fds[i].revents |= fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND); + } + + if (FD_ISSET(fds[i].fd, &exceptfds)) { + fds[i].revents |= fds[i].events & (POLLPRI | POLLRDBAND); + } + + if (fds[i].revents != 0) { + rc++; + } + } else { + fds[i].revents = POLLNVAL; + } + } + + return rc; +} + +void ssh_poll_init(void) { + ssh_poll_emu = bsd_poll; +} + +void ssh_poll_cleanup(void) { + ssh_poll_emu = bsd_poll; +} + +int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) { + return (ssh_poll_emu)(fds, nfds, timeout); +} + +#endif /* HAVE_POLL */ + +/** + * @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. + * + * @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 p; + + p = malloc(sizeof(struct ssh_poll_handle_struct)); + if (p == NULL) { + return NULL; + } + ZERO_STRUCTP(p); + + p->x.fd = fd; + p->events = events; + p->cb = cb; + p->cb_data = userdata; + + return p; +} + + +/** + * @brief Free a poll object. + * + * @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); +} + +/** + * @brief Get the poll context of a poll object. + * + * @param p Pointer to an already allocated poll object. + * + * @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; +} + +/** + * @brief Get the events of a poll object. + * + * @param p Pointer to an already allocated poll object. + * + * @return Poll 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. + * + * @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; + } +} + +/** + * @brief Set the file descriptor of a poll object. The FD will also be propagated + * to an associated poll context. + * + * @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; + } +} + +/** + * @brief Add extra events to a poll object. Duplicates are ignored. + * The events will also be propagated to an associated poll context. + * + * @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); +} + +/** + * @brief Remove events from a poll object. Non-existent are ignored. + * The events will also be propagated to an associated poll context. + * + * @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); +} + +/** + * @brief Get the raw socket of a poll object. + * + * @param p Pointer to an already allocated poll object. + * + * @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; + } + + return p->x.fd; +} +/** + * @brief Set the callback of a poll object. + * + * @param p Pointer to an already allocated poll object. + * @param cb Function to be called if any of the events are set. + * @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; + } +} + +/** + * @brief Create a new poll context. It could be associated with many poll object + * which are going to be polled at the same time as the poll context. You + * would need a single poll context per thread. + * + * @param chunk_size The size of the memory chunk that will be allocated, when + * more memory is needed. This is for efficiency reasons, + * i.e. don't allocate memory for each new poll object, but + * 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 ctx; + + ctx = malloc(sizeof(struct ssh_poll_ctx_struct)); + if (ctx == NULL) { + return NULL; + } + ZERO_STRUCTP(ctx); + + if (chunk_size == 0) { + chunk_size = SSH_POLL_CTX_CHUNK; + } + + ctx->chunk_size = chunk_size; + + return ctx; +} + +/** + * @brief Free a poll context. + * + * @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); + } + + SAFE_FREE(ctx->pollptrs); + SAFE_FREE(ctx->pollfds); + } + + SAFE_FREE(ctx); +} + +static int ssh_poll_ctx_resize(ssh_poll_ctx ctx, size_t new_size) { + ssh_poll_handle *pollptrs; + ssh_pollfd_t *pollfds; + + pollptrs = realloc(ctx->pollptrs, sizeof(ssh_poll_handle) * new_size); + if (pollptrs == NULL) { + return -1; + } + ctx->pollptrs = pollptrs; + + pollfds = realloc(ctx->pollfds, sizeof(ssh_pollfd_t) * new_size); + if (pollfds == NULL) { + pollptrs = realloc(ctx->pollptrs, sizeof(ssh_poll_handle) * ctx->polls_allocated); + if (pollptrs == NULL) { + return -1; + } + ctx->pollptrs = pollptrs; + return -1; + } + + ctx->pollfds = pollfds; + ctx->polls_allocated = new_size; + + return 0; +} + +/** + * @brief Add a poll object to a poll context. + * + * @param ctx Pointer to an already allocated poll context. + * @param p Pointer to an already allocated poll object. + * + * @return 0 on success, < 0 on error + */ +int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p) { + socket_t fd; + + if (p->ctx != NULL) { + /* already attached to a context */ + return -1; + } + + if (ctx->polls_used == ctx->polls_allocated && + ssh_poll_ctx_resize(ctx, ctx->polls_allocated + ctx->chunk_size) < 0) { + return -1; + } + + fd = p->x.fd; + p->x.idx = ctx->polls_used++; + ctx->pollptrs[p->x.idx] = p; + ctx->pollfds[p->x.idx].fd = fd; + ctx->pollfds[p->x.idx].events = p->events; + ctx->pollfds[p->x.idx].revents = 0; + p->ctx = ctx; + + return 0; +} + +/** + * @brief Add a socket object to a poll context. + * + * @param ctx Pointer to an already allocated poll context. + * @param s A SSH socket handle + * + * @return 0 on success, < 0 on error + */ +int ssh_poll_ctx_add_socket (ssh_poll_ctx ctx, ssh_socket s) +{ + ssh_poll_handle p; + int ret; + + p = ssh_socket_get_poll_handle(s); + if (p == NULL) { + return -1; + } + ret = ssh_poll_ctx_add(ctx,p); + return ret; +} + + +/** + * @brief Remove a poll object from a poll context. + * + * @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) { + size_t i; + + i = p->x.idx; + p->x.fd = ctx->pollfds[i].fd; + p->ctx = NULL; + + ctx->polls_used--; + + /* fill the empty poll slot with the last one */ + if (ctx->polls_used > 0 && ctx->polls_used != i) { + ctx->pollfds[i] = ctx->pollfds[ctx->polls_used]; + ctx->pollptrs[i] = ctx->pollptrs[ctx->polls_used]; + ctx->pollptrs[i]->x.idx = i; + } + + /* this will always leave at least chunk_size polls allocated */ + if (ctx->polls_allocated - ctx->polls_used > ctx->chunk_size) { + ssh_poll_ctx_resize(ctx, ctx->polls_allocated - ctx->chunk_size); + } +} + +/** + * @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. + * + * @param ctx Pointer to an already allocated poll context. + * @param timeout An upper limit on the time for which ssh_poll_ctx() will + * block, in milliseconds. Specifying a negative value + * means an infinite timeout. This parameter is passed to + * the poll() function. + * @returns SSH_OK No error. + * SSH_ERROR Error happened during the poll. + * SSH_AGAIN Timeout occured + */ + +int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) +{ + int rc; + size_t i, used; + ssh_poll_handle p; + socket_t fd; + int revents; + struct ssh_timestamp ts; + + if (ctx->polls_used == 0) { + return SSH_ERROR; + } + + ssh_timestamp_init(&ts); + do { + int tm = ssh_timeout_update(&ts, timeout); + rc = ssh_poll(ctx->pollfds, ctx->polls_used, tm); + } while (rc == -1 && errno == EINTR); + + if (rc < 0) { + return SSH_ERROR; + } + if (rc == 0) { + return SSH_AGAIN; + } + + used = ctx->polls_used; + for (i = 0; i < used && rc > 0; ) { + if (!ctx->pollfds[i].revents || ctx->pollptrs[i]->lock) { + 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; + if (p->cb && (ret = p->cb(p, fd, revents, p->cb_data)) < 0) { + if (ret == -2) { + return -1; + } + /* the poll was removed, reload the used counter and start again */ + used = ctx->polls_used; + i = 0; + } else { + ctx->pollfds[i].revents = 0; + ctx->pollfds[i].events = p->events; + p->lock = 0; + i++; + } + + rc--; + } + } + + return rc; +} + +/** + * @internal + * @brief gets the default poll structure for the current session, + * when used in blocking mode. + * @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; +} + +/* public event API */ + +struct ssh_event_fd_wrapper { + ssh_event_callback cb; + void * userdata; +}; + +struct ssh_event_struct { + ssh_poll_ctx ctx; +#ifdef WITH_SERVER + struct ssh_list *sessions; +#endif +}; + +/** + * @brief Create a new event context. It could be associated with many + * 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 event; + + event = malloc(sizeof(struct ssh_event_struct)); + if (event == NULL) { + return NULL; + } + ZERO_STRUCTP(event); + + event->ctx = ssh_poll_ctx_new(2); + if(event->ctx == NULL) { + free(event); + return NULL; + } + +#ifdef WITH_SERVER + event->sessions = ssh_list_new(); + if(event->sessions == NULL) { + ssh_poll_ctx_free(event->ctx); + free(event); + return NULL; + } +#endif + + return event; +} + +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) { + return pw->cb(fd, revents, pw->userdata); + } + return 0; +} + +/** + * @brief Add a fd to the event and assign it a callback, + * when used in blocking mode. + * @param event The ssh_event + * @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_event_callback)(socket_t fd, int revents, + * void *userdata); + * @param userdata Userdata to be passed to the callback function. NULL if + * not needed. + * + * @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) { + 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; + } + pw = malloc(sizeof(struct ssh_event_fd_wrapper)); + if(pw == NULL) { + return SSH_ERROR; + } + + pw->cb = cb; + pw->userdata = userdata; + + /* pw is freed by ssh_event_remove_fd */ + p = ssh_poll_new(fd, events, ssh_event_fd_wrapper_callback, pw); + if(p == NULL) { + free(pw); + return SSH_ERROR; + } + + if(ssh_poll_ctx_add(event->ctx, p) < 0) { + free(pw); + ssh_poll_free(p); + return SSH_ERROR; + } + return SSH_OK; +} + +/** + * @brief Add a poll handle to the event. + * + * @param event the ssh_event + * + * @param p the poll handle + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_add_poll(ssh_event event, ssh_poll_handle p) +{ + return ssh_poll_ctx_add(event->ctx, p); +} + +/** + * @brief remove a poll handle to the event. + * + * @param event the ssh_event + * + * @param p the poll handle + */ +void ssh_event_remove_poll(ssh_event event, ssh_poll_handle p) +{ + ssh_poll_ctx_remove(event->ctx,p); +} + +/** + * @brief remove the poll handle from session and assign them to a event, + * when used in blocking mode. + * + * @param event The ssh_event object + * @param session The session to add to the event. + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_add_session(ssh_event event, ssh_session session) { + ssh_poll_handle p; +#ifdef WITH_SERVER + struct ssh_iterator *iterator; +#endif + + if(event == NULL || event->ctx == NULL || session == NULL) { + return SSH_ERROR; + } + if(session->default_poll_ctx == NULL) { + return SSH_ERROR; + } + while (session->default_poll_ctx->polls_used > 0) { + p = session->default_poll_ctx->pollptrs[0]; + /* + * ssh_poll_ctx_remove() decrements + * session->default_poll_ctx->polls_used + */ + ssh_poll_ctx_remove(session->default_poll_ctx, p); + ssh_poll_ctx_add(event->ctx, p); + /* associate the pollhandler with a session so we can put it back + * at ssh_event_free() + */ + p->session = session; + } +#ifdef WITH_SERVER + iterator = ssh_list_get_iterator(event->sessions); + while(iterator != NULL) { + if((ssh_session)iterator->data == session) { + /* allow only one instance of this session */ + return SSH_OK; + } + iterator = iterator->next; + } + if(ssh_list_append(event->sessions, session) == SSH_ERROR) { + return SSH_ERROR; + } +#endif + return SSH_OK; +} + +/** + * @brief Add a connector to the SSH event loop + * + * @param[in] event The SSH event loop + * + * @param[in] connector The connector object + * + * @return SSH_OK + * + * @return SSH_ERROR in case of error + */ +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 + * + * 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. + * + * @param event The ssh_event object to poll. + * + * @param timeout An upper limit on the time for which the poll will + * block, in milliseconds. Specifying a negative value + * 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 + */ +int ssh_event_dopoll(ssh_event event, int timeout) { + int rc; + + if(event == NULL || event->ctx == NULL) { + return SSH_ERROR; + } + rc = ssh_poll_ctx_dopoll(event->ctx, timeout); + return rc; +} + +/** + * @brief Remove a socket fd from an event context. + * + * @param event The ssh_event object. + * @param fd The fd to remove. + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_remove_fd(ssh_event event, socket_t fd) { + register size_t i, used; + int rc = SSH_ERROR; + + if(event == NULL || event->ctx == NULL) { + return SSH_ERROR; + } + + used = event->ctx->polls_used; + for (i = 0; i < used; i++) { + if(fd == event->ctx->pollfds[i].fd) { + ssh_poll_handle p = event->ctx->pollptrs[i]; + if (p->session != NULL){ + /* we cannot free that handle, it's owned by its session */ + continue; + } + if (p->cb == ssh_event_fd_wrapper_callback) { + struct ssh_event_fd_wrapper *pw = p->cb_data; + SAFE_FREE(pw); + } + + /* + * The free function calls ssh_poll_ctx_remove() and decrements + * event->ctx->polls_used. + */ + ssh_poll_free(p); + rc = SSH_OK; + + /* restart the loop */ + used = event->ctx->polls_used; + i = 0; + } + } + + return rc; +} + +/** + * @brief Remove a session object from an event context. + * + * @param event The ssh_event object. + * @param session The session to remove. + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_remove_session(ssh_event event, ssh_session session) { + ssh_poll_handle p; + register size_t i, used; + int rc = SSH_ERROR; +#ifdef WITH_SERVER + struct ssh_iterator *iterator; +#endif + + 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){ + /* + * ssh_poll_ctx_remove() decrements + * event->ctx->polls_used + */ + ssh_poll_ctx_remove(event->ctx, p); + p->session = NULL; + ssh_poll_ctx_add(session->default_poll_ctx, p); + rc = SSH_OK; + /* + * Restart the loop! + * A session can initially have two pollhandlers. + */ + used = event->ctx->polls_used; + i = 0; + + } + } +#ifdef WITH_SERVER + iterator = ssh_list_get_iterator(event->sessions); + 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; + } + iterator = iterator->next; + } +#endif + + return rc; +} + +/** @brief Remove a connector from an event context + * @param[in] event The ssh_event object. + * @param[in] connector connector object to remove + * @return SSH_OK on success + * @return SSH_ERROR on failure + */ +int ssh_event_remove_connector(ssh_event event, ssh_connector connector){ + (void)event; + return ssh_connector_remove_event(connector); +} + +/** + * @brief Free an event context. + * + * @param event The ssh_event object to free. + * Note: you have to manually remove sessions and socket + * fds before freeing the event object. + * + */ +void ssh_event_free(ssh_event event) +{ + size_t used, i; + ssh_poll_handle p; + + if(event == NULL) { + return; + } + + if (event->ctx != NULL) { + used = event->ctx->polls_used; + for(i = 0; i < used; i++) { + p = event->ctx->pollptrs[i]; + if (p->session != NULL) { + ssh_poll_ctx_remove(event->ctx, p); + ssh_poll_ctx_add(p->session->default_poll_ctx, p); + p->session = NULL; + used = 0; + } + } + + ssh_poll_ctx_free(event->ctx); + } +#ifdef WITH_SERVER + if(event->sessions != NULL) { + ssh_list_free(event->sessions); + } +#endif + free(event); +} + +/** @} */ diff --git a/src/scp.c b/src/scp.c new file mode 100644 index 0000000..85d670a --- /dev/null +++ b/src/scp.c @@ -0,0 +1,1119 @@ +/* + * scp - SSH scp wrapper functions + * + * 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 +#include +#include + +#include "libssh/priv.h" +#include "libssh/scp.h" +#include "libssh/misc.h" + +/** + * @defgroup libssh_scp The SSH scp functions + * @ingroup libssh + * + * SCP protocol over SSH functions + * + * @{ + */ + +/** + * @brief Create a new scp session. + * + * @param[in] session The SSH session to use. + * + * @param[in] mode One of SSH_SCP_WRITE or SSH_SCP_READ, depending if you + * need to drop files remotely or read them. + * It is not possible to combine read and write. + * SSH_SCP_RECURSIVE Flag can be or'ed to this to indicate + * that you're going to use recursion. Browsing through + * directories is not possible without this. + * + * @param[in] location The directory in which write or read will be done. Any + * push or pull will be relative to this place. + * This can also be a pattern of files to download (read). + * + * @returns A ssh_scp handle, NULL if the creation was impossible. + */ +ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location) +{ + ssh_scp scp = NULL; + + if (session == NULL) { + goto error; + } + + scp = (ssh_scp)calloc(1, sizeof(struct ssh_scp_struct)); + if (scp == NULL) { + ssh_set_error(session, SSH_FATAL, + "Error allocating memory for ssh_scp"); + goto error; + } + + if ((mode & ~SSH_SCP_RECURSIVE) != SSH_SCP_WRITE && + (mode & ~SSH_SCP_RECURSIVE) != SSH_SCP_READ) + { + ssh_set_error(session, SSH_FATAL, + "Invalid mode %d for ssh_scp_new()", mode); + goto error; + } + + if (strlen(location) > 32 * 1024) { + ssh_set_error(session, SSH_FATAL, + "Location path is too long"); + goto error; + } + + scp->location = strdup(location); + if (scp->location == NULL) { + ssh_set_error(session, SSH_FATAL, + "Error allocating memory for ssh_scp"); + goto error; + } + + scp->session = session; + scp->mode = mode & ~SSH_SCP_RECURSIVE; + scp->recursive = (mode & SSH_SCP_RECURSIVE) != 0; + scp->channel = NULL; + scp->state = SSH_SCP_NEW; + + return scp; + +error: + ssh_scp_free(scp); + return NULL; +} + +/** + * @brief Initialize the scp channel. + * + * @param[in] scp The scp context to initialize. + * + * @return SSH_OK on success or an SSH error code. + * + * @see ssh_scp_new() + */ +int ssh_scp_init(ssh_scp scp) +{ + int rc; + char execbuffer[1024] = {0}; + char *quoted_location = NULL; + size_t quoted_location_len = 0; + size_t scp_location_len; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_NEW) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_init called under invalid state"); + return SSH_ERROR; + } + + if (scp->location == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Invalid scp context: location is NULL"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Initializing scp session %s %son location '%s'", + scp->mode == SSH_SCP_WRITE?"write":"read", + scp->recursive ? "recursive " : "", + scp->location); + + scp->channel = ssh_channel_new(scp->session); + if (scp->channel == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Channel creation failed for scp"); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_channel_open_session(scp->channel); + if (rc == SSH_ERROR) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to open channel for scp"); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + /* In the worst case, each character would be replaced by 3 plus the string + * terminator '\0' */ + scp_location_len = strlen(scp->location); + quoted_location_len = ((size_t)3 * scp_location_len) + 1; + /* Paranoia check */ + if (quoted_location_len < scp_location_len) { + ssh_set_error(scp->session, SSH_FATAL, + "Buffer overflow detected"); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + quoted_location = (char *)calloc(1, quoted_location_len); + if (quoted_location == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to allocate memory for quoted location"); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_quote_file_name(scp->location, quoted_location, + quoted_location_len); + if (rc <= 0) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to single quote command location"); + SAFE_FREE(quoted_location); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + if (scp->mode == SSH_SCP_WRITE) { + snprintf(execbuffer, sizeof(execbuffer), "scp -t %s %s", + scp->recursive ? "-r" : "", quoted_location); + } else { + snprintf(execbuffer, sizeof(execbuffer), "scp -f %s %s", + scp->recursive ? "-r" : "", quoted_location); + } + + SAFE_FREE(quoted_location); + + SSH_LOG(SSH_LOG_DEBUG, "Executing command: %s", execbuffer); + + rc = ssh_channel_request_exec(scp->channel, execbuffer); + if (rc == SSH_ERROR){ + ssh_set_error(scp->session, SSH_FATAL, + "Failed executing command: %s", execbuffer); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + if (scp->mode == SSH_SCP_WRITE) { + rc = ssh_scp_response(scp, NULL); + if (rc != 0) { + return SSH_ERROR; + } + } else { + ssh_channel_write(scp->channel, "", 1); + } + + if (scp->mode == SSH_SCP_WRITE) { + scp->state = SSH_SCP_WRITE_INITED; + } else { + scp->state = SSH_SCP_READ_INITED; + } + + return SSH_OK; +} + +/** + * @brief Close the scp channel. + * + * @param[in] scp The scp context to close. + * + * @return SSH_OK on success or an SSH error code. + * + * @see ssh_scp_init() + */ +int ssh_scp_close(ssh_scp scp) +{ + char buffer[128] = {0}; + int rc; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->channel != NULL) { + if (ssh_channel_send_eof(scp->channel) == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + /* avoid situations where data are buffered and + * not yet stored on disk. This can happen if the close is sent + * before we got the EOF back + */ + while (!ssh_channel_is_eof(scp->channel)) { + rc = ssh_channel_read(scp->channel, buffer, sizeof(buffer), 0); + if (rc == SSH_ERROR || rc == 0) { + break; + } + } + + if (ssh_channel_close(scp->channel) == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + ssh_channel_free(scp->channel); + scp->channel = NULL; + } + + scp->state = SSH_SCP_NEW; + return SSH_OK; +} + +/** + * @brief Free a scp context. + * + * @param[in] scp The context to free. + * + * @see ssh_scp_new() + */ +void ssh_scp_free(ssh_scp scp) +{ + if (scp == NULL) { + return; + } + + if (scp->state != SSH_SCP_NEW) { + ssh_scp_close(scp); + } + + if (scp->channel) { + ssh_channel_free(scp->channel); + } + + SAFE_FREE(scp->location); + SAFE_FREE(scp->request_name); + SAFE_FREE(scp->warning); + SAFE_FREE(scp); +} + +/** + * @brief Create a directory in a scp in sink mode. + * + * @param[in] scp The scp handle. + * + * @param[in] dirname The name of the directory being created. + * + * @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. + * + * @see ssh_scp_leave_directory() + */ +int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode) +{ + char buffer[1024] = {0}; + int rc; + char *dir = NULL; + char *perms = NULL; + char *vis_encoded = NULL; + size_t vis_encoded_len; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_WRITE_INITED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_push_directory called under invalid state"); + return SSH_ERROR; + } + + dir = ssh_basename(dirname); + if (dir == NULL) { + ssh_set_error_oom(scp->session); + return SSH_ERROR; + } + + vis_encoded_len = (2 * strlen(dir)) + 1; + vis_encoded = (char *)calloc(1, vis_encoded_len); + if (vis_encoded == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to allocate buffer to vis encode directory name"); + goto error; + } + + rc = ssh_newline_vis(dir, vis_encoded, vis_encoded_len); + if (rc <= 0) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to vis encode directory name"); + goto error; + } + + perms = ssh_scp_string_mode(mode); + if (perms == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to get directory permission string"); + goto error; + } + + SSH_LOG(SSH_LOG_PROTOCOL, + "SCP pushing directory %s with permissions '%s'", + vis_encoded, perms); + + /* Use vis encoded directory name */ + snprintf(buffer, sizeof(buffer), + "D%s 0 %s\n", + perms, vis_encoded); + + SAFE_FREE(dir); + SAFE_FREE(perms); + SAFE_FREE(vis_encoded); + + rc = ssh_channel_write(scp->channel, buffer, strlen(buffer)); + if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_scp_response(scp, NULL); + if (rc != 0) { + return SSH_ERROR; + } + + return SSH_OK; + +error: + SAFE_FREE(dir); + SAFE_FREE(perms); + SAFE_FREE(vis_encoded); + + return SSH_ERROR; +} + +/** + * @brief Leave a directory. + * + * @returns SSH_OK if the directory has been left, SSH_ERROR if an + * error occured. + * + * @see ssh_scp_push_directory() + */ +int ssh_scp_leave_directory(ssh_scp scp) +{ + char buffer[] = "E\n"; + int rc; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_WRITE_INITED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_leave_directory called under invalid state"); + return SSH_ERROR; + } + + rc = ssh_channel_write(scp->channel, buffer, strlen(buffer)); + if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_scp_response(scp, NULL); + if (rc != 0) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @brief Initialize the sending of a file to a scp in sink mode, using a 64-bit + * size. + * + * @param[in] scp The scp handle. + * + * @param[in] filename The name of the file being sent. It should not contain + * any path indicator + * + * @param[in] size Exact size in bytes of the file being sent. + * + * @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. + * + * @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}; + int rc; + char *file = NULL; + char *perms = NULL; + char *vis_encoded = NULL; + size_t vis_encoded_len; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_WRITE_INITED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_push_file called under invalid state"); + return SSH_ERROR; + } + + file = ssh_basename(filename); + if (file == NULL) { + ssh_set_error_oom(scp->session); + return SSH_ERROR; + } + + vis_encoded_len = (2 * strlen(file)) + 1; + vis_encoded = (char *)calloc(1, vis_encoded_len); + if (vis_encoded == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to allocate buffer to vis encode file name"); + goto error; + } + + rc = ssh_newline_vis(file, vis_encoded, vis_encoded_len); + if (rc <= 0) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to vis encode file name"); + goto error; + } + + perms = ssh_scp_string_mode(mode); + if (perms == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to get file permission string"); + goto error; + } + + SSH_LOG(SSH_LOG_PROTOCOL, + "SCP pushing file %s, size %" PRIu64 " with permissions '%s'", + vis_encoded, size, perms); + + /* Use vis encoded file name */ + snprintf(buffer, sizeof(buffer), + "C%s %" PRIu64 " %s\n", + perms, size, vis_encoded); + + SAFE_FREE(file); + SAFE_FREE(perms); + SAFE_FREE(vis_encoded); + + rc = ssh_channel_write(scp->channel, buffer, strlen(buffer)); + if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_scp_response(scp, NULL); + if (rc != 0) { + return SSH_ERROR; + } + + scp->filelen = size; + scp->processed = 0; + scp->state = SSH_SCP_WRITE_WRITING; + + return SSH_OK; + +error: + SAFE_FREE(file); + SAFE_FREE(perms); + SAFE_FREE(vis_encoded); + + return SSH_ERROR; +} + +/** + * @brief Initialize the sending of a file to a scp in sink mode. + * + * @param[in] scp The scp handle. + * + * @param[in] filename The name of the file being sent. It should not contain + * any path indicator + * + * @param[in] size Exact size in bytes of the file being sent. + * + * @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. + */ +int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int mode) +{ + return ssh_scp_push_file64(scp, filename, (uint64_t) size, mode); +} + +/** + * @internal + * + * @brief Wait for a response of the scp server. + * + * @param[in] scp The scp handle. + * + * @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. + */ +int ssh_scp_response(ssh_scp scp, char **response) +{ + unsigned char code; + int rc; + char msg[128] = {0}; + + if (scp == NULL) { + return SSH_ERROR; + } + + rc = ssh_channel_read(scp->channel, &code, 1, 0); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } + + if (code == 0) { + return 0; + } + + if (code > 2) { + ssh_set_error(scp->session, SSH_FATAL, + "SCP: invalid status code %u received", code); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_scp_read_string(scp, msg, sizeof(msg)); + if (rc == SSH_ERROR) { + return rc; + } + + /* Warning */ + if (code == 1) { + ssh_set_error(scp->session, SSH_REQUEST_DENIED, + "SCP: Warning: status code 1 received: %s", msg); + SSH_LOG(SSH_LOG_RARE, + "SCP: Warning: status code 1 received: %s", msg); + if (response) { + *response = strdup(msg); + } + return 1; + } + + if (code == 2) { + ssh_set_error(scp->session, SSH_FATAL, + "SCP: Error: status code 2 received: %s", msg); + if (response) { + *response = strdup(msg); + } + return 2; + } + + /* Not reached */ + return SSH_ERROR; +} + +/** + * @brief Write into a remote scp file. + * + * @param[in] scp The scp handle. + * + * @param[in] buffer The buffer to write. + * + * @param[in] len The number of bytes to write. + * + * @returns SSH_OK if the write was successful, SSH_ERROR an error + * occured while writing. + */ +int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len) +{ + int w; + int rc; + uint8_t code; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_WRITE_WRITING) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_write called under invalid state"); + return SSH_ERROR; + } + + if (scp->processed + len > scp->filelen) { + len = (size_t) (scp->filelen - scp->processed); + } + + /* hack to avoid waiting for window change */ + rc = ssh_channel_poll(scp->channel, 0); + if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + w = ssh_channel_write(scp->channel, buffer, len); + if (w != SSH_ERROR) { + scp->processed += w; + } else { + scp->state = SSH_SCP_ERROR; + //return = channel_get_exit_status(scp->channel); + return SSH_ERROR; + } + + /* Far end sometimes send a status message, which we need to read + * and handle */ + rc = ssh_channel_poll(scp->channel, 0); + if (rc > 0) { + rc = ssh_scp_response(scp, NULL); + if (rc != 0) { + return SSH_ERROR; + } + } + + /* Check if we arrived at end of file */ + if (scp->processed == scp->filelen) { + code = 0; + w = ssh_channel_write(scp->channel, &code, 1); + if (w == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + scp->processed = scp->filelen = 0; + scp->state = SSH_SCP_WRITE_INITED; + } + + return SSH_OK; +} + +/** + * @brief Read a string on a channel, terminated by '\n' + * + * @param[in] scp The scp handle. + * + * @param[out] buffer A pointer to a buffer to place the string. + * + * @param[in] len The size of the buffer in bytes. If the string is bigger + * than len-1, only len-1 bytes are read and the string is + * null-terminated. + * + * @returns SSH_OK if the string was read, SSH_ERROR if an error + * occured while reading. + */ +int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len) +{ + size_t read = 0; + int err = SSH_OK; + + if (scp == NULL) { + return SSH_ERROR; + } + + while (read < len - 1) { + err = ssh_channel_read(scp->channel, &buffer[read], 1, 0); + if (err == SSH_ERROR) { + break; + } + + if (err == 0) { + ssh_set_error(scp->session, SSH_FATAL, + "End of file while reading string"); + err = SSH_ERROR; + break; + } + + read++; + if (buffer[read - 1] == '\n') { + break; + } + } + + buffer[read] = 0; + return err; +} + +/** + * @brief Wait for a scp request (file, directory). + * + * @returns SSH_SCP_REQUEST_NEWFILE: The other side is sending + * a file + * SSH_SCP_REQUEST_NEWDIR: The other side is sending + * a directory + * SSH_SCP_REQUEST_ENDDIR: The other side has + * finished with the current + * directory + * SSH_SCP_REQUEST_WARNING: The other side sent us a warning + * SSH_SCP_REQUEST_EOF: The other side finished sending us + * files and data. + * SSH_ERROR: Some error happened + * + * @see ssh_scp_read() + * @see ssh_scp_deny_request() + * @see ssh_scp_accept_request() + * @see ssh_scp_request_get_warning() + */ +int ssh_scp_pull_request(ssh_scp scp) +{ + char buffer[MAX_BUF_SIZE] = {0}; + char *mode = NULL; + char *p, *tmp; + uint64_t size; + char *name = NULL; + int rc; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_READ_INITED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_pull_request called under invalid state"); + return SSH_ERROR; + } + + rc = ssh_scp_read_string(scp, buffer, sizeof(buffer)); + if (rc == SSH_ERROR) { + if (ssh_channel_is_eof(scp->channel)) { + scp->state = SSH_SCP_TERMINATED; + return SSH_SCP_REQUEST_EOF; + } + return rc; + } + + p = strchr(buffer, '\n'); + if (p != NULL) { + *p = '\0'; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Received SCP request: '%s'", buffer); + switch(buffer[0]) { + case 'C': + /* File */ + case 'D': + /* Directory */ + p = strchr(buffer, ' '); + if (p == NULL) { + goto error; + } + *p = '\0'; + p++; + //mode = strdup(&buffer[1]); + scp->request_mode = ssh_scp_integer_mode(&buffer[1]); + tmp = p; + p = strchr(p, ' '); + if (p == NULL) { + goto error; + } + *p = 0; + size = strtoull(tmp, NULL, 10); + p++; + name = strdup(p); + SAFE_FREE(scp->request_name); + scp->request_name = name; + if (buffer[0] == 'C') { + scp->filelen = size; + scp->request_type = SSH_SCP_REQUEST_NEWFILE; + } else { + scp->filelen = '0'; + scp->request_type = SSH_SCP_REQUEST_NEWDIR; + } + scp->state = SSH_SCP_READ_REQUESTED; + scp->processed = 0; + return scp->request_type; + break; + case 'E': + scp->request_type = SSH_SCP_REQUEST_ENDDIR; + ssh_channel_write(scp->channel, "", 1); + return scp->request_type; + case 0x1: + ssh_set_error(scp->session, SSH_REQUEST_DENIED, + "SCP: Warning: %s", &buffer[1]); + scp->request_type = SSH_SCP_REQUEST_WARNING; + SAFE_FREE(scp->warning); + scp->warning = strdup(&buffer[1]); + return scp->request_type; + case 0x2: + ssh_set_error(scp->session, SSH_FATAL, + "SCP: Error: %s", &buffer[1]); + return SSH_ERROR; + case 'T': + /* Timestamp */ + default: + ssh_set_error(scp->session, SSH_FATAL, + "Unhandled message: (%d)%s", buffer[0], buffer); + return SSH_ERROR; + } + + /* a parsing error occured */ +error: + SAFE_FREE(name); + SAFE_FREE(mode); + ssh_set_error(scp->session, SSH_FATAL, + "Parsing error while parsing message: %s", buffer); + return SSH_ERROR; +} + +/** + * @brief Deny the transfer of a file or creation of a directory coming from the + * remote party. + * + * @param[in] scp The scp handle. + * @param[in] reason A nul-terminated string with a human-readable + * explanation of the deny. + * + * @returns SSH_OK if the message was sent, SSH_ERROR if the sending + * the message failed, or sending it in a bad state. + */ +int ssh_scp_deny_request(ssh_scp scp, const char *reason) +{ + char buffer[MAX_BUF_SIZE] = {0}; + int rc; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_READ_REQUESTED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_deny_request called under invalid state"); + return SSH_ERROR; + } + + snprintf(buffer, sizeof(buffer), "%c%s\n", 2, reason); + rc = ssh_channel_write(scp->channel, buffer, strlen(buffer)); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } + + else { + scp->state = SSH_SCP_READ_INITED; + return SSH_OK; + } +} + +/** + * @brief Accepts transfer of a file or creation of a directory coming from the + * remote party. + * + * @param[in] scp The scp handle. + * + * @returns SSH_OK if the message was sent, SSH_ERROR if sending the + * message failed, or sending it in a bad state. + */ +int ssh_scp_accept_request(ssh_scp scp) +{ + char buffer[] = {0x00}; + int rc; + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_READ_REQUESTED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_deny_request called under invalid state"); + return SSH_ERROR; + } + + rc = ssh_channel_write(scp->channel, buffer, 1); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } + + if (scp->request_type == SSH_SCP_REQUEST_NEWFILE) { + scp->state = SSH_SCP_READ_READING; + } else { + scp->state = SSH_SCP_READ_INITED; + } + + return SSH_OK; +} + +/** @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 + * while reading. + */ +int ssh_scp_read(ssh_scp scp, void *buffer, size_t size) +{ + int rc; + int code; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state == SSH_SCP_READ_REQUESTED && + scp->request_type == SSH_SCP_REQUEST_NEWFILE) + { + rc = ssh_scp_accept_request(scp); + if (rc == SSH_ERROR) { + return rc; + } + } + + if (scp->state != SSH_SCP_READ_READING) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_read called under invalid state"); + return SSH_ERROR; + } + + if (scp->processed + size > scp->filelen) { + size = (size_t) (scp->filelen - scp->processed); + } + + if (size > 65536) { + size = 65536; /* avoid too large reads */ + } + + rc = ssh_channel_read(scp->channel, buffer, size, 0); + if (rc != SSH_ERROR) { + scp->processed += rc; + } else { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + /* Check if we arrived at end of file */ + if (scp->processed == scp->filelen) { + scp->processed = scp->filelen = 0; + ssh_channel_write(scp->channel, "", 1); + code = ssh_scp_response(scp, NULL); + if (code == 0) { + scp->state = SSH_SCP_READ_INITED; + return rc; + } + if (code == 1) { + scp->state = SSH_SCP_READ_INITED; + return SSH_ERROR; + } + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + return rc; +} + +/** + * @brief Get the name of the directory or file being pushed from the other + * party. + * + * @returns The file name, NULL on error. The string should not be + * freed. + */ +const char *ssh_scp_request_get_filename(ssh_scp scp) +{ + if (scp == NULL) { + return NULL; + } + + return scp->request_name; +} + +/** + * @brief Get the permissions of the directory or file being pushed from the + * other party. + * + * @returns The UNIX permission, e.g 0644, -1 on error. + */ +int ssh_scp_request_get_permissions(ssh_scp scp) +{ + if (scp == NULL) { + return -1; + } + + return scp->request_mode; +} + +/** @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 + * be truncated. + * @see ssh_scp_request_get_size64() + */ +size_t ssh_scp_request_get_size(ssh_scp scp) +{ + if (scp == NULL) { + return 0; + } + return (size_t)scp->filelen; +} + +/** @brief Get the size of the file being pushed from the other party. + * + * @returns The numeric size of the file being read. + */ +uint64_t ssh_scp_request_get_size64(ssh_scp scp) +{ + if (scp == NULL) { + return 0; + } + return scp->filelen; +} + +/** + * @brief Convert a scp text mode to an integer. + * + * @param[in] mode The mode to convert, e.g. "0644". + * + * @returns An integer value, e.g. 420 for "0644". + */ +int ssh_scp_integer_mode(const char *mode) +{ + int value = strtoul(mode, NULL, 8) & 0xffff; + return value; +} + +/** + * @brief Convert a unix mode into a scp string. + * + * @param[in] mode The mode to convert, e.g. 420 or 0644. + * + * @returns A pointer to a malloc'ed string containing the scp mode, + * e.g. "0644". + */ +char *ssh_scp_string_mode(int mode) +{ + char buffer[16] = {0}; + snprintf(buffer, sizeof(buffer), "%.4o", mode); + return strdup(buffer); +} + +/** + * @brief Get the warning string from a scp handle. + * + * @param[in] scp The scp handle. + * + * @returns A warning string, or NULL on error. The string should + * not be freed. + */ +const char *ssh_scp_request_get_warning(ssh_scp scp) +{ + if (scp == NULL) { + return NULL; + } + + return scp->warning; +} + +/** @} */ diff --git a/src/server.c b/src/server.c new file mode 100644 index 0000000..841a1c4 --- /dev/null +++ b/src/server.c @@ -0,0 +1,1181 @@ +/* + * server.c - functions for creating a SSH server + * + * This file is part of the SSH Library + * + * Copyright (c) 2004-2013 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 +#include +#include +#include +#include + +#ifdef _WIN32 +# include +# include + + /* + * is necessary for getaddrinfo before Windows XP, but it isn't + * available on some platforms like MinGW. + */ +# ifdef HAVE_WSPIAPI_H +# include +# endif +#else +# include +#endif + +#include "libssh/priv.h" +#include "libssh/libssh.h" +#include "libssh/server.h" +#include "libssh/ssh2.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/socket.h" +#include "libssh/session.h" +#include "libssh/kex.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/dh.h" +#include "libssh/messages.h" +#include "libssh/options.h" +#include "libssh/curve25519.h" +#include "libssh/token.h" + +#define set_status(session, status) do {\ + if (session->common.callbacks && session->common.callbacks->connect_status_function) \ + session->common.callbacks->connect_status_function(session->common.callbacks->userdata, status); \ + } while (0) + +/** + * @addtogroup libssh_server + * + * @{ + */ + +/** @internal + * + * @brief initialize the set of key exchange, hostkey, ciphers, MACs, and + * compression algorithms for the given ssh_session + * + * The selection of algorithms and keys used are determined by the + * options that are currently set in the given ssh_session structure. + */ + +int server_set_kex(ssh_session session) +{ + struct ssh_kex_struct *server = &session->next_crypto->server_kex; + int i, j, rc; + const char *wanted, *allowed; + char *kept; + char hostkeys[128] = {0}; + enum ssh_keytypes_e keytype; + size_t len; + int ok; + + ZERO_STRUCTP(server); + + ok = ssh_get_random(server->cookie, 16, 0); + if (!ok) { + ssh_set_error(session, SSH_FATAL, "PRNG error"); + return -1; + } + + if (session->srv.ed25519_key != NULL) { + snprintf(hostkeys, + sizeof(hostkeys), + "%s", + ssh_key_type_to_char(ssh_key_type(session->srv.ed25519_key))); + } +#ifdef HAVE_ECC + if (session->srv.ecdsa_key != NULL) { + len = strlen(hostkeys); + snprintf(hostkeys + len, sizeof(hostkeys) - len, + ",%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); + snprintf(hostkeys + len, sizeof(hostkeys) - len, + ",rsa-sha2-512,rsa-sha2-256"); + + len = strlen(hostkeys); + keytype = ssh_key_type(session->srv.rsa_key); + + snprintf(hostkeys + len, sizeof(hostkeys) - len, + ",%s", ssh_key_type_to_char(keytype)); + } + + if (strlen(hostkeys) == 0) { + return -1; + } + + if (session->opts.wanted_methods[SSH_HOSTKEYS]) { + allowed = session->opts.wanted_methods[SSH_HOSTKEYS]; + } else { + if (ssh_fips_mode()) { + allowed = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + allowed = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + } + + /* It is expected for the list of allowed hostkeys to be ordered by + * preference */ + kept = ssh_find_all_matching(hostkeys[0] == ',' ? hostkeys + 1 : hostkeys, + allowed); + if (kept == NULL) { + /* Nothing was allowed */ + return -1; + } + + rc = ssh_options_set_algo(session, + SSH_HOSTKEYS, + kept); + SAFE_FREE(kept); + if (rc < 0) { + return -1; + } + + for (i = 0; i < SSH_KEX_METHODS; i++) { + wanted = session->opts.wanted_methods[i]; + if (wanted == NULL) { + if (ssh_fips_mode()) { + wanted = ssh_kex_get_fips_methods(i); + } else { + wanted = ssh_kex_get_default_methods(i); + } + } + if (wanted == NULL) { + for (j = 0; j < i; j++) { + SAFE_FREE(server->methods[j]); + } + return -1; + } + + server->methods[i] = strdup(wanted); + if (server->methods[i] == NULL) { + for (j = 0; j < i; j++) { + SAFE_FREE(server->methods[j]); + } + return -1; + } + } + + return 0; +} + +int ssh_server_init_kex(ssh_session session) { + int i; + + if (session->session_state > SSH_SESSION_STATE_BANNER_RECEIVED) { + return SSH_ERROR; + } + + /* free any currently-set methods: server_set_kex will allocate new ones */ + for (i = 0; i < SSH_KEX_METHODS; i++) { + SAFE_FREE(session->next_crypto->server_kex.methods[i]); + } + + return server_set_kex(session); +} + +static int ssh_server_send_extensions(ssh_session session) { + int rc; + const char *hostkey_algorithms; + + SSH_LOG(SSH_LOG_PACKET, "Sending SSH_MSG_EXT_INFO"); + + if (session->opts.pubkey_accepted_types) { + hostkey_algorithms = session->opts.pubkey_accepted_types; + } else { + if (ssh_fips_mode()) { + hostkey_algorithms = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + /* There are no restrictions to the accepted public keys */ + hostkey_algorithms = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + } + + rc = ssh_buffer_pack(session->out_buffer, + "bdss", + SSH2_MSG_EXT_INFO, + 1, /* nr. of extensions */ + "server-sig-algs", + hostkey_algorithms); + if (rc != SSH_OK) { + goto error; + } + + if (ssh_packet_send(session) == SSH_ERROR) { + goto error; + } + + return 0; +error: + ssh_buffer_reinit(session->out_buffer); + + return -1; +} + +SSH_PACKET_CALLBACK(ssh_packet_kexdh_init){ + (void)packet; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_PACKET,"Received SSH_MSG_KEXDH_INIT"); + if(session->dh_handshake_state != DH_STATE_INIT){ + SSH_LOG(SSH_LOG_RARE,"Invalid state for SSH_MSG_KEXDH_INIT"); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; + } + + /* If first_kex_packet_follows guess was wrong, ignore this message. */ + if (session->first_kex_follows_guess_wrong != 0) { + SSH_LOG(SSH_LOG_RARE, "first_kex_packet_follows guess was wrong, " + "ignoring first SSH_MSG_KEXDH_INIT message"); + session->first_kex_follows_guess_wrong = 0; + + return SSH_PACKET_USED; + } + SSH_LOG(SSH_LOG_DEBUG, "Calling next KEXDH handler"); + return SSH_PACKET_NOT_USED; +} + +int +ssh_get_key_params(ssh_session session, + ssh_key *privkey, + enum ssh_digest_e *digest) +{ + ssh_key pubkey; + ssh_string pubkey_blob; + 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; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + *privkey = session->srv.ecdsa_key; + break; + case SSH_KEYTYPE_ED25519: + *privkey = session->srv.ed25519_key; + break; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + *privkey = NULL; + } + + *digest = session->srv.hostkey_digest; + rc = ssh_pki_export_privkey_to_pubkey(*privkey, &pubkey); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Could not get the public key from the private key"); + + return -1; + } + + rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); + ssh_key_free(pubkey); + if (rc < 0) { + ssh_set_error_oom(session); + return -1; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + ssh_set_error(session, + SSH_FATAL, + "Could not import server public key"); + return -1; + } + + return SSH_OK; +} + +/** + * @internal + * + * @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){ + 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) { + 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); + 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 */ + 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) { + 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) { + + /* + * 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); + } + } + + 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); + } + + return; +error: + ssh_socket_close(session->socket); + session->alive = 0; + session->session_state=SSH_SESSION_STATE_ERROR; +} + +/** + * @internal + * + * @brief Gets the banner from socket and saves it in session. + * Updates the session state + * + * @param data pointer to the beginning of header + * @param len size of the banner + * @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; + char *str = NULL; + size_t i; + int ret=0; + + for (i = 0; i < len; i++) { +#ifdef WITH_PCAP + if(session->pcap_ctx && buffer[i] == '\n') { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_IN, + buffer, + i + 1, + i + 1); + } +#endif + if (buffer[i] == '\r') { + buffer[i]='\0'; + } + + if (buffer[i] == '\n') { + buffer[i]='\0'; + + str = strdup(buffer); + /* number of bytes read */ + ret = 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; + } + + if(i > 127) { + /* Too big banner */ + session->session_state = SSH_SESSION_STATE_ERROR; + ssh_set_error(session, SSH_FATAL, "Receiving banner: too large banner"); + + return 0; + } + } + + return ret; +} + +/* returns 0 until the key exchange is not finished */ +static int ssh_server_kex_termination(void *s){ + ssh_session session = s; + if (session->session_state != SSH_SESSION_STATE_ERROR && + session->session_state != SSH_SESSION_STATE_AUTHENTICATING && + session->session_state != SSH_SESSION_STATE_DISCONNECTED) + return 0; + else + return 1; +} + +/* FIXME: auth_methods should be unsigned */ +void ssh_set_auth_methods(ssh_session session, int auth_methods) +{ + /* accept only methods in range */ + session->auth.supported_methods = (uint32_t)auth_methods & 0x3fU; +} + +/* Do the banner and key exchange */ +int ssh_handle_key_exchange(ssh_session session) { + int rc; + if (session->session_state != SSH_SESSION_STATE_NONE) + goto pending; + rc = ssh_send_banner(session, 1); + if (rc < 0) { + return SSH_ERROR; + } + + session->alive = 1; + + 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; + + rc = server_set_kex(session); + if (rc < 0) { + return SSH_ERROR; + } + pending: + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, + 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; + if (session->session_state == SSH_SESSION_STATE_ERROR || + session->session_state == SSH_SESSION_STATE_DISCONNECTED) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/* messages */ + +/** @internal + * replies to an SSH_AUTH packet with a default (denied) response. + */ +int ssh_auth_reply_default(ssh_session session,int partial) { + char methods_c[128] = {0}; + int rc = SSH_ERROR; + + + if (session->auth.supported_methods == 0) { + session->auth.supported_methods = SSH_AUTH_METHOD_PUBLICKEY | SSH_AUTH_METHOD_PASSWORD; + } + if (session->auth.supported_methods & SSH_AUTH_METHOD_PUBLICKEY) { + strncat(methods_c, "publickey,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + if (session->auth.supported_methods & SSH_AUTH_METHOD_GSSAPI_MIC){ + strncat(methods_c,"gssapi-with-mic,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + if (session->auth.supported_methods & SSH_AUTH_METHOD_INTERACTIVE) { + strncat(methods_c, "keyboard-interactive,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + if (session->auth.supported_methods & SSH_AUTH_METHOD_PASSWORD) { + strncat(methods_c, "password,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + if (session->auth.supported_methods & SSH_AUTH_METHOD_HOSTBASED) { + strncat(methods_c, "hostbased,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + + if (methods_c[0] == '\0' || methods_c[strlen(methods_c)-1] != ',') { + return SSH_ERROR; + } + + /* Strip the comma. */ + methods_c[strlen(methods_c) - 1] = '\0'; // strip the comma. We are sure there is at + + SSH_LOG(SSH_LOG_PACKET, + "Sending a auth failure. methods that can continue: %s", methods_c); + + rc = ssh_buffer_pack(session->out_buffer, + "bsb", + SSH2_MSG_USERAUTH_FAILURE, + methods_c, + partial ? 1 : 0); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + return SSH_ERROR; + } + rc = ssh_packet_send(session); + return rc; +} + +static int ssh_message_channel_request_open_reply_default(ssh_message msg) { + int rc; + + SSH_LOG(SSH_LOG_FUNCTIONS, "Refusing a channel"); + + rc = ssh_buffer_pack(msg->session->out_buffer, + "bdddd", + SSH2_MSG_CHANNEL_OPEN_FAILURE, + msg->channel_request_open.sender, + SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, + 0, /* reason is empty string */ + 0); /* language string */ + if (rc != SSH_OK){ + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + + rc = ssh_packet_send(msg->session); + return rc; +} + +static int ssh_message_channel_request_reply_default(ssh_message msg) { + uint32_t channel; + int rc; + + if (msg->channel_request.want_reply) { + channel = msg->channel_request.channel->remote_channel; + + SSH_LOG(SSH_LOG_PACKET, + "Sending a default channel_request denied to channel %d", channel); + + rc = ssh_buffer_pack(msg->session->out_buffer, + "bd", + SSH2_MSG_CHANNEL_FAILURE, + channel); + if (rc != SSH_OK){ + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + return ssh_packet_send(msg->session); + } + + SSH_LOG(SSH_LOG_PACKET, + "The client doesn't want to know the request failed!"); + + return SSH_OK; +} + +static int ssh_message_service_request_reply_default(ssh_message msg) { + /* The only return code accepted by specifications are success or disconnect */ + return ssh_message_service_reply_success(msg); +} + +int ssh_message_service_reply_success(ssh_message msg) { + ssh_session session; + int rc; + + if (msg == NULL) { + return SSH_ERROR; + } + session = msg->session; + + SSH_LOG(SSH_LOG_PACKET, + "Sending a SERVICE_ACCEPT for service %s", msg->service_request.service); + + rc = ssh_buffer_pack(session->out_buffer, + "bs", + SSH2_MSG_SERVICE_ACCEPT, + msg->service_request.service); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + return SSH_ERROR; + } + rc = ssh_packet_send(msg->session); + return rc; +} + +int ssh_message_global_request_reply_success(ssh_message msg, uint16_t bound_port) { + int rc; + + SSH_LOG(SSH_LOG_FUNCTIONS, "Accepting a global request"); + + if (msg->global_request.want_reply) { + if (ssh_buffer_add_u8(msg->session->out_buffer + , SSH2_MSG_REQUEST_SUCCESS) < 0) { + goto error; + } + + 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) { + ssh_set_error_oom(msg->session); + goto error; + } + } + + return ssh_packet_send(msg->session); + } + + 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!"); + } + + return SSH_OK; +error: + return SSH_ERROR; +} + +static int ssh_message_global_request_reply_default(ssh_message msg) { + SSH_LOG(SSH_LOG_FUNCTIONS, "Refusing a global request"); + + if (msg->global_request.want_reply) { + if (ssh_buffer_add_u8(msg->session->out_buffer + , SSH2_MSG_REQUEST_FAILURE) < 0) { + goto error; + } + return ssh_packet_send(msg->session); + } + SSH_LOG(SSH_LOG_PACKET, + "The client doesn't want to know the request failed!"); + + return SSH_OK; +error: + return SSH_ERROR; +} + +int ssh_message_reply_default(ssh_message msg) { + if (msg == NULL) { + return -1; + } + + switch(msg->type) { + case SSH_REQUEST_AUTH: + return ssh_auth_reply_default(msg->session, 0); + case SSH_REQUEST_CHANNEL_OPEN: + return ssh_message_channel_request_open_reply_default(msg); + case SSH_REQUEST_CHANNEL: + return ssh_message_channel_request_reply_default(msg); + case SSH_REQUEST_SERVICE: + return ssh_message_service_request_reply_default(msg); + case SSH_REQUEST_GLOBAL: + return ssh_message_global_request_reply_default(msg); + default: + SSH_LOG(SSH_LOG_PACKET, + "Don't know what to default reply to %d type", + msg->type); + break; + } + + return -1; +} + +const char *ssh_message_service_service(ssh_message msg){ + if (msg == NULL) { + return NULL; + } + return msg->service_request.service; +} + +const char *ssh_message_auth_user(ssh_message msg) { + if (msg == NULL) { + return NULL; + } + + return msg->auth_request.username; +} + +const char *ssh_message_auth_password(ssh_message msg){ + if (msg == NULL) { + return NULL; + } + + return msg->auth_request.password; +} + +ssh_key ssh_message_auth_pubkey(ssh_message msg) { + if (msg == NULL) { + return NULL; + } + + 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; + } + + return ssh_pki_convert_key_to_publickey(msg->auth_request.pubkey); +} + +enum ssh_publickey_state_e ssh_message_auth_publickey_state(ssh_message msg){ + if (msg == NULL) { + return -1; + } + return msg->auth_request.signature_state; +} + +int ssh_message_auth_kbdint_is_response(ssh_message msg) { + if (msg == NULL) { + return -1; + } + + return msg->auth_request.kbdint_response != 0; +} + +/* FIXME: methods should be unsigned */ +int ssh_message_auth_set_methods(ssh_message msg, int methods) { + if (msg == NULL || msg->session == NULL) { + return -1; + } + + if (methods < 0) { + return -1; + } + + msg->session->auth.supported_methods = (uint32_t)methods; + + return 0; +} + +int ssh_message_auth_interactive_request(ssh_message msg, const char *name, + const char *instruction, unsigned int num_prompts, + const char **prompts, char *echo) { + int rc; + unsigned int i = 0; + + if(name == NULL || instruction == NULL) { + return SSH_ERROR; + } + if(num_prompts > 0 && (prompts == NULL || echo == NULL)) { + return SSH_ERROR; + } + + rc = ssh_buffer_pack(msg->session->out_buffer, + "bsssd", + SSH2_MSG_USERAUTH_INFO_REQUEST, + name, + instruction, + "", /* language tag */ + num_prompts); + if (rc != SSH_OK){ + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + + for(i = 0; i < num_prompts; i++) { + rc = ssh_buffer_pack(msg->session->out_buffer, + "sb", + prompts[i], + echo[i] ? 1 : 0); + if (rc != SSH_OK){ + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + } + + rc = ssh_packet_send(msg->session); + + /* 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."); + + msg->session->kbdint = ssh_kbdint_new(); + if (msg->session->kbdint == NULL) { + ssh_set_error_oom(msg->session); + + return SSH_ERROR; + } + } else { + ssh_kbdint_clean(msg->session->kbdint); + } + + msg->session->kbdint->name = strdup(name); + if(msg->session->kbdint->name == NULL) { + ssh_set_error_oom(msg->session); + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_PACKET_USED; + } + msg->session->kbdint->instruction = strdup(instruction); + if(msg->session->kbdint->instruction == NULL) { + ssh_set_error_oom(msg->session); + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_PACKET_USED; + } + + msg->session->kbdint->nprompts = num_prompts; + if(num_prompts > 0) { + msg->session->kbdint->prompts = calloc(num_prompts, sizeof(char *)); + if (msg->session->kbdint->prompts == NULL) { + msg->session->kbdint->nprompts = 0; + ssh_set_error_oom(msg->session); + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_ERROR; + } + msg->session->kbdint->echo = calloc(num_prompts, sizeof(unsigned char)); + if (msg->session->kbdint->echo == NULL) { + ssh_set_error_oom(msg->session); + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_ERROR; + } + for (i = 0; i < num_prompts; i++) { + msg->session->kbdint->echo[i] = echo[i]; + msg->session->kbdint->prompts[i] = strdup(prompts[i]); + if (msg->session->kbdint->prompts[i] == NULL) { + ssh_set_error_oom(msg->session); + msg->session->kbdint->nprompts = i; + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_PACKET_USED; + } + } + } else { + msg->session->kbdint->prompts = NULL; + msg->session->kbdint->echo = NULL; + } + msg->session->auth.state = SSH_AUTH_STATE_INFO; + + return rc; +} + +int ssh_auth_reply_success(ssh_session session, int partial) +{ + struct ssh_crypto_struct *crypto = NULL; + int r; + + if (session == NULL) { + return SSH_ERROR; + } + + if (partial) { + return ssh_auth_reply_default(session, partial); + } + + r = ssh_buffer_add_u8(session->out_buffer,SSH2_MSG_USERAUTH_SUCCESS); + if (r < 0) { + return SSH_ERROR; + } + + r = ssh_packet_send(session); + + /* + * Consider the session as having been authenticated only after sending + * the USERAUTH_SUCCESS message. Setting these flags after ssh_packet_send + * ensures that a rekey is not triggered prematurely, causing the message + * to be queued. + */ + session->session_state = SSH_SESSION_STATE_AUTHENTICATED; + session->flags |= SSH_SESSION_FLAG_AUTHENTICATED; + + 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"); + 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"); + crypto->do_compress_in = 1; + } + return r; +} + +int ssh_message_auth_reply_success(ssh_message msg, int partial) { + if(msg == NULL) + return SSH_ERROR; + return ssh_auth_reply_success(msg->session, partial); +} + +/* Answer OK to a pubkey auth request */ +int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pubkey) { + int rc; + if (msg == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_pack(msg->session->out_buffer, + "bSS", + SSH2_MSG_USERAUTH_PK_OK, + algo, + pubkey); + if(rc != SSH_OK){ + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + + rc = ssh_packet_send(msg->session); + return rc; +} + +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); + if (algo == NULL) { + return SSH_ERROR; + } + + ret = ssh_pki_export_pubkey_blob(msg->auth_request.pubkey, &pubkey_blob); + if (ret < 0) { + SSH_STRING_FREE(algo); + return SSH_ERROR; + } + + ret = ssh_message_auth_reply_pk_ok(msg, algo, pubkey_blob); + + SSH_STRING_FREE(algo); + SSH_STRING_FREE(pubkey_blob); + + return ret; +} + + +const char *ssh_message_channel_request_open_originator(ssh_message msg){ + return msg->channel_request_open.originator; +} + +int ssh_message_channel_request_open_originator_port(ssh_message msg){ + return msg->channel_request_open.originator_port; +} + +const char *ssh_message_channel_request_open_destination(ssh_message msg){ + return msg->channel_request_open.destination; +} + +int ssh_message_channel_request_open_destination_port(ssh_message msg){ + return msg->channel_request_open.destination_port; +} + +ssh_channel ssh_message_channel_request_channel(ssh_message msg){ + return msg->channel_request.channel; +} + +const char *ssh_message_channel_request_pty_term(ssh_message msg){ + return msg->channel_request.TERM; +} + +int ssh_message_channel_request_pty_width(ssh_message msg){ + return msg->channel_request.width; +} + +int ssh_message_channel_request_pty_height(ssh_message msg){ + return msg->channel_request.height; +} + +int ssh_message_channel_request_pty_pxwidth(ssh_message msg){ + return msg->channel_request.pxwidth; +} + +int ssh_message_channel_request_pty_pxheight(ssh_message msg){ + return msg->channel_request.pxheight; +} + +const char *ssh_message_channel_request_env_name(ssh_message msg){ + return msg->channel_request.var_name; +} + +const char *ssh_message_channel_request_env_value(ssh_message msg){ + return msg->channel_request.var_value; +} + +const char *ssh_message_channel_request_command(ssh_message msg){ + return msg->channel_request.command; +} + +const char *ssh_message_channel_request_subsystem(ssh_message msg){ + return msg->channel_request.subsystem; +} + +int ssh_message_channel_request_x11_single_connection(ssh_message msg){ + return msg->channel_request.x11_single_connection ? 1 : 0; +} + +const char *ssh_message_channel_request_x11_auth_protocol(ssh_message msg){ + return msg->channel_request.x11_auth_protocol; +} + +const char *ssh_message_channel_request_x11_auth_cookie(ssh_message msg){ + return msg->channel_request.x11_auth_cookie; +} + +int ssh_message_channel_request_x11_screen_number(ssh_message msg){ + return msg->channel_request.x11_screen_number; +} + +const char *ssh_message_global_request_address(ssh_message msg){ + return msg->global_request.bind_address; +} + +int ssh_message_global_request_port(ssh_message msg){ + return msg->global_request.bind_port; +} + +/** @brief defines the ssh_message callback + * @param session the current ssh session + * @param[in] ssh_bind_message_callback a function pointer to a callback taking the + * current ssh session and received message as parameters. the function returns + * 0 if the message has been parsed and treated successfully, 1 otherwise (libssh + * must take care of the response). + * @param[in] data void pointer to be passed to callback functions + */ +void ssh_set_message_callback(ssh_session session, + int(*ssh_bind_message_callback)(ssh_session session, ssh_message msg, void *data), + void *data) { + session->ssh_message_callback = ssh_bind_message_callback; + session->ssh_message_callback_data = data; +} + +int ssh_execute_message_callbacks(ssh_session session){ + ssh_message msg=NULL; + int ret; + ssh_handle_packets(session, SSH_TIMEOUT_NONBLOCKING); + if(!session->ssh_message_list) + return SSH_OK; + if(session->ssh_message_callback){ + while((msg=ssh_message_pop_head(session)) != NULL) { + ret=session->ssh_message_callback(session,msg, + session->ssh_message_callback_data); + if(ret==1){ + ret = ssh_message_reply_default(msg); + ssh_message_free(msg); + if(ret != SSH_OK) + return ret; + } else { + ssh_message_free(msg); + } + } + } else { + while((msg=ssh_message_pop_head(session)) != NULL) { + ret = ssh_message_reply_default(msg); + ssh_message_free(msg); + if(ret != SSH_OK) + return ret; + } + } + return SSH_OK; +} + +int ssh_send_keepalive(ssh_session session) +{ + /* Client denies the request, so the error code is not meaningful */ + (void)ssh_global_request(session, "keepalive@openssh.com", NULL, 1); + + return SSH_OK; +} + +/** @} */ diff --git a/src/session.c b/src/session.c new file mode 100644 index 0000000..3b3830f --- /dev/null +++ b/src/session.c @@ -0,0 +1,1223 @@ +/* + * session.c - non-networking functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2013 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 +#include + +#include "libssh/priv.h" +#include "libssh/libssh.h" +#include "libssh/crypto.h" +#include "libssh/server.h" +#include "libssh/socket.h" +#include "libssh/ssh2.h" +#include "libssh/agent.h" +#include "libssh/packet.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/buffer.h" +#include "libssh/poll.h" +#include "libssh/pki.h" + +#define FIRST_CHANNEL 42 // why not ? it helps to find bugs. + +/** + * @defgroup libssh_session The SSH session functions. + * @ingroup libssh + * + * Functions that manage a session. + * + * @{ + */ + +/** + * @brief Create a new ssh session. + * + * @returns A new ssh_session pointer, NULL on error. + */ +ssh_session ssh_new(void) +{ + ssh_session session; + char *id = NULL; + int rc; + + session = calloc(1, sizeof (struct ssh_session_struct)); + if (session == NULL) { + return NULL; + } + + session->next_crypto = crypto_new(); + if (session->next_crypto == NULL) { + goto err; + } + + session->socket = ssh_socket_new(session); + if (session->socket == NULL) { + goto err; + } + + session->out_buffer = ssh_buffer_new(); + if (session->out_buffer == NULL) { + goto err; + } + + session->in_buffer = ssh_buffer_new(); + if (session->in_buffer == NULL) { + goto err; + } + + session->out_queue = ssh_list_new(); + if (session->out_queue == NULL) { + goto err; + } + + session->alive = 0; + session->auth.supported_methods = 0; + 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.fd = -1; + session->opts.compressionlevel = 7; + session->opts.nodelay = 0; + + 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.identity = ssh_list_new(); + if (session->opts.identity == NULL) { + goto err; + } + + id = strdup("%d/id_ed25519"); + if (id == NULL) { + goto err; + } + + rc = ssh_list_append(session->opts.identity, id); + if (rc == SSH_ERROR) { + goto err; + } + +#ifdef HAVE_ECC + id = strdup("%d/id_ecdsa"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->opts.identity, id); + if (rc == SSH_ERROR) { + goto err; + } +#endif + + id = strdup("%d/id_rsa"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->opts.identity, 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; + session->packet_state = PACKET_STATE_INIT; + session->dh_handshake_state = DH_STATE_INIT; + session->global_req_state = SSH_CHANNEL_REQ_STATE_NONE; + + session->auth.state = SSH_AUTH_STATE_NONE; + session->auth.service_state = SSH_AUTH_SERVICE_NONE; + + return session; + +err: + free(id); + ssh_free(session); + return NULL; +} + +/** + * @brief Deallocate a SSH session handle. + * + * @param[in] session The SSH session to free. + * + * @see ssh_disconnect() + * @see ssh_new() + */ +void ssh_free(ssh_session session) +{ + int i; + struct ssh_iterator *it = NULL; + struct ssh_buffer_struct *b = NULL; + + if (session == NULL) { + return; + } + + /* + * Delete all channels + * + * This needs the first thing we clean up cause if there is still an open + * channel we call ssh_channel_close() first. So we need a working socket + * and poll context for it. + */ + for (it = ssh_list_get_iterator(session->channels); + it != NULL; + it = ssh_list_get_iterator(session->channels)) { + ssh_channel_do_free(ssh_iterator_value(ssh_channel,it)); + ssh_list_remove(session->channels, it); + } + ssh_list_free(session->channels); + session->channels = NULL; + +#ifdef WITH_PCAP + if (session->pcap_ctx) { + ssh_pcap_context_free(session->pcap_ctx); + session->pcap_ctx = NULL; + } +#endif + + ssh_socket_free(session->socket); + session->socket = NULL; + + if (session->default_poll_ctx) { + ssh_poll_ctx_free(session->default_poll_ctx); + } + + SSH_BUFFER_FREE(session->in_buffer); + SSH_BUFFER_FREE(session->out_buffer); + session->in_buffer = session->out_buffer = NULL; + + if (session->in_hashbuf != NULL) { + SSH_BUFFER_FREE(session->in_hashbuf); + } + if (session->out_hashbuf != NULL) { + SSH_BUFFER_FREE(session->out_hashbuf); + } + + 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); + session->srv.ecdsa_key = NULL; + ssh_key_free(session->srv.ed25519_key); + session->srv.ed25519_key = NULL; + + if (session->ssh_message_list) { + ssh_message msg; + + for (msg = ssh_list_pop_head(ssh_message, session->ssh_message_list); + msg != NULL; + msg = ssh_list_pop_head(ssh_message, session->ssh_message_list)) { + ssh_message_free(msg); + } + ssh_list_free(session->ssh_message_list); + } + + if (session->kbdint != NULL) { + ssh_kbdint_free(session->kbdint); + } + + if (session->packet_callbacks) { + ssh_list_free(session->packet_callbacks); + } + + /* options */ + if (session->opts.identity) { + char *id; + + for (id = ssh_list_pop_head(char *, session->opts.identity); + id != NULL; + id = ssh_list_pop_head(char *, session->opts.identity)) { + SAFE_FREE(id); + } + ssh_list_free(session->opts.identity); + } + + 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 + 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->opts.bindaddr); + SAFE_FREE(session->opts.custombanner); + SAFE_FREE(session->opts.username); + SAFE_FREE(session->opts.host); + SAFE_FREE(session->opts.sshdir); + SAFE_FREE(session->opts.knownhosts); + SAFE_FREE(session->opts.global_knownhosts); + SAFE_FREE(session->opts.ProxyCommand); + SAFE_FREE(session->opts.gss_server_identity); + SAFE_FREE(session->opts.gss_client_identity); + SAFE_FREE(session->opts.pubkey_accepted_types); + + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (session->opts.wanted_methods[i]) { + SAFE_FREE(session->opts.wanted_methods[i]); + } + } + + /* burn connection, it could contain sensitive data */ + explicit_bzero(session, sizeof(struct ssh_session_struct)); + SAFE_FREE(session); +} + +/** + * @brief get the client banner + * + * @param[in] session The SSH session + * + * @return Returns the client banner string or NULL. + */ +const char* ssh_get_clientbanner(ssh_session session) { + if (session == NULL) { + return NULL; + } + + return session->clientbanner; +} + +/** + * @brief get the server banner + * + * @param[in] session The SSH session + * + * @return Returns the server banner string or NULL. + */ +const char* ssh_get_serverbanner(ssh_session session) { + if(!session) { + return NULL; + } + return session->serverbanner; +} + +/** + * @brief get the name of the current key exchange algorithm. + * + * @param[in] session The SSH session + * + * @return Returns the key exchange algorithm string or NULL. + */ +const char* ssh_get_kex_algo(ssh_session session) { + if ((session == NULL) || + (session->current_crypto == NULL)) { + return NULL; + } + + switch (session->current_crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + return "diffie-hellman-group1-sha1"; + case SSH_KEX_DH_GROUP14_SHA1: + return "diffie-hellman-group14-sha1"; + case SSH_KEX_DH_GROUP14_SHA256: + return "diffie-hellman-group14-sha256"; + case SSH_KEX_DH_GROUP16_SHA512: + return "diffie-hellman-group16-sha512"; + case SSH_KEX_DH_GROUP18_SHA512: + return "diffie-hellman-group18-sha512"; + case SSH_KEX_ECDH_SHA2_NISTP256: + return "ecdh-sha2-nistp256"; + case SSH_KEX_ECDH_SHA2_NISTP384: + return "ecdh-sha2-nistp384"; + case SSH_KEX_ECDH_SHA2_NISTP521: + return "ecdh-sha2-nistp521"; + case SSH_KEX_CURVE25519_SHA256: + return "curve25519-sha256"; + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + return "curve25519-sha256@libssh.org"; + default: + break; + } + + return NULL; +} + +/** + * @brief get the name of the input cipher for the given session. + * + * @param[in] session The SSH session. + * + * @return Returns cipher name or NULL. + */ +const char* ssh_get_cipher_in(ssh_session session) { + if ((session != NULL) && + (session->current_crypto != NULL) && + (session->current_crypto->in_cipher != NULL)) { + return session->current_crypto->in_cipher->name; + } + return NULL; +} + +/** + * @brief get the name of the output cipher for the given session. + * + * @param[in] session The SSH session. + * + * @return Returns cipher name or NULL. + */ +const char* ssh_get_cipher_out(ssh_session session) { + if ((session != NULL) && + (session->current_crypto != NULL) && + (session->current_crypto->out_cipher != NULL)) { + return session->current_crypto->out_cipher->name; + } + return NULL; +} + +/** + * @brief get the name of the input HMAC algorithm for the given session. + * + * @param[in] session The SSH session. + * + * @return Returns HMAC algorithm name or NULL if unknown. + */ +const char* ssh_get_hmac_in(ssh_session session) { + if ((session != NULL) && + (session->current_crypto != NULL)) { + return ssh_hmac_type_to_string(session->current_crypto->in_hmac, session->current_crypto->in_hmac_etm); + } + return NULL; +} + +/** + * @brief get the name of the output HMAC algorithm for the given session. + * + * @param[in] session The SSH session. + * + * @return Returns HMAC algorithm name or NULL if unknown. + */ +const char* ssh_get_hmac_out(ssh_session session) { + if ((session != NULL) && + (session->current_crypto != NULL)) { + return ssh_hmac_type_to_string(session->current_crypto->out_hmac, session->current_crypto->out_hmac_etm); + } + return NULL; +} + +/** + * @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; + } + + ssh_socket_close(session->socket); + session->alive = 0; + ssh_disconnect(session); +} + +/** + * @brief Set the session in blocking/nonblocking mode. + * + * @param[in] session The ssh session to change. + * + * @param[in] blocking Zero for nonblocking mode. + */ +void ssh_set_blocking(ssh_session session, int blocking) +{ + if (session == NULL) { + return; + } + session->flags &= ~SSH_SESSION_FLAG_BLOCKING; + session->flags |= blocking ? SSH_SESSION_FLAG_BLOCKING : 0; +} + +/** + * @brief Return the blocking mode of libssh + * @param[in] session The SSH session + * @returns 0 if the session is nonblocking, + * @returns 1 if the functions may block. + */ +int ssh_is_blocking(ssh_session session) +{ + return (session->flags & SSH_SESSION_FLAG_BLOCKING) ? 1 : 0; +} + +/* Waits until the output socket is empty */ +static int ssh_flush_termination(void *c){ + ssh_session session = c; + if (ssh_socket_buffered_write_bytes(session->socket) == 0 || + session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +/** + * @brief Blocking flush of the outgoing buffer + * @param[in] session The SSH session + * @param[in] timeout Set an upper limit on the time for which this function + * will block, in milliseconds. Specifying -1 + * means an infinite timeout. This parameter is passed to + * the poll() function. + * @returns SSH_OK on success, SSH_AGAIN if timeout occurred, + * SSH_ERROR otherwise. + */ + +int ssh_blocking_flush(ssh_session session, int timeout){ + int rc; + if (session == NULL) { + return SSH_ERROR; + } + + rc = ssh_handle_packets_termination(session, timeout, + ssh_flush_termination, session); + if (rc == SSH_ERROR) { + return rc; + } + if (!ssh_flush_termination(session)) { + rc = SSH_AGAIN; + } + + return rc; +} + +/** + * @brief Check if we are connected. + * + * @param[in] session The session to check if it is connected. + * + * @return 1 if we are connected, 0 if not. + */ +int ssh_is_connected(ssh_session session) { + if (session == NULL) { + return 0; + } + + return session->alive; +} + +/** + * @brief Get the fd of a connection. + * + * In case you'd need the file descriptor of the connection to the server/client. + * + * @param[in] session The ssh session to use. + * + * @return The file descriptor of the connection, or -1 if it is + * not connected + */ +socket_t ssh_get_fd(ssh_session session) { + if (session == NULL) { + return -1; + } + + return ssh_socket_get_fd(session->socket); +} + +/** + * @brief Tell the session it has data to read on the file descriptor without + * blocking. + * + * @param[in] session The ssh session to use. + */ +void ssh_set_fd_toread(ssh_session session) { + if (session == NULL) { + return; + } + + ssh_socket_set_read_wontblock(session->socket); +} + +/** + * @brief Tell the session it may write to the file descriptor without blocking. + * + * @param[in] session The ssh session to use. + */ +void ssh_set_fd_towrite(ssh_session session) { + if (session == NULL) { + return; + } + + ssh_socket_set_write_wontblock(session->socket); +} + +/** + * @brief Tell the session it has an exception to catch on the file descriptor. + * + * \param[in] session The ssh session to use. + */ +void ssh_set_fd_except(ssh_session session) { + if (session == NULL) { + return; + } + + ssh_socket_set_except(session->socket); +} + +/** + * @internal + * + * @brief Poll the current session for an event and call the appropriate + * callbacks. This function will not loop until the timeout is expired. + * + * This will block until one event happens. + * + * @param[in] session The session handle to use. + * + * @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 + * specified in options. 0 means poll will return immediately. + * This parameter is passed to the poll() function. + * + * @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 tm = timeout; + int rc; + + if (session == NULL || session->socket == NULL) { + return SSH_ERROR; + } + + spoll = ssh_socket_get_poll_handle(session->socket); + ssh_poll_add_events(spoll, POLLIN); + ctx = ssh_poll_get_ctx(spoll); + + if (!ctx) { + ctx = ssh_poll_get_default_ctx(session); + 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; + } + rc = ssh_poll_ctx_dopoll(ctx, tm); + if (rc == SSH_ERROR) { + session->session_state = SSH_SESSION_STATE_ERROR; + } + + return rc; +} + +/** + * @internal + * + * @brief Poll the current session for an event and call the appropriate + * callbacks. + * + * This will block until termination function returns true, or timeout expired. + * + * @param[in] session The session handle to use. + * + * @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 + * specified in options. 0 means poll will return + * immediately. + * SSH_TIMEOUT_DEFAULT uses the session timeout if set or + * uses blocking parameters of the session. + * This parameter is passed to the poll() function. + * + * @param[in] fct Termination function to be used to determine if it is + * possible to stop polling. + * @param[in] user User parameter to be passed to fct termination function. + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_handle_packets_termination(ssh_session session, + long timeout, + ssh_termination_function fct, + void *user) +{ + struct ssh_timestamp ts; + long timeout_ms = SSH_TIMEOUT_INFINITE; + long tm; + int ret = SSH_OK; + + /* If a timeout has been provided, use it */ + if (timeout >= 0) { + timeout_ms = timeout; + } else { + if (ssh_is_blocking(session)) { + if (timeout == SSH_TIMEOUT_USER || timeout == SSH_TIMEOUT_DEFAULT) { + if (session->opts.timeout > 0 || + session->opts.timeout_usec > 0) { + timeout_ms = + ssh_make_milliseconds(session->opts.timeout, + session->opts.timeout_usec); + } + } + } else { + timeout_ms = SSH_TIMEOUT_NONBLOCKING; + } + } + + /* avoid unnecessary syscall for the SSH_TIMEOUT_NONBLOCKING case */ + if (timeout_ms != SSH_TIMEOUT_NONBLOCKING) { + ssh_timestamp_init(&ts); + } + + tm = timeout_ms; + while(!fct(user)) { + ret = ssh_handle_packets(session, tm); + if (ret == SSH_ERROR) { + break; + } + if (ssh_timeout_elapsed(&ts, timeout_ms)) { + ret = fct(user) ? SSH_OK : SSH_AGAIN; + break; + } + + tm = ssh_timeout_update(&ts, timeout_ms); + } + + return ret; +} + +/** + * @brief Get session status + * + * @param session The ssh session to use. + * + * @returns A bitmask including SSH_CLOSED, SSH_READ_PENDING, SSH_WRITE_PENDING + * or SSH_CLOSED_ERROR which respectively means the session is closed, + * has data to read on the connection socket and session was closed + * due to an error. + */ +int ssh_get_status(ssh_session session) { + int socketstate; + int r = 0; + + if (session == NULL) { + return 0; + } + + socketstate = ssh_socket_get_status(session->socket); + + if (session->session_state == SSH_SESSION_STATE_DISCONNECTED) { + r |= SSH_CLOSED; + } + if (socketstate & SSH_READ_PENDING) { + r |= SSH_READ_PENDING; + } + if (socketstate & SSH_WRITE_PENDING) { + r |= SSH_WRITE_PENDING; + } + if ((session->session_state == SSH_SESSION_STATE_DISCONNECTED && + (socketstate & SSH_CLOSED_ERROR)) || + session->session_state == SSH_SESSION_STATE_ERROR) { + r |= SSH_CLOSED_ERROR; + } + + return r; +} + +/** + * @brief Get poll flags for an external mainloop + * + * @param session The ssh session to use. + * + * @returns A bitmask including SSH_READ_PENDING or SSH_WRITE_PENDING. + * For SSH_READ_PENDING, your invocation of poll() should include + * POLLIN. For SSH_WRITE_PENDING, your invocation of poll() should + * include POLLOUT. + */ +int ssh_get_poll_flags(ssh_session session) +{ + if (session == NULL) { + return 0; + } + + return ssh_socket_get_poll_flags (session->socket); +} + +/** + * @brief Get the disconnect message from the server. + * + * @param[in] session The ssh session to use. + * + * @return The message sent by the server along with the + * disconnect, or NULL in which case the reason of the + * disconnect may be found with ssh_get_error. + * + * @see ssh_get_error() + */ +const char *ssh_get_disconnect_message(ssh_session session) { + if (session == NULL) { + return NULL; + } + + if (session->session_state != SSH_SESSION_STATE_DISCONNECTED) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Connection not closed yet"); + } else if(!session->discon_msg) { + ssh_set_error(session, SSH_FATAL, + "Connection correctly closed but no disconnect message"); + } else { + return session->discon_msg; + } + + return NULL; +} + +/** + * @brief Get the protocol version of the session. + * + * @param session The ssh session to use. + * + * @return The SSH version as integer, < 0 on error. + */ +int ssh_get_version(ssh_session session) { + if (session == NULL) { + return -1; + } + + return 2; +} + +/** + * @internal + * @brief Callback to be called when the socket received an exception code. + * @param user is a pointer to session + */ +void ssh_socket_exception_callback(int code, int errno_code, void *user){ + ssh_session session=(ssh_session)user; + + SSH_LOG(SSH_LOG_RARE,"Socket exception callback: %d (%d)",code, errno_code); + session->session_state = SSH_SESSION_STATE_ERROR; + 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)); + } + + session->ssh_connection_callback(session); +} + +/** + * @brief Send a message that should be ignored + * + * @param[in] session The SSH session + * @param[in] data Data to be sent + * + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_send_ignore (ssh_session session, const char *data) { + const int type = SSH2_MSG_IGNORE; + int rc; + + if (ssh_socket_is_open(session->socket)) { + rc = ssh_buffer_pack(session->out_buffer, + "bs", + type, + data); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + goto error; + } + ssh_packet_send(session); + ssh_handle_packets(session, 0); + } + + return SSH_OK; + +error: + ssh_buffer_reinit(session->out_buffer); + return SSH_ERROR; +} + +/** + * @brief Send a debug message + * + * @param[in] session The SSH session + * @param[in] message Data to be sent + * @param[in] always_display Message SHOULD be displayed by the server. It + * SHOULD NOT be displayed unless debugging + * information has been explicitly requested. + * + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_send_debug (ssh_session session, const char *message, int always_display) { + int rc; + + if (ssh_socket_is_open(session->socket)) { + rc = ssh_buffer_pack(session->out_buffer, + "bbsd", + SSH2_MSG_DEBUG, + always_display != 0 ? 1 : 0, + message, + 0); /* empty language tag */ + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + ssh_packet_send(session); + ssh_handle_packets(session, 0); + } + + return SSH_OK; + +error: + ssh_buffer_reinit(session->out_buffer); + return SSH_ERROR; +} + + /** + * @brief Set the session data counters. + * + * This functions sets the counter structures to be used to calculate data + * which comes in and goes out through the session at different levels. + * + * @code + * struct ssh_counter_struct scounter = { + * .in_bytes = 0, + * .out_bytes = 0, + * .in_packets = 0, + * .out_packets = 0 + * }; + * + * struct ssh_counter_struct rcounter = { + * .in_bytes = 0, + * .out_bytes = 0, + * .in_packets = 0, + * .out_packets = 0 + * }; + * + * ssh_set_counters(session, &scounter, &rcounter); + * @endcode + * + * @param[in] session The SSH session. + * + * @param[in] scounter Counter for byte data handled by the session sockets. + * + * @param[in] rcounter Counter for byte and packet data handled by the session, + * prior compression and SSH overhead. + */ +void ssh_set_counters(ssh_session session, ssh_counter scounter, + ssh_counter rcounter) { + if (session != NULL) { + session->socket_counter = scounter; + session->raw_counter = rcounter; + } +} + +/** + * @deprecated Use ssh_get_publickey_hash() + */ +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; + int rc; + + if (session == NULL || hash == NULL) { + return SSH_ERROR; + } + + /* In FIPS mode, we cannot use MD5 */ + if (ssh_fips_mode()) { + ssh_set_error(session, + SSH_FATAL, + "In FIPS mode MD5 is not allowed." + "Try ssh_get_publickey_hash() with" + "SSH_PUBLICKEY_HASH_SHA256"); + return SSH_ERROR; + } + + *hash = NULL; + if (session->current_crypto == NULL || + session->current_crypto->server_pubkey == NULL) { + ssh_set_error(session,SSH_FATAL,"No current cryptographic context"); + return SSH_ERROR; + } + + h = calloc(MD5_DIGEST_LEN, sizeof(unsigned char)); + if (h == NULL) { + return SSH_ERROR; + } + + ctx = md5_init(); + if (ctx == NULL) { + SAFE_FREE(h); + return SSH_ERROR; + } + + rc = ssh_get_server_publickey(session, &pubkey); + if (rc != SSH_OK) { + md5_final(h, ctx); + SAFE_FREE(h); + return SSH_ERROR; + } + + rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); + ssh_key_free(pubkey); + if (rc != SSH_OK) { + md5_final(h, ctx); + SAFE_FREE(h); + return SSH_ERROR; + } + + 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; +} + +/** + * @brief Deallocate the hash obtained by ssh_get_pubkey_hash. + * + * 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) { + SAFE_FREE(*hash); +} + +/** + * @brief Get the server public key from a session. + * + * @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. + * + * @return SSH_OK on success, SSH_ERROR on errror. + * + * @see ssh_key_free() + */ +int ssh_get_server_publickey(ssh_session session, ssh_key *key) +{ + ssh_key pubkey = NULL; + + if (session == NULL || + session->current_crypto == NULL || + session->current_crypto->server_pubkey == NULL) { + return SSH_ERROR; + } + + pubkey = ssh_key_dup(session->current_crypto->server_pubkey); + if (pubkey == NULL) { + return SSH_ERROR; + } + + *key = pubkey; + return SSH_OK; +} + +/** + * @deprecated Use ssh_get_server_publickey() + */ +int ssh_get_publickey(ssh_session session, ssh_key *key) +{ + return ssh_get_server_publickey(session, key); +} + +/** + * @brief Allocates a buffer with the hash of the public key. + * + * This function allows you to get a hash of the public key. You can then + * print this hash in a human-readable form to the user so that he is able to + * verify it. Use ssh_get_hexa() or ssh_print_hash() to display it. + * + * @param[in] key The public key to create the hash for. + * + * @param[in] type The type of the hash you want. + * + * @param[in] 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. + * + * @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 + * you at making things secure. + * OpenSSH uses SHA256 to print public key digests. + * + * @see ssh_session_update_known_hosts() + * @see ssh_get_hexa() + * @see ssh_print_hash() + * @see ssh_clean_pubkey_hash() + */ +int ssh_get_publickey_hash(const ssh_key key, + enum ssh_publickey_hash_type type, + unsigned char **hash, + size_t *hlen) +{ + ssh_string blob; + unsigned char *h; + int rc; + + rc = ssh_pki_export_pubkey_blob(key, &blob); + if (rc < 0) { + return rc; + } + + switch (type) { + case SSH_PUBLICKEY_HASH_SHA1: + { + SHACTX ctx; + + h = calloc(1, SHA_DIGEST_LEN); + if (h == NULL) { + rc = -1; + goto out; + } + + ctx = sha1_init(); + if (ctx == NULL) { + free(h); + rc = -1; + goto out; + } + + sha1_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); + sha1_final(h, ctx); + + *hlen = SHA_DIGEST_LEN; + } + break; + case SSH_PUBLICKEY_HASH_SHA256: + { + SHA256CTX ctx; + + h = calloc(1, SHA256_DIGEST_LEN); + if (h == NULL) { + rc = -1; + goto out; + } + + ctx = sha256_init(); + if (ctx == NULL) { + free(h); + rc = -1; + goto out; + } + + sha256_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); + sha256_final(h, ctx); + + *hlen = SHA256_DIGEST_LEN; + } + break; + case SSH_PUBLICKEY_HASH_MD5: + { + MD5CTX ctx; + + /* 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"); + rc = SSH_ERROR; + goto out; + } + + h = calloc(1, MD5_DIGEST_LEN); + if (h == NULL) { + rc = -1; + goto out; + } + + ctx = md5_init(); + if (ctx == NULL) { + free(h); + rc = -1; + goto out; + } + + md5_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); + md5_final(h, ctx); + + *hlen = MD5_DIGEST_LEN; + } + break; + default: + rc = -1; + goto out; + } + + *hash = h; + rc = 0; +out: + SSH_STRING_FREE(blob); + return rc; +} + +/** @} */ diff --git a/src/sftp.c b/src/sftp.c new file mode 100644 index 0000000..b64aad6 --- /dev/null +++ b/src/sftp.c @@ -0,0 +1,3431 @@ +/* + * sftp.c - Secure FTP functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2008 by Aris Adamantiadis + * Copyright (c) 2008-2018 by Andreas Schneider + * + * 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. + */ + +/* This file contains code written by Nick Zitzmann */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/sftp.h" +#include "libssh/sftp_priv.h" +#include "libssh/buffer.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/bytearray.h" + +#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; + + ext = calloc(1, sizeof(struct sftp_ext_struct)); + if (ext == NULL) { + return NULL; + } + + return ext; +} + +static void sftp_ext_free(sftp_ext ext) +{ + size_t i; + + if (ext == NULL) { + return; + } + + if (ext->count > 0) { + if (ext->name != NULL) { + for (i = 0; i < ext->count; i++) { + SAFE_FREE(ext->name[i]); + } + SAFE_FREE(ext->name); + } + + if (ext->data != NULL) { + for (i = 0; i < ext->count; i++) { + SAFE_FREE(ext->data[i]); + } + SAFE_FREE(ext->data); + } + } + + SAFE_FREE(ext); +} + +sftp_session sftp_new(ssh_session session) +{ + sftp_session sftp; + + if (session == NULL) { + return NULL; + } + + sftp = calloc(1, sizeof(struct sftp_session_struct)); + if (sftp == NULL) { + ssh_set_error_oom(session); + + return NULL; + } + + sftp->ext = sftp_ext_new(); + if (sftp->ext == NULL) { + ssh_set_error_oom(session); + goto error; + } + + sftp->read_packet = calloc(1, sizeof(struct sftp_packet_struct)); + if (sftp->read_packet == NULL) { + ssh_set_error_oom(session); + goto error; + } + + 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 = ssh_channel_new(session); + if (sftp->channel == NULL) { + ssh_set_error_oom(session); + goto error; + } + + if (ssh_channel_open_session(sftp->channel)) { + goto error; + } + + if (ssh_channel_request_sftp(sftp->channel)) { + goto error; + } + + return sftp; +error: + if (sftp->ext != NULL) { + sftp_ext_free(sftp->ext); + } + if (sftp->channel != NULL) { + ssh_channel_free(sftp->channel); + } + 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; +} + +sftp_session sftp_new_channel(ssh_session session, ssh_channel channel){ + sftp_session sftp; + + if (session == NULL) { + return NULL; + } + + sftp = calloc(1, sizeof(struct sftp_session_struct)); + if (sftp == NULL) { + ssh_set_error_oom(session); + + return NULL; + } + + sftp->ext = sftp_ext_new(); + if (sftp->ext == NULL) { + ssh_set_error_oom(session); + SAFE_FREE(sftp); + + return NULL; + } + + sftp->session = session; + sftp->channel = channel; + + return sftp; +} + +#ifdef WITH_SERVER +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->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->session = session; + sftp->channel = chan; + + 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); + } + SAFE_FREE(sftp->read_packet); + } + 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; + } + + 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; + } + + if (sftp_packet_write(sftp, SSH_FXP_VERSION, reply) < 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 = (int)version; + } + + return 0; +} + +void sftp_server_free(sftp_session sftp) +{ + sftp_request_queue ptr; + + if (sftp == NULL) { + return; + } + + ptr = sftp->queue; + while(ptr) { + sftp_request_queue old; + sftp_message_free(ptr->message); + old = ptr->next; + SAFE_FREE(ptr); + ptr = old; + } + + SAFE_FREE(sftp->handles); + SSH_BUFFER_FREE(sftp->read_packet->payload); + SAFE_FREE(sftp->read_packet); + + sftp_ext_free(sftp->ext); + + SAFE_FREE(sftp); +} +#endif /* WITH_SERVER */ + +void sftp_free(sftp_session sftp) +{ + sftp_request_queue ptr; + + if (sftp == NULL) { + return; + } + + if (sftp->channel != NULL) { + ssh_channel_send_eof(sftp->channel); + ptr = sftp->queue; + while(ptr) { + sftp_request_queue old; + sftp_message_free(ptr->message); + old = ptr->next; + SAFE_FREE(ptr); + ptr = old; + } + + ssh_channel_free(sftp->channel); + sftp->channel = NULL; + } + + SAFE_FREE(sftp->handles); + SSH_BUFFER_FREE(sftp->read_packet->payload); + SAFE_FREE(sftp->read_packet); + + sftp_ext_free(sftp->ext); + + 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) +{ + uint8_t buffer[SFTP_BUFFER_SIZE_MAX]; + 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, 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; + } + + 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]; + + /* 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; + } + 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; + } + + return packet; +error: + ssh_buffer_reinit(packet->payload); + return NULL; +} + +static void sftp_set_error(sftp_session sftp, int errnum) { + if (sftp != NULL) { + sftp->errnum = errnum; + } +} + +/* Get the last sftp error */ +int sftp_get_error(sftp_session sftp) { + if (sftp == NULL) { + return -1; + } + + 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_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 %d type %d", + msg->id, + msg->packet_type); + + return msg; +} + +static 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; +} + +void sftp_packet_free(sftp_packet packet) +{ + if (packet == NULL) { + return; + } + + SSH_BUFFER_FREE(packet->payload); + free(packet); +} + +/* 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; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + 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); + + packet = sftp_packet_read(sftp); + if (packet == NULL) { + return -1; + } + + 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; + } + + /* 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; + } + + SSH_LOG(SSH_LOG_PROTOCOL, + "SFTP server extension: %s, version: %s", + ext_name, ext_data); + + 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; + + 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; + } + 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; +} + +unsigned int sftp_extensions_get_count(sftp_session sftp) { + if (sftp == NULL || sftp->ext == NULL) { + return 0; + } + + return sftp->ext->count; +} + +const char *sftp_extensions_get_name(sftp_session sftp, unsigned int idx) { + if (sftp == NULL) + return NULL; + if (sftp->ext == NULL || sftp->ext->name == NULL) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + if (idx > sftp->ext->count) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + return sftp->ext->name[idx]; +} + +const char *sftp_extensions_get_data(sftp_session sftp, unsigned int idx) { + if (sftp == NULL) + return NULL; + if (sftp->ext == NULL || sftp->ext->name == NULL) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + if (idx > sftp->ext->count) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + return sftp->ext->data[idx]; +} + +int sftp_extension_supported(sftp_session sftp, const char *name, + const char *data) { + size_t i, n; + + if (sftp == NULL || name == NULL || data == NULL) { + return 0; + } + + n = sftp_extensions_get_count(sftp); + for (i = 0; i < n; i++) { + const char *ext_name = sftp_extensions_get_name(sftp, i); + const char *ext_data = sftp_extensions_get_data(sftp, i); + + if (ext_name != NULL && ext_data != NULL && + strcmp(ext_name, name) == 0 && + strcmp(ext_data, data) == 0) { + return 1; + } + } + + 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; + + if(msg->packet_type != SSH_FXP_HANDLE) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Not a ssh_fxp_handle message passed in!"); + return NULL; + } + + file = calloc(1, sizeof(struct sftp_file_struct)); + if (file == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + file->handle = ssh_buffer_get_ssh_string(msg->payload); + if (file->handle == NULL) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_HANDLE message"); + SAFE_FREE(file); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + file->sftp = msg->sftp; + file->offset = 0; + file->eof = 0; + + return file; +} + +/* Open a directory */ +sftp_dir sftp_opendir(sftp_session sftp, const char *path) +{ + sftp_message msg = NULL; + sftp_file file = NULL; + sftp_dir dir = NULL; + sftp_status_message status; + ssh_buffer payload; + uint32_t id; + int rc; + + if (sftp == NULL) { + return NULL; + } + + payload = ssh_buffer_new(); + if (payload == 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(payload, + "ds", + id, + path); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(payload); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_OPENDIR, payload); + SSH_BUFFER_FREE(payload); + if (rc < 0) { + return NULL; + } + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + 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); + return NULL; + case SSH_FXP_HANDLE: + file = parse_handle_msg(msg); + sftp_message_free(msg); + if (file != NULL) { + dir = calloc(1, sizeof(struct sftp_dir_struct)); + if (dir == NULL) { + ssh_set_error_oom(sftp->session); + free(file); + return NULL; + } + + dir->sftp = sftp; + dir->name = strdup(path); + if (dir->name == NULL) { + SAFE_FREE(dir); + SAFE_FREE(file); + return NULL; + } + dir->handle = file->handle; + SAFE_FREE(file); + } + return dir; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "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_longanme_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; +} + +/* Get a single file attributes structure of a directory. */ +sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) +{ + sftp_message msg = NULL; + sftp_status_message status; + sftp_attributes attr; + ssh_buffer payload; + uint32_t id; + int rc; + + if (dir->buffer == NULL) { + payload = ssh_buffer_new(); + if (payload == 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(payload, + "dS", + id, + dir->handle); + if (rc != 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(payload); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_READDIR, payload); + SSH_BUFFER_FREE(payload); + if (rc < 0) { + return NULL; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sent a ssh_fxp_readdir with id %d", id); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + 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); + switch (status->status) { + case SSH_FX_EOF: + dir->eof = 1; + status_msg_free(status); + return NULL; + default: + break; + } + + ssh_set_error(sftp->session, SSH_FATAL, + "Unknown error status: %d", status->status); + status_msg_free(status); + + return NULL; + case SSH_FXP_NAME: + ssh_buffer_get_u32(msg->payload, &dir->count); + dir->count = ntohl(dir->count); + dir->buffer = msg->payload; + msg->payload = NULL; + sftp_message_free(msg); + break; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Unsupported message back %d", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + + return NULL; + } + } + + /* now dir->buffer contains a buffer and dir->count != 0 */ + if (dir->count == 0) { + ssh_set_error(sftp->session, SSH_FATAL, + "Count of files sent by the server is zero, which is invalid, or " + "libsftp bug"); + return NULL; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Count is %d", dir->count); + + attr = sftp_parse_attr(sftp, dir->buffer, 1); + if (attr == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Couldn't parse the SFTP attributes"); + return NULL; + } + + dir->count--; + if (dir->count == 0) { + SSH_BUFFER_FREE(dir->buffer); + dir->buffer = NULL; + } + + return attr; +} + +/* Tell if the directory has reached EOF (End Of File). */ +int sftp_dir_eof(sftp_dir dir) { + return dir->eof; +} + +/* Free a SFTP_ATTRIBUTE handle */ +void sftp_attributes_free(sftp_attributes file){ + if (file == NULL) { + return; + } + + SSH_STRING_FREE(file->acl); + SSH_STRING_FREE(file->extended_data); + SSH_STRING_FREE(file->extended_type); + + SAFE_FREE(file->name); + SAFE_FREE(file->longname); + SAFE_FREE(file->group); + SAFE_FREE(file->owner); + + SAFE_FREE(file); +} + +static int sftp_handle_close(sftp_session sftp, ssh_string handle) +{ + sftp_status_message status; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; + + 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, + "dS", + id, + handle); + 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_CLOSE, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + return -1; + } + msg = sftp_dequeue(sftp,id); + } + + switch (msg->packet_type) { + case 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; + break; + default: + break; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during sftp_handle_close!", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* Close an open file handle. */ +int sftp_close(sftp_file file){ + int err = SSH_NO_ERROR; + + SAFE_FREE(file->name); + if (file->handle){ + err = sftp_handle_close(file->sftp,file->handle); + SSH_STRING_FREE(file->handle); + } + /* FIXME: check server response and implement errno */ + SAFE_FREE(file); + + return err; +} + +/* Close an open directory. */ +int sftp_closedir(sftp_dir dir){ + int err = SSH_NO_ERROR; + + SAFE_FREE(dir->name); + if (dir->handle) { + err = sftp_handle_close(dir->sftp, dir->handle); + SSH_STRING_FREE(dir->handle); + } + /* FIXME: check server response and implement errno */ + SSH_BUFFER_FREE(dir->buffer); + SAFE_FREE(dir); + + return err; +} + +/* Open a file on the server. */ +sftp_file sftp_open(sftp_session sftp, + const char *file, + int flags, + mode_t mode) +{ + sftp_message msg = NULL; + sftp_status_message status; + struct sftp_attributes_struct attr; + sftp_file handle; + ssh_buffer buffer; + sftp_attributes stat_data; + uint32_t sftp_flags = 0; + uint32_t id; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + ZERO_STRUCT(attr); + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + + if ((flags & O_RDWR) == O_RDWR) { + sftp_flags |= (SSH_FXF_WRITE | SSH_FXF_READ); + } else if ((flags & O_WRONLY) == O_WRONLY) { + sftp_flags |= SSH_FXF_WRITE; + } else { + sftp_flags |= SSH_FXF_READ; + } + if ((flags & O_CREAT) == O_CREAT) + sftp_flags |= SSH_FXF_CREAT; + if ((flags & O_TRUNC) == O_TRUNC) + sftp_flags |= SSH_FXF_TRUNC; + if ((flags & O_EXCL) == O_EXCL) + sftp_flags |= SSH_FXF_EXCL; + 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); + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "dsd", + id, + file, + sftp_flags); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = buffer_add_attributes(buffer, &attr); + if (rc < 0) { + 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_OPEN, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + 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); + + return NULL; + case SSH_FXP_HANDLE: + handle = parse_handle_msg(msg); + if (handle == NULL) { + 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; + } + 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_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_message msg = NULL; + sftp_status_message status; + ssh_string datastring; + size_t datalen; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (handle->eof) { + return 0; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + id = sftp_get_new_id(handle->sftp); + + rc = ssh_buffer_pack(buffer, + "dSqd", + id, + handle->handle, + handle->offset, + count); + 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(handle->sftp, SSH_FXP_READ, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + while (msg == NULL) { + if (handle->nonblocking) { + if (ssh_channel_poll(handle->sftp->channel, 0) == 0) { + /* we cannot block */ + return 0; + } + } + if (sftp_read_and_dispatch(handle->sftp) < 0) { + /* something nasty has happened */ + return -1; + } + msg = sftp_dequeue(handle->sftp, id); + } + + switch (msg->packet_type) { + case 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_EOF: + handle->eof = 1; + status_msg_free(status); + return 0; + default: + break; + } + ssh_set_error(sftp->session,SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + case SSH_FXP_DATA: + datastring = ssh_buffer_get_ssh_string(msg->payload); + sftp_message_free(msg); + if (datastring == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Received invalid DATA packet from sftp server"); + return -1; + } + + datalen = ssh_string_len(datastring); + if (datalen > count) { + ssh_set_error(sftp->session, SSH_FATAL, + "Received a too big DATA packet from sftp server: " + "%" PRIdS " and asked for %" PRIdS, + datalen, count); + SSH_STRING_FREE(datastring); + return -1; + } + handle->offset += (uint64_t)datalen; + memcpy(buf, ssh_string_data(datastring), datalen); + SSH_STRING_FREE(datastring); + return datalen; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during read!", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + return -1; + } + + return -1; /* not reached */ +} + +/* Start an asynchronous read from a file using an opened sftp file handle. */ +int sftp_async_read_begin(sftp_file file, uint32_t len){ + sftp_session sftp = file->sftp; + ssh_buffer buffer; + uint32_t id; + int rc; + + 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, + "dSqd", + id, + file->handle, + file->offset, + len); + 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_READ, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + file->offset += len; /* assume we'll read len bytes */ + + return id; +} + +/* Wait for an asynchronous read to complete and save the data. */ +int sftp_async_read(sftp_file file, void *data, uint32_t size, uint32_t id){ + sftp_session sftp; + sftp_message msg = NULL; + sftp_status_message status; + ssh_string datastring; + int err = SSH_OK; + uint32_t len; + + if (file == NULL) { + return SSH_ERROR; + } + sftp = file->sftp; + + if (file->eof) { + return 0; + } + + /* 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 */ + return SSH_ERROR; + } + + msg = sftp_dequeue(sftp,id); + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + 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; + } + status_msg_free(status); + return err; + case SSH_FXP_DATA: + datastring = ssh_buffer_get_ssh_string(msg->payload); + sftp_message_free(msg); + if (datastring == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Received invalid DATA packet from sftp server"); + return SSH_ERROR; + } + if (ssh_string_len(datastring) > size) { + ssh_set_error(sftp->session, SSH_FATAL, + "Received a too big DATA packet from sftp server: " + "%" PRIdS " and asked for %u", + ssh_string_len(datastring), size); + SSH_STRING_FREE(datastring); + return SSH_ERROR; + } + len = ssh_string_len(datastring); + /* Update the offset with the correct value */ + file->offset = file->offset - (size - len); + memcpy(data, ssh_string_data(datastring), len); + SSH_STRING_FREE(datastring); + return len; + default: + ssh_set_error(sftp->session,SSH_FATAL,"Received message %d during read!",msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + return SSH_ERROR; + } + + return SSH_ERROR; +} + +ssize_t sftp_write(sftp_file file, const void *buf, size_t count) { + sftp_session sftp = file->sftp; + sftp_message msg = NULL; + sftp_status_message status; + ssh_buffer buffer; + uint32_t id; + ssize_t len; + size_t packetlen; + int rc; + + 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(file->sftp); + + rc = ssh_buffer_pack(buffer, + "dSqdP", + id, + file->handle, + file->offset, + count, /* len of datastring */ + (size_t)count, buf); + if (rc != SSH_OK){ + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + 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); + SSH_BUFFER_FREE(buffer); + if (len < 0) { + return -1; + } else if ((size_t)len != packetlen) { + SSH_LOG(SSH_LOG_PACKET, + "Could not write as much data as expected"); + } + + while (msg == NULL) { + if (sftp_read_and_dispatch(file->sftp) < 0) { + /* something nasty has happened */ + return -1; + } + msg = sftp_dequeue(file->sftp, id); + } + + switch (msg->packet_type) { + case 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: + file->offset += count; + status_msg_free(status); + return count; + default: + break; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + file->offset += count; + status_msg_free(status); + return -1; + default: + 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 -1; + } + + return -1; /* not reached */ +} + +/* Seek to a specific location in a file. */ +int sftp_seek(sftp_file file, uint32_t new_offset) { + if (file == NULL) { + return -1; + } + + file->offset = new_offset; + file->eof = 0; + + return 0; +} + +int sftp_seek64(sftp_file file, uint64_t new_offset) { + if (file == NULL) { + return -1; + } + + file->offset = new_offset; + file->eof = 0; + + return 0; +} + +/* Report current byte position in file. */ +unsigned long sftp_tell(sftp_file file) { + return (unsigned long)file->offset; +} +/* Report current byte position in file. */ +uint64_t sftp_tell64(sftp_file file) { + return (uint64_t) file->offset; +} + +/* Rewinds the position of the file pointer to the beginning of the file.*/ +void sftp_rewind(sftp_file file) { + file->offset = 0; + file->eof = 0; +} + +/* code written by Nick */ +int sftp_unlink(sftp_session sftp, const char *file) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + 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, + "ds", + id, + file); + 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_REMOVE, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp)) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_STATUS) { + /* by specification, this command's only supposed to return 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; + } + + /* + * The 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 remove file", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* code written by Nick */ +int sftp_rmdir(sftp_session sftp, const char *directory) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + 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, + "ds", + id, + directory); + 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_RMDIR, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + /* By specification, this command 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; + } + 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 remove directory", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* Code written by Nick */ +int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + sftp_attributes errno_attr = NULL; + struct sftp_attributes_struct attr; + ssh_buffer buffer; + uint32_t id; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + ZERO_STRUCT(attr); + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "ds", + id, + directory); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = buffer_add_attributes(buffer, &attr); + if (rc < 0) { + 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_MKDIR, 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_FAILURE: + /* + * mkdir always returns a failure, even if the path already exists. + * To be POSIX conform and to be able to map it to EEXIST a stat + * call is needed here. + */ + errno_attr = sftp_lstat(sftp, directory); + if (errno_attr != NULL) { + SAFE_FREE(errno_attr); + sftp_set_error(sftp, SSH_FX_FILE_ALREADY_EXISTS); + } + break; + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * The 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 make directory", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* 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; + + 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, + "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->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); + } + + if (sftp_packet_write(sftp, SSH_FXP_RENAME, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + 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 rename", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* Code written by Nick */ +/* Set file attributes on a file, directory or symbolic link. */ +int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr) +{ + uint32_t id; + ssh_buffer buffer; + sftp_message msg = NULL; + sftp_status_message status = NULL; + int rc; + + 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, + "ds", + id, + file); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = buffer_add_attributes(buffer, attr); + if (rc != 0) { + 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_SETSTAT, 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; + } + /* + * The 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 set stats", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* Change the file owner and group */ +int sftp_chown(sftp_session sftp, const char *file, uid_t owner, gid_t group) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + + attr.uid = owner; + attr.gid = group; + + attr.flags = SSH_FILEXFER_ATTR_UIDGID; + + return sftp_setstat(sftp, file, &attr); +} + +/* Change permissions of a file */ +int sftp_chmod(sftp_session sftp, const char *file, mode_t mode) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + + return sftp_setstat(sftp, file, &attr); +} + +/* Change the last modification and access time of a file. */ +int sftp_utimes(sftp_session sftp, const char *file, + const struct timeval *times) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + + attr.atime = times[0].tv_sec; + attr.atime_nseconds = times[0].tv_usec; + + attr.mtime = times[1].tv_sec; + attr.mtime_nseconds = times[1].tv_usec; + + attr.flags |= SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_MODIFYTIME | + SSH_FILEXFER_ATTR_SUBSECOND_TIMES; + + return sftp_setstat(sftp, file, &attr); +} + +int sftp_symlink(sftp_session sftp, const char *target, const char *dest) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) + return -1; + if (target == NULL || dest == 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); + + /* TODO check for version number if they ever fix it. */ + if (ssh_get_openssh_version(sftp->session)) { + rc = ssh_buffer_pack(buffer, + "dss", + id, + target, + dest); + } else { + rc = ssh_buffer_pack(buffer, + "dss", + id, + dest, + target); + } + 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_SYMLINK, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + 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; + } + /* + * The 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 set stats", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +char *sftp_readlink(sftp_session sftp, const char *path) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) { + return NULL; + } + + if (path == NULL) { + ssh_set_error_invalid(sftp); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + if (sftp->version < 3){ + ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_readlink",sftp->version); + 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, + "ds", + id, + path); + if (rc < 0) { + 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_READLINK, 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_NAME) { + uint32_t ignored = 0; + char *lnk = NULL; + + rc = ssh_buffer_unpack(msg->payload, + "ds", + &ignored, + &lnk); + sftp_message_free(msg); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to retrieve link"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return lnk; + } 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 set stats", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +static sftp_statvfs_t sftp_parse_statvfs(sftp_session sftp, ssh_buffer buf) { + sftp_statvfs_t statvfs; + int rc; + + statvfs = calloc(1, sizeof(struct sftp_statvfs_struct)); + if (statvfs == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_unpack(buf, "qqqqqqqqqqq", + &statvfs->f_bsize, /* file system block size */ + &statvfs->f_frsize, /* fundamental fs block size */ + &statvfs->f_blocks, /* number of blocks (unit f_frsize) */ + &statvfs->f_bfree, /* free blocks in file system */ + &statvfs->f_bavail, /* free blocks for non-root */ + &statvfs->f_files, /* total file inodes */ + &statvfs->f_ffree, /* free file inodes */ + &statvfs->f_favail, /* free file inodes for to non-root */ + &statvfs->f_fsid, /* file system id */ + &statvfs->f_flag, /* bit mask of f_flag values */ + &statvfs->f_namemax/* maximum filename length */ + ); + if (rc != SSH_OK) { + SAFE_FREE(statvfs); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid statvfs structure"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return statvfs; +} + +sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) + return NULL; + if (path == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + if (sftp->version < 3){ + ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_statvfs",sftp->version); + 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, + "statvfs@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) { + if (sftp_read_and_dispatch(sftp) < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload); + sftp_message_free(msg); + if (buf == NULL) { + return NULL; + } + + return buf; + } 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 statvfs", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +int sftp_fsync(sftp_file file) +{ + sftp_session sftp; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (file == NULL) { + return -1; + } + sftp = file->sftp; + + 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, + "dsS", + id, + "fsync@openssh.com", + file->handle); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto done; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + goto done; + } + + do { + rc = sftp_read_and_dispatch(sftp); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + rc = -1; + goto done; + } + msg = sftp_dequeue(sftp, id); + } while (msg == NULL); + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + sftp_status_message status; + + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + rc = -1; + goto done; + } + + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + /* SUCCESS, LEAVE */ + status_msg_free(status); + rc = 0; + goto done; + default: + break; + } + + /* + * The 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); + + rc = -1; + goto done; + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d when attempting to set stats", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + rc = -1; +done: + SSH_BUFFER_FREE(buffer); + + return rc; +} + +sftp_statvfs_t sftp_fstatvfs(sftp_file file) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + sftp_session sftp; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (file == NULL) { + return NULL; + } + sftp = file->sftp; + + 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, + "fstatvfs@openssh.com", + file->handle); + if (rc < 0) { + 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_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload); + sftp_message_free(msg); + if (buf == NULL) { + return NULL; + } + + return buf; + } 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 set stats", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +void sftp_statvfs_free(sftp_statvfs_t statvfs) { + if (statvfs == NULL) { + return; + } + + SAFE_FREE(statvfs); +} + +/* another code written by Nick */ +char *sftp_canonicalize_path(sftp_session sftp, const char *path) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) + return NULL; + if (path == NULL) { + ssh_set_error_invalid(sftp->session); + 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, + "ds", + id, + path); + if (rc < 0) { + 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_REALPATH, 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_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 canonicalized path"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return cname; + } 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 set stats", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +static sftp_attributes sftp_xstat(sftp_session sftp, + const char *path, + int param) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) { + return NULL; + } + + if (path == NULL) { + ssh_set_error_invalid(sftp->session); + 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, + "ds", + id, + 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, param, 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_ATTRS) { + sftp_attributes attr = sftp_parse_attr(sftp, msg->payload, 0); + sftp_message_free(msg); + + return attr; + } 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); + return NULL; + } + ssh_set_error(sftp->session, SSH_FATAL, + "Received mesg %d during stat()", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + + return NULL; +} + +sftp_attributes sftp_stat(sftp_session session, const char *path) { + return sftp_xstat(session, path, SSH_FXP_STAT); +} + +sftp_attributes sftp_lstat(sftp_session session, const char *path) { + return sftp_xstat(session, path, SSH_FXP_LSTAT); +} + +sftp_attributes sftp_fstat(sftp_file file) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (file == NULL) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(file->sftp->session); + sftp_set_error(file->sftp, SSH_FX_FAILURE); + return NULL; + } + + id = sftp_get_new_id(file->sftp); + + rc = ssh_buffer_pack(buffer, + "dS", + id, + file->handle); + if (rc != SSH_OK) { + ssh_set_error_oom(file->sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(file->sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(file->sftp, SSH_FXP_FSTAT, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + while (msg == NULL) { + if (sftp_read_and_dispatch(file->sftp) < 0) { + return NULL; + } + msg = sftp_dequeue(file->sftp, id); + } + + if (msg->packet_type == SSH_FXP_ATTRS){ + sftp_attributes attr = sftp_parse_attr(file->sftp, msg->payload, 0); + sftp_message_free(msg); + + return attr; + } 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(file->sftp, status->status); + ssh_set_error(file->sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + + return NULL; + } + ssh_set_error(file->sftp->session, SSH_FATAL, + "Received msg %d during fstat()", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(file->sftp, SSH_FX_BAD_MESSAGE); + + return NULL; +} + +#endif /* WITH_SFTP */ diff --git a/src/sftpserver.c b/src/sftpserver.c new file mode 100644 index 0000000..5a2110e --- /dev/null +++ b/src/sftpserver.c @@ -0,0 +1,546 @@ +/* + * sftpserver.c - server based function for the sftp protocol + * + * This file is part of the SSH Library + * + * Copyright (c) 2005 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 + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/libssh.h" +#include "libssh/sftp.h" +#include "libssh/sftp_priv.h" +#include "libssh/ssh2.h" +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/misc.h" + +#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; + + msg = malloc(sizeof (struct sftp_client_message_struct)); + if (msg == NULL) { + ssh_set_error_oom(session); + return NULL; + } + ZERO_STRUCTP(msg); + + packet = sftp_packet_read(sftp); + if (packet == NULL) { + ssh_set_error_oom(session); + 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(); + ssh_buffer_add_data(msg->complete_message, + ssh_buffer_get(payload), + ssh_buffer_get_len(payload)); + + ssh_buffer_get_u32(payload, &msg->id); + + 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); + 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; +} + +/* 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); +} + +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; +} + +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; +} + +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; +} + +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); +} + +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; + } + + 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) { + SSH_BUFFER_FREE(out); + SSH_STRING_FREE(file); + return -1; + } + SSH_BUFFER_FREE(out); + SSH_STRING_FREE(file); + + return 0; +} + +int sftp_reply_handle(sftp_client_message msg, ssh_string handle){ + ssh_buffer out; + + 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_BUFFER_FREE(out); + return -1; + } + SSH_BUFFER_FREE(out); + + return 0; +} + +int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr) { + ssh_buffer out; + + 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_BUFFER_FREE(out); + return -1; + } + SSH_BUFFER_FREE(out); + + return 0; +} + +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; + } + + if (msg->attrbuf == NULL) { + 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; + } + + 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++; + + return 0; +} + +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; + } + + 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; + + return 0; +} + +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; + } + + s = ssh_string_from_char(message ? message : ""); + if (s == NULL) { + SSH_BUFFER_FREE(out); + 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; +} + +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; + } + + 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 0; +} + +/* + * 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; + + if (sftp->handles == 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; + } + } + + if (i == SFTP_HANDLES) { + return NULL; /* no handle available */ + } + + 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; + + return ret; +} + +void *sftp_handle(sftp_session sftp, ssh_string handle){ + uint32_t val; + + if (sftp->handles == NULL) { + return NULL; + } + + if (ssh_string_len(handle) != sizeof(uint32_t)) { + return NULL; + } + + memcpy(&val, ssh_string_data(handle), sizeof(uint32_t)); + + 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; + } + } +} diff --git a/src/socket.c b/src/socket.c new file mode 100644 index 0000000..2fef8e7 --- /dev/null +++ b/src/socket.c @@ -0,0 +1,939 @@ +/* + * socket.c - socket functions for the library + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2010 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 +#include +#ifdef _WIN32 +#include +#include +#if _MSC_VER >= 1400 +#include +#undef open +#define open _open +#undef close +#define close _close +#undef read +#define read _read +#undef write +#define write _write +#endif /* _MSC_VER */ +#else /* _WIN32 */ +#include +#include +#include +#include +#include +#include +#endif /* _WIN32 */ + +#include "libssh/priv.h" +#include "libssh/callbacks.h" +#include "libssh/socket.h" +#include "libssh/buffer.h" +#include "libssh/poll.h" +#include "libssh/session.h" + +/** + * @internal + * + * @defgroup libssh_socket The SSH socket functions. + * @ingroup libssh + * + * Functions for handling sockets. + * + * @{ + */ + +enum ssh_socket_states_e { + SSH_SOCKET_NONE, + SSH_SOCKET_CONNECTING, + SSH_SOCKET_CONNECTED, + SSH_SOCKET_EOF, + SSH_SOCKET_ERROR, + SSH_SOCKET_CLOSED +}; + +struct ssh_socket_struct { + socket_t fd; + int fd_is_socket; + int last_errno; + int read_wontblock; /* reading now on socket will + not block */ + int write_wontblock; + int data_except; + enum ssh_socket_states_e state; + ssh_buffer out_buffer; + ssh_buffer in_buffer; + ssh_session session; + ssh_socket_callbacks callbacks; + ssh_poll_handle poll_handle; +#ifndef _WIN32 + pid_t proxy_pid; +#endif +}; + +static int sockets_initialized = 0; + +static ssize_t ssh_socket_unbuffered_read(ssh_socket s, + void *buffer, + uint32_t len); +static ssize_t ssh_socket_unbuffered_write(ssh_socket s, + const void *buffer, + uint32_t len); + +/** + * \internal + * \brief inits the socket system (windows specific) + */ +int ssh_socket_init(void) +{ + if (sockets_initialized == 0) { +#ifdef _WIN32 + struct WSAData wsaData; + + /* Initiates use of the Winsock DLL by a process. */ + if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0) { + return -1; + } +#endif + ssh_poll_init(); + + sockets_initialized = 1; + } + + return 0; +} + +/** + * @brief Cleanup the socket system. + */ +void ssh_socket_cleanup(void) +{ + if (sockets_initialized == 1) { + ssh_poll_cleanup(); +#ifdef _WIN32 + WSACleanup(); +#endif + sockets_initialized = 0; + } +} + + +/** + * \internal + * \brief creates a new Socket object + */ +ssh_socket ssh_socket_new(ssh_session session) +{ + ssh_socket s; + + s = calloc(1, sizeof(struct ssh_socket_struct)); + if (s == NULL) { + ssh_set_error_oom(session); + return NULL; + } + s->fd = SSH_INVALID_SOCKET; + s->last_errno = -1; + s->fd_is_socket = 1; + s->session = session; + s->in_buffer = ssh_buffer_new(); + if (s->in_buffer == NULL) { + ssh_set_error_oom(session); + SAFE_FREE(s); + return NULL; + } + s->out_buffer=ssh_buffer_new(); + if (s->out_buffer == NULL) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(s->in_buffer); + SAFE_FREE(s); + return NULL; + } + s->read_wontblock = 0; + s->write_wontblock = 0; + s->data_except = 0; + s->poll_handle = NULL; + s->state=SSH_SOCKET_NONE; + return s; +} + +/** + * @internal + * @brief Reset the state of a socket so it looks brand-new + * @param[in] s socket to rest + */ +void ssh_socket_reset(ssh_socket s) +{ + s->fd = SSH_INVALID_SOCKET; + s->last_errno = -1; + s->fd_is_socket = 1; + ssh_buffer_reinit(s->in_buffer); + ssh_buffer_reinit(s->out_buffer); + s->read_wontblock = 0; + s->write_wontblock = 0; + s->data_except = 0; + s->poll_handle = NULL; + s->state=SSH_SOCKET_NONE; +#ifndef _WIN32 + s->proxy_pid = 0; +#endif +} + +/** + * @internal + * @brief the socket callbacks, i.e. callbacks to be called + * upon a socket event. + * @param s socket to set callbacks on. + * @param callbacks a ssh_socket_callback object reference. + */ + +void ssh_socket_set_callbacks(ssh_socket s, ssh_socket_callbacks callbacks) +{ + s->callbacks = callbacks; +} + +/** + * @brief SSH poll callback. This callback will be used when an event + * caught on the socket. + * + * @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, + * in this case the socket object. + * + * @return 0 on success, < 0 when the poll object has been removed + * from its poll context. + */ +int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, + socket_t fd, + int revents, + void *v_s) +{ + ssh_socket s = (ssh_socket)v_s; + char buffer[MAX_BUF_SIZE]; + ssize_t nread; + int rc; + int err = 0; + socklen_t errlen = sizeof(err); + + /* Do not do anything if this socket was already closed */ + 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, + (revents & POLLIN) ? "POLLIN ":"", + (revents & POLLOUT) ? "POLLOUT ":"", + (revents & POLLERR) ? "POLLERR":"", + ssh_buffer_get_len(s->out_buffer)); + if ((revents & POLLERR) || (revents & POLLHUP)) { + /* Check if we are in a connecting state */ + if (s->state == SSH_SOCKET_CONNECTING) { + s->state = SSH_SOCKET_ERROR; + rc = getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&err, &errlen); + if (rc < 0) { + err = errno; + } + ssh_socket_close(s); + /* Overwrite ssh_socket_close() error with the real socket error */ + s->last_errno = err; + errno = err; + + if (s->callbacks != NULL && s->callbacks->connected != NULL) { + s->callbacks->connected(SSH_SOCKET_CONNECTED_ERROR, + err, + s->callbacks->userdata); + } + + return -1; + } + /* Then we are in a more standard kind of error */ + /* force a read to get an explanation */ + revents |= POLLIN; + } + if ((revents & POLLIN) && s->state == SSH_SOCKET_CONNECTED) { + s->read_wontblock = 1; + nread = ssh_socket_unbuffered_read(s, buffer, sizeof(buffer)); + if (nread < 0) { + if (p != NULL) { + ssh_poll_remove_events(p, POLLIN); + } + + if (s->callbacks != NULL && s->callbacks->exception != NULL) { + s->callbacks->exception(SSH_SOCKET_EXCEPTION_ERROR, + s->last_errno, + s->callbacks->userdata); + } + return -2; + } + if (nread == 0) { + if (p != NULL) { + ssh_poll_remove_events(p, POLLIN); + } + if (s->callbacks != NULL && s->callbacks->exception != NULL) { + s->callbacks->exception(SSH_SOCKET_EXCEPTION_EOF, + 0, + s->callbacks->userdata); + } + return -2; + } + + if (s->session->socket_counter != NULL) { + 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; + } + if (s->callbacks != NULL && s->callbacks->data != NULL) { + 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)); + + /* p may have been freed, so don't use it + * anymore in this function */ + p = NULL; + } + } +#ifdef _WIN32 + if (revents & POLLOUT || revents & POLLWRNORM) { +#else + if (revents & POLLOUT) { +#endif + uint32_t len; + + /* 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); + } + + rc = ssh_socket_set_blocking(ssh_socket_get_fd(s)); + if (rc < 0) { + return -1; + } + + if (s->callbacks != NULL && s->callbacks->connected != NULL) { + s->callbacks->connected(SSH_SOCKET_CONNECTED_OK, + 0, + s->callbacks->userdata); + } + + return 0; + } + + /* So, we can write data */ + s->write_wontblock = 1; + if (p != NULL) { + ssh_poll_remove_events(p, POLLOUT); + } + + /* If buffered data is pending, write it */ + len = ssh_buffer_get_len(s->out_buffer); + if (len > 0) { + 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"); + s->callbacks->controlflow(SSH_SOCKET_FLOW_WRITEWONTBLOCK, + s->callbacks->userdata); + } + /* TODO: Find a way to put back POLLOUT when buffering occurs */ + } + + /* Return -1 if the poll handler disappeared */ + if (s->poll_handle == NULL) { + return -1; + } + + return 0; +} + +/** @internal + * @brief returns the poll handle corresponding to the socket, + * creates it if it does not exist. + * @returns allocated and initialized ssh_poll_handle object + */ +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); + return s->poll_handle; +} + +/** \internal + * \brief Deletes a socket object + */ +void ssh_socket_free(ssh_socket s) +{ + if (s == NULL) { + return; + } + ssh_socket_close(s); + SSH_BUFFER_FREE(s->in_buffer); + SSH_BUFFER_FREE(s->out_buffer); + SAFE_FREE(s); +} + +#ifndef _WIN32 +int ssh_socket_unix(ssh_socket s, const char *path) +{ + struct sockaddr_un sunaddr; + socket_t fd; + sunaddr.sun_family = AF_UNIX; + snprintf(sunaddr.sun_path, sizeof(sunaddr.sun_path), "%s", path); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == SSH_INVALID_SOCKET) { + ssh_set_error(s->session, SSH_FATAL, + "Error from socket(AF_UNIX, SOCK_STREAM, 0): %s", + strerror(errno)); + return -1; + } + + 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); + return -1; + } + + if (connect(fd, (struct sockaddr *) &sunaddr, sizeof(sunaddr)) < 0) { + ssh_set_error(s->session, SSH_FATAL, "Error from connect(): %s", + strerror(errno)); + close(fd); + return -1; + } + ssh_socket_set_fd(s,fd); + return 0; +} +#endif + +/** \internal + * \brief closes a socket + */ +void ssh_socket_close(ssh_socket s) +{ + if (ssh_socket_is_open(s)) { +#ifdef _WIN32 + CLOSE_SOCKET(s->fd); + s->last_errno = WSAGetLastError(); +#else + CLOSE_SOCKET(s->fd); + s->last_errno = errno; +#endif + } + + if (s->poll_handle != NULL) { + ssh_poll_free(s->poll_handle); + s->poll_handle = NULL; + } + + s->state = SSH_SOCKET_CLOSED; + +#ifndef _WIN32 + /* If the proxy command still runs try to kill it */ + if (s->proxy_pid != 0) { + int status; + pid_t pid = s->proxy_pid; + + s->proxy_pid = 0; + kill(pid, SIGTERM); + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + SSH_LOG(SSH_LOG_WARN, "waitpid failed: %s", strerror(errno)); + return; + } + } + if (!WIFEXITED(status)) { + SSH_LOG(SSH_LOG_WARN, "Proxy command exitted abnormally"); + return; + } + SSH_LOG(SSH_LOG_TRACE, "Proxy command returned %d", WEXITSTATUS(status)); + } +#endif +} + +/** + * @internal + * @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 + * file descriptors + */ +void ssh_socket_set_fd(ssh_socket s, socket_t fd) +{ + s->fd = fd; + + if (s->poll_handle) { + ssh_poll_set_fd(s->poll_handle,fd); + } else { + s->state = SSH_SOCKET_CONNECTING; + + /* POLLOUT is the event to wait for in a nonblocking connect */ + ssh_poll_set_events(ssh_socket_get_poll_handle(s), POLLOUT); +#ifdef _WIN32 + ssh_poll_add_events(ssh_socket_get_poll_handle(s), POLLWRNORM); +#endif + } +} + +/** \internal + * \brief returns the input file descriptor of the socket + */ +socket_t ssh_socket_get_fd(ssh_socket s) +{ + return s->fd; +} + +/** \internal + * \brief returns nonzero if the socket is open + */ +int ssh_socket_is_open(ssh_socket s) +{ + return s->fd != SSH_INVALID_SOCKET; +} + +/** \internal + * \brief read len bytes from socket into buffer + */ +static ssize_t ssh_socket_unbuffered_read(ssh_socket s, + void *buffer, + uint32_t len) +{ + ssize_t rc = -1; + + if (s->data_except) { + return -1; + } + if (s->fd_is_socket) { + rc = recv(s->fd,buffer, len, 0); + } else { + rc = read(s->fd,buffer, len); + } +#ifdef _WIN32 + s->last_errno = WSAGetLastError(); +#else + s->last_errno = errno; +#endif + s->read_wontblock = 0; + + if (rc < 0) { + s->data_except = 1; + } + + return rc; +} + +/** \internal + * \brief writes len bytes from buffer to socket + */ +static ssize_t ssh_socket_unbuffered_write(ssh_socket s, + const void *buffer, + uint32_t len) +{ + ssize_t w = -1; + int flags = 0; + +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + + if (s->data_except) { + return -1; + } + + if (s->fd_is_socket) { + w = send(s->fd, buffer, len, flags); + } else { + w = write(s->fd, buffer, len); + } +#ifdef _WIN32 + s->last_errno = WSAGetLastError(); +#else + s->last_errno = errno; +#endif + s->write_wontblock = 0; + /* 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); + } + if (w < 0) { + s->data_except = 1; + } + + return w; +} + +/** \internal + * \brief returns nonzero if the current socket is in the fd_set + */ +int ssh_socket_fd_isset(ssh_socket s, fd_set *set) +{ + if(s->fd == SSH_INVALID_SOCKET) { + return 0; + } + return FD_ISSET(s->fd,set); +} + +/** \internal + * \brief sets the current fd in a fd_set and updates the max_fd + */ + +void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd) +{ + if (s->fd == SSH_INVALID_SOCKET) { + return; + } + + FD_SET(s->fd,set); + + if (s->fd >= 0 && + s->fd >= *max_fd && + s->fd != SSH_INVALID_SOCKET) { + *max_fd = s->fd + 1; + } +} + +/** \internal + * \brief buffered write of data + * \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) +{ + if (len > 0) { + if (ssh_buffer_add_data(s->out_buffer, buffer, len) < 0) { + ssh_set_error_oom(s->session); + return SSH_ERROR; + } + ssh_socket_nonblocking_flush(s); + } + + return SSH_OK; +} + + +/** \internal + * \brief starts a nonblocking flush of the output buffer + * + */ +int ssh_socket_nonblocking_flush(ssh_socket s) +{ + ssh_session session = s->session; + uint32_t len; + + if (!ssh_socket_is_open(s)) { + session->alive = 0; + if (s->callbacks && s->callbacks->exception) { + s->callbacks->exception(SSH_SOCKET_EXCEPTION_ERROR, + s->last_errno, + s->callbacks->userdata); + } else { + ssh_set_error(session, + SSH_FATAL, + "Writing packet: error on socket (or connection " + "closed): %s", + strerror(s->last_errno)); + } + + return SSH_ERROR; + } + + len = ssh_buffer_get_len(s->out_buffer); + if (!s->write_wontblock && s->poll_handle && len > 0) { + /* force the poll system to catch pollout events */ + ssh_poll_add_events(s->poll_handle, POLLOUT); + + return SSH_AGAIN; + } + + if (s->write_wontblock && len > 0) { + ssize_t bwritten; + + bwritten = ssh_socket_unbuffered_write(s, + ssh_buffer_get(s->out_buffer), + len); + if (bwritten < 0) { + session->alive = 0; + ssh_socket_close(s); + + if (s->callbacks && s->callbacks->exception) { + s->callbacks->exception(SSH_SOCKET_EXCEPTION_ERROR, + s->last_errno, + s->callbacks->userdata); + } else { + ssh_set_error(session, + SSH_FATAL, + "Writing packet: error on socket (or connection " + "closed): %s", + strerror(s->last_errno)); + } + + return SSH_ERROR; + } + + ssh_buffer_pass_bytes(s->out_buffer, bwritten); + if (s->session->socket_counter != NULL) { + s->session->socket_counter->out_bytes += bwritten; + } + } + + /* Is there some data pending? */ + len = ssh_buffer_get_len(s->out_buffer); + if (s->poll_handle && len > 0) { + /* force the poll system to catch pollout events */ + ssh_poll_add_events(s->poll_handle, POLLOUT); + + return SSH_AGAIN; + } + + /* all data written */ + return SSH_OK; +} + +void ssh_socket_set_write_wontblock(ssh_socket s) +{ + s->write_wontblock = 1; +} + +void ssh_socket_set_read_wontblock(ssh_socket s) +{ + s->read_wontblock = 1; +} + +void ssh_socket_set_except(ssh_socket s) +{ + s->data_except = 1; +} + +int ssh_socket_data_available(ssh_socket s) +{ + return s->read_wontblock; +} + +int ssh_socket_data_writable(ssh_socket s) +{ + return s->write_wontblock; +} + +/** @internal + * @brief returns the number of outgoing bytes currently buffered + * @param s the socket + * @returns numbers of bytes buffered, or 0 if the socket isn't connected + */ +int ssh_socket_buffered_write_bytes(ssh_socket s) +{ + if (s==NULL || s->out_buffer == NULL) { + return 0; + } + + return ssh_buffer_get_len(s->out_buffer); +} + + +int ssh_socket_get_status(ssh_socket s) +{ + int r = 0; + + if (ssh_buffer_get_len(s->in_buffer) > 0) { + r |= SSH_READ_PENDING; + } + + if (ssh_buffer_get_len(s->out_buffer) > 0) { + r |= SSH_WRITE_PENDING; + } + + if (s->data_except) { + r |= SSH_CLOSED_ERROR; + } + + return r; +} + +int ssh_socket_get_poll_flags(ssh_socket s) +{ + int r = 0; + if (s->poll_handle != NULL && (ssh_poll_get_events (s->poll_handle) & POLLIN) > 0) { + r |= SSH_READ_PENDING; + } + if (s->poll_handle != NULL && (ssh_poll_get_events (s->poll_handle) & POLLOUT) > 0) { + r |= SSH_WRITE_PENDING; + } + return r; +} + +#ifdef _WIN32 +int ssh_socket_set_nonblocking(socket_t fd) +{ + u_long nonblocking = 1; + return ioctlsocket(fd, FIONBIO, &nonblocking); +} + +int ssh_socket_set_blocking(socket_t fd) +{ + u_long nonblocking = 0; + return ioctlsocket(fd, FIONBIO, &nonblocking); +} + +#else /* _WIN32 */ +int ssh_socket_set_nonblocking(socket_t fd) +{ + return fcntl(fd, F_SETFL, O_NONBLOCK); +} + +int ssh_socket_set_blocking(socket_t fd) +{ + return fcntl(fd, F_SETFL, 0); +} +#endif /* _WIN32 */ + +/** + * @internal + * @brief Launches a socket connection + * If a 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. + * @param port port number to connect to. + * @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, + uint16_t port, + 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); + if (fd == SSH_INVALID_SOCKET) { + return SSH_ERROR; + } + ssh_socket_set_fd(s,fd); + + return SSH_OK; +} + +#ifndef _WIN32 +/** + * @internal + * @brief executes a command and redirect input and outputs + * @param command command to execute + * @param in input file descriptor + * @param out output file descriptor + */ +void +ssh_execute_command(const char *command, socket_t in, socket_t out) +{ + const char *args[] = {"/bin/sh", "-c", command, NULL}; + /* Prepare /dev/null socket for the stderr redirection */ + int devnull = open("/dev/null", O_WRONLY); + if (devnull == -1) { + SSH_LOG(SSH_LOG_WARNING, "Failed to open /dev/null"); + exit(1); + } + + /* redirect in and out to stdin, stdout */ + dup2(in, 0); + dup2(out, 1); + /* Ignore anything on the stderr */ + dup2(devnull, STDERR_FILENO); + close(in); + close(out); + execv(args[0], (char * const *)args); + exit(1); +} + +/** + * @internal + * @brief Open a socket on a ProxyCommand + * This call will always be nonblocking. + * @param s socket to connect. + * @param command Command to execute. + * @returns SSH_OK socket is being connected. + * @returns SSH_ERROR error while executing the command. + */ + +int +ssh_socket_connect_proxycommand(ssh_socket s, const char *command) +{ + socket_t pair[2]; + int pid; + int rc; + + if (s->state != SSH_SOCKET_NONE) { + return SSH_ERROR; + } + + rc = socketpair(PF_UNIX, SOCK_STREAM, 0, pair); + if (rc < 0) { + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Executing proxycommand '%s'", command); + pid = fork(); + if (pid == 0) { + ssh_execute_command(command, pair[0], pair[0]); + /* Does not return */ + } + s->proxy_pid = pid; + close(pair[0]); + SSH_LOG(SSH_LOG_PROTOCOL, "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); + if (s->callbacks && s->callbacks->connected) { + s->callbacks->connected(SSH_SOCKET_CONNECTED_OK, 0, s->callbacks->userdata); + } + + return SSH_OK; +} + +#endif /* _WIN32 */ +/** @} */ diff --git a/src/string.c b/src/string.c new file mode 100644 index 0000000..acd3cf4 --- /dev/null +++ b/src/string.c @@ -0,0 +1,281 @@ +/* + * string.c - ssh string functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 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 +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/string.h" + +/* String maximum size is 256M */ +#define STRING_SIZE_MAX 0x10000000 + +/** + * @defgroup libssh_string The SSH string functions + * @ingroup libssh + * + * @brief String manipulations used in libssh. + * + * @{ + */ + +/** + * @brief Create a new SSH String object. + * + * @param[in] size The size of the string. + * + * @return The newly allocated string, NULL on error. + */ +struct ssh_string_struct *ssh_string_new(size_t size) +{ + struct ssh_string_struct *str = NULL; + + if (size > STRING_SIZE_MAX) { + errno = EINVAL; + return NULL; + } + + str = malloc(sizeof(struct ssh_string_struct) + size); + if (str == NULL) { + return NULL; + } + + str->size = htonl(size); + str->data[0] = 0; + + return str; +} + +/** + * @brief Fill a string with given data. The string should be big enough. + * + * @param s An allocated string to fill with data. + * + * @param data The data to fill the string with. + * + * @param len Size of data. + * + * @return 0 on success, < 0 on error. + */ +int ssh_string_fill(struct ssh_string_struct *s, const void *data, size_t len) { + if ((s == NULL) || (data == NULL) || + (len == 0) || (len > ssh_string_len(s))) { + return -1; + } + + memcpy(s->data, data, len); + + return 0; +} + +/** + * @brief Create a ssh string using a C string + * + * @param[in] what The source 0-terminated C string. + * + * @return The newly allocated string, NULL on error with errno + * set. + * + * @note The nul byte is not copied nor counted in the ouput string. + */ +struct ssh_string_struct *ssh_string_from_char(const char *what) { + struct ssh_string_struct *ptr; + size_t len; + + if(what == NULL) { + errno = EINVAL; + return NULL; + } + + len = strlen(what); + + ptr = ssh_string_new(len); + if (ptr == NULL) { + return NULL; + } + + memcpy(ptr->data, what, len); + + return ptr; +} + +/** + * @brief Return the size of a SSH string. + * + * @param[in] s The the input SSH string. + * + * @return The size of the content of the string, 0 on error. + */ +size_t ssh_string_len(struct ssh_string_struct *s) { + size_t size; + + if (s == NULL) { + return 0; + } + + size = ntohl(s->size); + if (size > 0 && size <= STRING_SIZE_MAX) { + return size; + } + + return 0; +} + +/** + * @brief Get the the string as a C nul-terminated string. + * + * This is only available as long as the SSH string exists. + * + * @param[in] s The SSH string to get the C string from. + * + * @return The char pointer, NULL on error. + */ +const char *ssh_string_get_char(struct ssh_string_struct *s) +{ + if (s == NULL) { + return NULL; + } + s->data[ssh_string_len(s)] = '\0'; + + return (const char *) s->data; +} + +/** + * @brief Convert a SSH string to a C nul-terminated string. + * + * @param[in] s The SSH input string. + * + * @return An allocated string pointer, NULL on error with errno + * set. + * + * @note If the input SSH string contains zeroes, some parts of the output + * string may not be readable with regular libc functions. + */ +char *ssh_string_to_char(struct ssh_string_struct *s) { + size_t len; + char *new; + + if (s == NULL) { + return NULL; + } + + len = ssh_string_len(s); + if (len + 1 < len) { + return NULL; + } + + new = malloc(len + 1); + if (new == NULL) { + return NULL; + } + memcpy(new, s->data, len); + new[len] = '\0'; + + return new; +} + +/** + * @brief Deallocate a char string object. + * + * @param[in] s The string to delete. + */ +void ssh_string_free_char(char *s) { + SAFE_FREE(s); +} + +/** + * @brief Copy a string, return a newly allocated string. The caller has to + * free the string. + * + * @param[in] s String to copy. + * + * @return Newly allocated copy of the string, NULL on error. + */ +struct ssh_string_struct *ssh_string_copy(struct ssh_string_struct *s) { + struct ssh_string_struct *new; + size_t len; + + if (s == NULL) { + return NULL; + } + + len = ssh_string_len(s); + if (len == 0) { + return NULL; + } + + new = ssh_string_new(len); + if (new == NULL) { + return NULL; + } + + memcpy(new->data, s->data, len); + + return new; +} + +/** + * @brief Destroy the data in a string so it couldn't appear in a core dump. + * + * @param[in] s The string to burn. + */ +void ssh_string_burn(struct ssh_string_struct *s) { + if (s == NULL || s->size == 0) { + return; + } + + explicit_bzero(s->data, ssh_string_len(s)); +} + +/** + * @brief Get the payload of the string. + * + * @param s The string to get the data from. + * + * @return Return the data of the string or NULL on error. + */ +void *ssh_string_data(struct ssh_string_struct *s) { + if (s == NULL) { + return NULL; + } + + return s->data; +} + +/** + * @brief Deallocate a SSH string object. + * + * \param[in] s The SSH string to delete. + */ +void ssh_string_free(struct ssh_string_struct *s) { + SAFE_FREE(s); +} + +/** @} */ diff --git a/src/threads.c b/src/threads.c new file mode 100644 index 0000000..792b976 --- /dev/null +++ b/src/threads.c @@ -0,0 +1,97 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 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. + */ + +/** + * @defgroup libssh_threads The SSH threading functions. + * @ingroup libssh + * + * Threading with libssh + * @{ + */ + +#include "config.h" + +#include "libssh/priv.h" +#include "libssh/crypto.h" +#include "libssh/threads.h" + +static struct ssh_threads_callbacks_struct *user_callbacks = NULL; + +/** @internal + * @brief inits the threading with the backend cryptographic libraries + */ + +int ssh_threads_init(void) +{ + static int threads_initialized = 0; + int rc; + + if (threads_initialized) { + return SSH_OK; + } + + /* first initialize the user_callbacks with our default handlers if not + * already the case + */ + if (user_callbacks == NULL){ + user_callbacks = ssh_threads_get_default(); + } + + /* Then initialize the crypto libraries threading callbacks */ + rc = crypto_thread_init(user_callbacks); + if (rc == SSH_OK) { + threads_initialized = 1; + } + return rc; +} + +void ssh_threads_finalize(void) +{ + crypto_thread_finalize(); +} + +int ssh_threads_set_callbacks(struct ssh_threads_callbacks_struct *cb) +{ + + int rc; + + if (user_callbacks != NULL) { + crypto_thread_finalize(); + } + + user_callbacks = cb; + + rc = crypto_thread_init(user_callbacks); + + return rc; +} + +const char *ssh_threads_get_type(void) +{ + if (user_callbacks != NULL) { + return user_callbacks->type; + } + return NULL; +} + +/** + * @} + */ diff --git a/src/threads/libcrypto.c b/src/threads/libcrypto.c new file mode 100644 index 0000000..5786948 --- /dev/null +++ b/src/threads/libcrypto.c @@ -0,0 +1,133 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * 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/threads.h" +#include + +#if (OPENSSL_VERSION_NUMBER >= 0x10100000) + +int crypto_thread_init(struct ssh_threads_callbacks_struct *cb) +{ + (void) cb; + return SSH_OK; +} + +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/libgcrypt.c b/src/threads/libgcrypt.c new file mode 100644 index 0000000..3560dc5 --- /dev/null +++ b/src/threads/libgcrypt.c @@ -0,0 +1,74 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * 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/threads.h" +#include + +#if (GCRYPT_VERSION_NUMBER >= 0x010600) +/* libgcrypt >= 1.6 does not support custom callbacks */ +GCRY_THREAD_OPTION_PTHREAD_IMPL; + +int crypto_thread_init(struct ssh_threads_callbacks_struct *user_callbacks) +{ + (void) user_callbacks; + + return SSH_OK; +} + +#else +/* Libgcrypt < 1.6 specific way of handling thread callbacks */ + +static struct gcry_thread_cbs gcrypt_threads_callbacks; + +int crypto_thread_init(struct ssh_threads_callbacks_struct *user_callbacks) +{ + int cmp; + + if (user_callbacks == NULL) { + return SSH_OK; + } + + cmp = strcmp(user_callbacks->type, "threads_noop"); + if (cmp == 0) { + gcrypt_threads_callbacks.option= GCRY_THREAD_OPTION_VERSION << 8 || + GCRY_THREAD_OPTION_DEFAULT; + } else { + gcrypt_threads_callbacks.option= GCRY_THREAD_OPTION_VERSION << 8 || + GCRY_THREAD_OPTION_USER; + } + + gcrypt_threads_callbacks.mutex_init = user_callbacks->mutex_init; + gcrypt_threads_callbacks.mutex_destroy = user_callbacks->mutex_destroy; + gcrypt_threads_callbacks.mutex_lock = user_callbacks->mutex_lock; + gcrypt_threads_callbacks.mutex_unlock = user_callbacks->mutex_unlock; + gcry_control(GCRYCTL_SET_THREAD_CBS, &gcrypt_threads_callbacks); + + return SSH_OK; +} + +#endif /* GCRYPT_VERSION_NUMBER */ + +void crypto_thread_finalize(void) +{ + return; +} diff --git a/src/threads/mbedtls.c b/src/threads/mbedtls.c new file mode 100644 index 0000000..6cc3fa5 --- /dev/null +++ b/src/threads/mbedtls.c @@ -0,0 +1,65 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * 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/threads.h" +#include + +#include + +int crypto_thread_init(struct ssh_threads_callbacks_struct *user_callbacks) +{ + int cmp; + + if (user_callbacks == NULL) { + return SSH_OK; + } + + cmp = strcmp(user_callbacks->type, "threads_noop"); + if (cmp == 0) { + return SSH_OK; + } +#ifdef MBEDTLS_THREADING_ALT + else { + if (user_callbacks != NULL) { + crypto_thread_finalize(); + } + + mbedtls_threading_set_alt(user_callbacks->mutex_init, + user_callbacks->mutex_destroy, + user_callbacks->mutex_lock, + user_callbacks->mutex_unlock); + } +#elif defined MBEDTLS_THREADING_PTHREAD + return SSH_OK; +#else + return SSH_ERROR; +#endif +} + +void crypto_thread_finalize(void) +{ +#ifdef MBEDTLS_THREADING_ALT + mbedtls_threading_free_alt(); +#endif + return; +} diff --git a/src/threads/noop.c b/src/threads/noop.c new file mode 100644 index 0000000..63aca1e --- /dev/null +++ b/src/threads/noop.c @@ -0,0 +1,74 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * 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/threads.h" +#include + +static int threads_noop(void **lock) +{ + (void)lock; + + return 0; +} + +static unsigned long threads_id_noop (void) +{ + return 1; +} + +static struct ssh_threads_callbacks_struct ssh_threads_noop = +{ + .type = "threads_noop", + .mutex_init = threads_noop, + .mutex_destroy = threads_noop, + .mutex_lock = threads_noop, + .mutex_unlock = threads_noop, + .thread_id = threads_id_noop +}; + +/* Threads interface implementation */ + +#if !(HAVE_PTHREAD) && !(defined _WIN32 || defined _WIN64) +void ssh_mutex_lock(SSH_MUTEX *mutex) +{ + (void) mutex; + + return; +} + +void ssh_mutex_unlock(SSH_MUTEX *mutex) +{ + (void) mutex; + + return; +} + +struct ssh_threads_callbacks_struct *ssh_threads_get_default(void) +{ + return &ssh_threads_noop; +} +#endif + +struct ssh_threads_callbacks_struct *ssh_threads_get_noop(void) +{ + return &ssh_threads_noop; +} diff --git a/src/threads/pthread.c b/src/threads/pthread.c new file mode 100644 index 0000000..422dd85 --- /dev/null +++ b/src/threads/pthread.c @@ -0,0 +1,140 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 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/threads.h" +#include + +#include +#include +#include + +static int ssh_pthread_mutex_init (void **mutex) +{ + int rc = 0; + + if (mutex == NULL) { + return EINVAL; + } + + *mutex = malloc(sizeof(pthread_mutex_t)); + if (*mutex == NULL) { + return ENOMEM; + } + + rc = pthread_mutex_init ((pthread_mutex_t *)*mutex, NULL); + if (rc){ + free (*mutex); + *mutex = NULL; + } + + return rc; +} + +static int ssh_pthread_mutex_destroy (void **mutex) +{ + + int rc = 0; + + if (mutex == NULL) { + return EINVAL; + } + + rc = pthread_mutex_destroy ((pthread_mutex_t *)*mutex); + + free (*mutex); + *mutex = NULL; + + return rc; +} + +static int ssh_pthread_mutex_lock (void **mutex) +{ + return pthread_mutex_lock((pthread_mutex_t *)*mutex); +} + +static int ssh_pthread_mutex_unlock (void **mutex) +{ + return pthread_mutex_unlock((pthread_mutex_t *)*mutex); +} + +static unsigned long ssh_pthread_thread_id (void) +{ +#if defined(_WIN32) && !defined(__WINPTHREADS_VERSION) + return (unsigned long) pthread_self().p; +#else + return (unsigned long) pthread_self(); +#endif +} + +static struct ssh_threads_callbacks_struct ssh_threads_pthread = +{ + .type = "threads_pthread", + .mutex_init = ssh_pthread_mutex_init, + .mutex_destroy = ssh_pthread_mutex_destroy, + .mutex_lock = ssh_pthread_mutex_lock, + .mutex_unlock = ssh_pthread_mutex_unlock, + .thread_id = ssh_pthread_thread_id +}; + +/* Threads interface implementation */ + +#if (HAVE_PTHREAD) +void ssh_mutex_lock(SSH_MUTEX *mutex) +{ + int rc; + + if (mutex == NULL) { + exit(EINVAL); + } + + rc = pthread_mutex_lock(mutex); + + if (rc) { + exit(rc); + } +} + +void ssh_mutex_unlock(SSH_MUTEX *mutex) +{ + int rc; + + if (mutex == NULL) { + exit(EINVAL); + } + + rc = pthread_mutex_unlock(mutex); + + if (rc) { + exit(rc); + } +} + +struct ssh_threads_callbacks_struct *ssh_threads_get_default(void) +{ + return &ssh_threads_pthread; +} +#endif + +struct ssh_threads_callbacks_struct *ssh_threads_get_pthread(void) +{ + return &ssh_threads_pthread; +} diff --git a/src/threads/winlocks.c b/src/threads/winlocks.c new file mode 100644 index 0000000..a179953 --- /dev/null +++ b/src/threads/winlocks.c @@ -0,0 +1,123 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * 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/threads.h" +#include + +#include +#include +#include + +static int ssh_winlock_mutex_init (void **priv) +{ + CRITICAL_SECTION *lock = malloc(sizeof(CRITICAL_SECTION)); + + if (lock == NULL) { + return ENOMEM; + } + + InitializeCriticalSection(lock); + + *priv = lock; + + return 0; +} + +static int ssh_winlock_mutex_destroy (void **lock) +{ + DeleteCriticalSection((CRITICAL_SECTION *) *lock); + free(*lock); + + return 0; +} + +static int ssh_winlock_mutex_lock (void **lock) +{ + EnterCriticalSection((CRITICAL_SECTION *) *lock); + return 0; +} + +static int ssh_winlock_mutex_unlock (void **lock) +{ + LeaveCriticalSection((CRITICAL_SECTION *) *lock); + return 0; +} + +static unsigned long ssh_winlock_thread_id (void) +{ + return GetCurrentThreadId(); +} + +static struct ssh_threads_callbacks_struct ssh_threads_winlock = +{ + .type = "threads_winlock", + .mutex_init = ssh_winlock_mutex_init, + .mutex_destroy = ssh_winlock_mutex_destroy, + .mutex_lock = ssh_winlock_mutex_lock, + .mutex_unlock = ssh_winlock_mutex_unlock, + .thread_id = ssh_winlock_thread_id +}; + +/* Threads interface implementation */ + +void ssh_mutex_lock(SSH_MUTEX *mutex) +{ + void *rc; + + CRITICAL_SECTION *mutex_tmp = NULL; + + if (*mutex == NULL) { + mutex_tmp = malloc(sizeof(CRITICAL_SECTION)); + + if (mutex_tmp == NULL) { + exit(ENOMEM); + } + + InitializeCriticalSection(mutex_tmp); + + rc = InterlockedCompareExchangePointer((PVOID*)mutex, + (PVOID)mutex_tmp, + NULL); + if (rc != NULL) { + DeleteCriticalSection(mutex_tmp); + free(mutex_tmp); + exit(ENOMEM); + } + } + + EnterCriticalSection(*mutex); +} + +void ssh_mutex_unlock(SSH_MUTEX *mutex) +{ + LeaveCriticalSection(*mutex); +} + +struct ssh_threads_callbacks_struct *ssh_threads_get_winlock(void) +{ + return &ssh_threads_winlock; +} + +struct ssh_threads_callbacks_struct *ssh_threads_get_default(void) +{ + return &ssh_threads_winlock; +} diff --git a/src/token.c b/src/token.c new file mode 100644 index 0000000..0924d3b --- /dev/null +++ b/src/token.c @@ -0,0 +1,412 @@ +/* + * token.c - Token list handling functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * Copyright (c) 2019 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * 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 +#include +#include + +#include "libssh/priv.h" +#include "libssh/token.h" + +/** + * @internal + * + * @brief Free the given tokens list structure. The used buffer is overwritten + * with zeroes before freed. + * + * @param[in] tokens The pointer to a structure to be freed; + */ +void ssh_tokens_free(struct ssh_tokens_st *tokens) +{ + int i; + if (tokens == NULL) { + return; + } + + if (tokens->tokens != NULL) { + for (i = 0; tokens->tokens[i] != NULL; i++) { + explicit_bzero(tokens->tokens[i], strlen(tokens->tokens[i])); + } + } + + SAFE_FREE(tokens->buffer); + SAFE_FREE(tokens->tokens); + SAFE_FREE(tokens); +} + +/** + * @internal + * + * @brief Split a given string on the given separator character. The returned + * structure holds an array of pointers (tokens) pointing to the obtained + * parts and a buffer where all the content of the list is stored. The last + * element of the array will always be set as NULL. + * + * @param[in] chain The string to split + * @param[in] separator The character used to separate the tokens. + * + * @return A newly allocated tokens list structure; NULL in case of error. + */ +struct ssh_tokens_st *ssh_tokenize(const char *chain, char separator) +{ + + struct ssh_tokens_st *tokens = NULL; + size_t num_tokens = 1, i = 1; + + char *found, *c; + + if (chain == NULL) { + return NULL; + } + + tokens = calloc(1, sizeof(struct ssh_tokens_st)); + if (tokens == NULL) { + return NULL; + } + + tokens->buffer= strdup(chain); + if (tokens->buffer == NULL) { + goto error; + } + + c = tokens->buffer; + do { + found = strchr(c, separator); + if (found != NULL) { + c = found + 1; + num_tokens++; + } + } while(found != NULL); + + /* Allocate tokens list */ + tokens->tokens = calloc(num_tokens + 1, sizeof(char *)); + if (tokens->tokens == NULL) { + goto error; + } + + /* First token starts in the beginning of the chain */ + tokens->tokens[0] = tokens->buffer; + c = tokens->buffer; + + for (i = 1; i < num_tokens; i++) { + /* Find next separator */ + found = strchr(c, separator); + if (found == NULL) { + break; + } + + /* Replace it with a string terminator */ + *found = '\0'; + + /* The next token starts in the next byte */ + c = found + 1; + + /* If we did not reach the end of the chain yet, set the next token */ + if (*c != '\0') { + tokens->tokens[i] = c; + } else { + break; + } + } + + return tokens; + +error: + ssh_tokens_free(tokens); + return NULL; +} + +/** + * @internal + * + * @brief Given two strings, the first containing a list of available tokens and + * the second containing a list of tokens to be searched ordered by preference, + * returns a copy of the first preferred token present in the available list. + * + * @param[in] available_list The list of available tokens + * @param[in] preferred_list The list of tokens to search, ordered by + * preference + * + * @return A newly allocated copy of the token if found; NULL otherwise + */ +char *ssh_find_matching(const char *available_list, + const char *preferred_list) +{ + struct ssh_tokens_st *a_tok = NULL, *p_tok = NULL; + + int i, j; + char *ret = NULL; + + if ((available_list == NULL) || (preferred_list == NULL)) { + return NULL; + } + + a_tok = ssh_tokenize(available_list, ','); + if (a_tok == NULL) { + return NULL; + } + + p_tok = ssh_tokenize(preferred_list, ','); + if (p_tok == NULL) { + goto out; + } + + for (i = 0; p_tok->tokens[i]; i++) { + for (j = 0; a_tok->tokens[j]; j++) { + if (strcmp(a_tok->tokens[j], p_tok->tokens[i]) == 0) { + ret = strdup(a_tok->tokens[j]); + goto out; + } + } + } + +out: + ssh_tokens_free(a_tok); + ssh_tokens_free(p_tok); + return ret; +} + +/** + * @internal + * + * @brief Given two strings, the first containing a list of available tokens and + * the second containing a list of tokens to be searched ordered by preference, + * returns a list of all matching tokens ordered by preference. + * + * @param[in] available_list The list of available tokens + * @param[in] preferred_list The list of tokens to search, ordered by + * preference + * + * @return A newly allocated string containing the list of all matching tokens; + * NULL otherwise + */ +char *ssh_find_all_matching(const char *available_list, + const char *preferred_list) +{ + struct ssh_tokens_st *a_tok = NULL, *p_tok = NULL; + int i, j; + char *ret = NULL; + size_t max, len, pos = 0; + int match; + + if ((available_list == NULL) || (preferred_list == NULL)) { + return NULL; + } + + max = MAX(strlen(available_list), strlen(preferred_list)); + + ret = calloc(1, max + 1); + if (ret == NULL) { + return NULL; + } + + a_tok = ssh_tokenize(available_list, ','); + if (a_tok == NULL) { + SAFE_FREE(ret); + goto out; + } + + p_tok = ssh_tokenize(preferred_list, ','); + if (p_tok == NULL) { + SAFE_FREE(ret); + goto out; + } + + for (i = 0; p_tok->tokens[i] ; i++) { + for (j = 0; a_tok->tokens[j]; j++) { + match = !strcmp(a_tok->tokens[j], p_tok->tokens[i]); + if (match) { + if (pos != 0) { + ret[pos] = ','; + pos++; + } + + len = strlen(a_tok->tokens[j]); + memcpy(&ret[pos], a_tok->tokens[j], len); + pos += len; + ret[pos] = '\0'; + } + } + } + + if (ret[0] == '\0') { + SAFE_FREE(ret); + } + +out: + ssh_tokens_free(a_tok); + ssh_tokens_free(p_tok); + return ret; +} + +/** + * @internal + * + * @brief Given a string containing a list of elements, remove all duplicates + * and return in a newly allocated string. + * + * @param[in] list The list to be freed of duplicates + * + * @return A newly allocated copy of the string free of duplicates; NULL in + * case of error. + */ +char *ssh_remove_duplicates(const char *list) +{ + struct ssh_tokens_st *tok = NULL; + + size_t i, j, num_tokens, max_len; + char *ret = NULL; + bool *should_copy = NULL, need_comma = false; + + if (list == NULL) { + return NULL; + } + + /* The maximum number of tokens is the size of the list */ + max_len = strlen(list); + if (max_len == 0) { + return NULL; + } + + /* Add space for ending '\0' */ + max_len++; + + tok = ssh_tokenize(list, ','); + if ((tok == NULL) || (tok->tokens == NULL) || (tok->tokens[0] == NULL)) { + goto out; + } + + should_copy = calloc(1, max_len); + if (should_copy == NULL) { + goto out; + } + + if (strlen(tok->tokens[0]) > 0) { + should_copy[0] = true; + } + + for (i = 1; tok->tokens[i]; i++) { + for (j = 0; j < i; j++) { + if (strcmp(tok->tokens[i], tok->tokens[j]) == 0) { + /* Found a duplicate; do not copy */ + should_copy[i] = false; + break; + } + } + + /* No matching token before */ + if (j == i) { + /* Only copy if it is not an empty string */ + if (strlen(tok->tokens[i]) > 0) { + should_copy[i] = true; + } else { + should_copy[i] = false; + } + } + } + + num_tokens = i; + + ret = calloc(1, max_len); + if (ret == NULL) { + goto out; + } + + for (i = 0; i < num_tokens; i++) { + if (should_copy[i]) { + if (need_comma) { + strncat(ret, ",", (max_len - strlen(ret) - 1)); + } + strncat(ret, tok->tokens[i], (max_len - strlen(ret) - 1)); + need_comma = true; + } + } + + /* If no comma is needed, nothing was copied */ + if (!need_comma) { + SAFE_FREE(ret); + } + +out: + SAFE_FREE(should_copy); + ssh_tokens_free(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 appended with + * all the elements of the second list, without duplicates. The order of the + * elements will be preserved. + * + * @param[in] list The first list + * @param[in] appended_list The list to be appended + * + * @return A newly allocated copy list containing all the elements of the + * kept_list appended with the elements of the appended_list without duplicates; + * NULL in case of error. + */ +char *ssh_append_without_duplicates(const char *list, + const char *appended_list) +{ + size_t concat_len = 0; + char *ret = NULL, *concat = NULL; + + if (list != NULL) { + concat_len = strlen(list); + } + + if (appended_list != NULL) { + concat_len += strlen(appended_list); + } + + if (concat_len == 0) { + return NULL; + } + + /* Add room for ending '\0' and for middle ',' */ + concat_len += 2; + concat = calloc(1, concat_len); + if (concat == NULL) { + return NULL; + } + + if (list != NULL) { + strcpy(concat, list); + strncat(concat, ",", concat_len - strlen(concat) - 1); + } + if (appended_list != NULL) { + strncat(concat, appended_list, concat_len - strlen(concat) - 1); + } + + ret = ssh_remove_duplicates(concat); + + SAFE_FREE(concat); + + return ret; +} diff --git a/src/wrapper.c b/src/wrapper.c new file mode 100644 index 0000000..7e57ab5 --- /dev/null +++ b/src/wrapper.c @@ -0,0 +1,583 @@ +/* + * wrapper.c - wrapper for crytpo functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 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. + */ + +/* + * 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() + * 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. + */ + +#include "config.h" + + +#include +#include +#include + +#ifdef WITH_ZLIB +#include +#endif + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/wrapper.h" +#include "libssh/pki.h" +#include "libssh/poly1305.h" +#include "libssh/dh.h" +#ifdef WITH_GEX +#include "libssh/dh-gex.h" +#endif /* WITH_GEX */ +#include "libssh/ecdh.h" +#include "libssh/curve25519.h" + +static struct ssh_hmac_struct ssh_hmac_tab[] = { + { "hmac-sha1", SSH_HMAC_SHA1, false }, + { "hmac-sha2-256", SSH_HMAC_SHA256, false }, + { "hmac-sha2-512", SSH_HMAC_SHA512, false }, + { "hmac-md5", SSH_HMAC_MD5, false }, + { "aead-poly1305", SSH_HMAC_AEAD_POLY1305, false }, + { "aead-gcm", SSH_HMAC_AEAD_GCM, false }, + { "hmac-sha1-etm@openssh.com", SSH_HMAC_SHA1, true }, + { "hmac-sha2-256-etm@openssh.com", SSH_HMAC_SHA256, true }, + { "hmac-sha2-512-etm@openssh.com", SSH_HMAC_SHA512, true }, + { "hmac-md5-etm@openssh.com", SSH_HMAC_MD5, true }, + { NULL, 0, false } +}; + +struct ssh_hmac_struct *ssh_get_hmactab(void) { + return ssh_hmac_tab; +} + +size_t hmac_digest_len(enum ssh_hmac_e type) { + switch(type) { + case SSH_HMAC_SHA1: + return SHA_DIGEST_LEN; + case SSH_HMAC_SHA256: + return SHA256_DIGEST_LEN; + case SSH_HMAC_SHA512: + return SHA512_DIGEST_LEN; + case SSH_HMAC_MD5: + return MD5_DIGEST_LEN; + case SSH_HMAC_AEAD_POLY1305: + return POLY1305_TAGLEN; + case SSH_HMAC_AEAD_GCM: + return AES_GCM_TAGLEN; + default: + return 0; + } +} + +const char *ssh_hmac_type_to_string(enum ssh_hmac_e hmac_type, bool etm) +{ + int i = 0; + struct ssh_hmac_struct *ssh_hmactab = ssh_get_hmactab(); + while (ssh_hmactab[i].name && + ((ssh_hmactab[i].hmac_type != hmac_type) || + (ssh_hmactab[i].etm != etm))) { + i++; + } + return ssh_hmactab[i].name; +} + +/* it allocates a new cipher structure based on its offset into the global table */ +static struct ssh_cipher_struct *cipher_new(int offset) { + struct ssh_cipher_struct *cipher = NULL; + + cipher = malloc(sizeof(struct ssh_cipher_struct)); + if (cipher == NULL) { + return NULL; + } + + /* note the memcpy will copy the pointers : so, you shouldn't free them */ + memcpy(cipher, &ssh_get_ciphertab()[offset], sizeof(*cipher)); + + return cipher; +} + +void ssh_cipher_clear(struct ssh_cipher_struct *cipher){ +#ifdef HAVE_LIBGCRYPT + unsigned int i; +#endif + + if (cipher == NULL) { + return; + } + +#ifdef HAVE_LIBGCRYPT + if (cipher->key) { + for (i = 0; i < (cipher->keylen / sizeof(gcry_cipher_hd_t)); i++) { + gcry_cipher_close(cipher->key[i]); + } + SAFE_FREE(cipher->key); + } +#endif + + if (cipher->cleanup != NULL) { + cipher->cleanup(cipher); + } +} + +static void cipher_free(struct ssh_cipher_struct *cipher) { + ssh_cipher_clear(cipher); + SAFE_FREE(cipher); +} + +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; +} + +void crypto_free(struct ssh_crypto_struct *crypto) +{ + size_t i; + + if (crypto == NULL) { + return; + } + + ssh_key_free(crypto->server_pubkey); + + ssh_dh_cleanup(crypto); + bignum_safe_free(crypto->shared_secret); +#ifdef HAVE_ECDH + SAFE_FREE(crypto->ecdh_client_pubkey); + SAFE_FREE(crypto->ecdh_server_pubkey); + if(crypto->ecdh_privkey != NULL){ +#ifdef HAVE_OPENSSL_ECC + EC_KEY_free(crypto->ecdh_privkey); +#elif defined HAVE_GCRYPT_ECC + gcry_sexp_release(crypto->ecdh_privkey); +#endif + crypto->ecdh_privkey = NULL; + } +#endif + if (crypto->session_id != NULL) { + explicit_bzero(crypto->session_id, crypto->digest_len); + SAFE_FREE(crypto->session_id); + } + if (crypto->secret_hash != NULL) { + explicit_bzero(crypto->secret_hash, crypto->digest_len); + SAFE_FREE(crypto->secret_hash); + } +#ifdef WITH_ZLIB + if (crypto->compress_out_ctx && + (deflateEnd(crypto->compress_out_ctx) != 0)) { + inflateEnd(crypto->compress_out_ctx); + } + SAFE_FREE(crypto->compress_out_ctx); + + if (crypto->compress_in_ctx && + (deflateEnd(crypto->compress_in_ctx) != 0)) { + inflateEnd(crypto->compress_in_ctx); + } + SAFE_FREE(crypto->compress_in_ctx); +#endif /* WITH_ZLIB */ + SAFE_FREE(crypto->encryptIV); + SAFE_FREE(crypto->decryptIV); + SAFE_FREE(crypto->encryptMAC); + SAFE_FREE(crypto->decryptMAC); + if (crypto->encryptkey != NULL) { + explicit_bzero(crypto->encryptkey, crypto->out_cipher->keysize / 8); + SAFE_FREE(crypto->encryptkey); + } + if (crypto->decryptkey != NULL) { + explicit_bzero(crypto->decryptkey, crypto->in_cipher->keysize / 8); + SAFE_FREE(crypto->decryptkey); + } + + cipher_free(crypto->in_cipher); + cipher_free(crypto->out_cipher); + + for (i = 0; i < SSH_KEX_METHODS; i++) { + SAFE_FREE(crypto->client_kex.methods[i]); + SAFE_FREE(crypto->server_kex.methods[i]); + SAFE_FREE(crypto->kex_methods[i]); + } + + explicit_bzero(crypto, sizeof(struct ssh_crypto_struct)); + + SAFE_FREE(crypto); +} + +static int crypt_set_algorithms2(ssh_session session) +{ + const char *wanted = NULL; + struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab(); + struct ssh_hmac_struct *ssh_hmactab=ssh_get_hmactab(); + size_t i = 0; + int cmp; + + /* + * We must scan the kex entries to find crypto algorithms and set their + * appropriate structure. + */ + + /* out */ + wanted = session->next_crypto->kex_methods[SSH_CRYPT_C_S]; + for (i = 0; i < 64 && ssh_ciphertab[i].name != NULL; ++i) { + cmp = strcmp(wanted, ssh_ciphertab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_ciphertab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "crypt_set_algorithms2: no crypto algorithm function found for %s", + wanted); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set output algorithm to %s", wanted); + + session->next_crypto->out_cipher = cipher_new(i); + if (session->next_crypto->out_cipher == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + if (session->next_crypto->out_cipher->aead_encrypt != NULL) { + /* this cipher has integrated MAC */ + if (session->next_crypto->out_cipher->ciphertype == SSH_AEAD_CHACHA20_POLY1305) { + wanted = "aead-poly1305"; + } else { + wanted = "aead-gcm"; + } + } else { + /* + * We must scan the kex entries to find hmac algorithms and set their + * appropriate structure. + */ + + /* out */ + wanted = session->next_crypto->kex_methods[SSH_MAC_C_S]; + } + + for (i = 0; ssh_hmactab[i].name != NULL; i++) { + cmp = strcmp(wanted, ssh_hmactab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_hmactab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "crypt_set_algorithms2: no hmac algorithm function found for %s", + wanted); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set HMAC output algorithm to %s", wanted); + + session->next_crypto->out_hmac = ssh_hmactab[i].hmac_type; + session->next_crypto->out_hmac_etm = ssh_hmactab[i].etm; + + /* in */ + wanted = session->next_crypto->kex_methods[SSH_CRYPT_S_C]; + + for (i = 0; ssh_ciphertab[i].name != NULL; i++) { + cmp = strcmp(wanted, ssh_ciphertab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_ciphertab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "Crypt_set_algorithms: no crypto algorithm function found for %s", + wanted); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set input algorithm to %s", wanted); + + session->next_crypto->in_cipher = cipher_new(i); + if (session->next_crypto->in_cipher == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + if (session->next_crypto->in_cipher->aead_encrypt != NULL){ + /* this cipher has integrated MAC */ + if (session->next_crypto->in_cipher->ciphertype == SSH_AEAD_CHACHA20_POLY1305) { + wanted = "aead-poly1305"; + } else { + wanted = "aead-gcm"; + } + } else { + /* we must scan the kex entries to find hmac algorithms and set their appropriate structure */ + wanted = session->next_crypto->kex_methods[SSH_MAC_S_C]; + } + + for (i = 0; ssh_hmactab[i].name != NULL; i++) { + cmp = strcmp(wanted, ssh_hmactab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_hmactab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "crypt_set_algorithms2: no hmac algorithm function found for %s", + wanted); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set HMAC input algorithm to %s", wanted); + + 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"); + if (cmp == 0) { + session->next_crypto->do_compress_out = 1; + } + cmp = strcmp(session->next_crypto->kex_methods[SSH_COMP_S_C], "zlib"); + if (cmp == 0) { + session->next_crypto->do_compress_in = 1; + } + cmp = strcmp(session->next_crypto->kex_methods[SSH_COMP_C_S], "zlib@openssh.com"); + if (cmp == 0) { + session->next_crypto->delayed_compress_out = 1; + } + cmp = strcmp(session->next_crypto->kex_methods[SSH_COMP_S_C], "zlib@openssh.com"); + if (cmp == 0) { + session->next_crypto->delayed_compress_in = 1; + } + + return SSH_OK; +} + +int crypt_set_algorithms_client(ssh_session session) +{ + return crypt_set_algorithms2(session); +} + +#ifdef WITH_SERVER +int crypt_set_algorithms_server(ssh_session session){ + const char *method = NULL; + size_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; + } + + /* + * We must scan the kex entries to find crypto algorithms and set their + * appropriate structure + */ + /* out */ + method = session->next_crypto->kex_methods[SSH_CRYPT_S_C]; + + for (i = 0; ssh_ciphertab[i].name != NULL; i++) { + cmp = strcmp(method, ssh_ciphertab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_ciphertab[i].name == NULL) { + ssh_set_error(session,SSH_FATAL,"crypt_set_algorithms_server : " + "no crypto algorithm function found for %s",method); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET,"Set output algorithm %s",method); + + session->next_crypto->out_cipher = cipher_new(i); + if (session->next_crypto->out_cipher == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + if (session->next_crypto->out_cipher->aead_encrypt != NULL){ + /* this cipher has integrated MAC */ + if (session->next_crypto->out_cipher->ciphertype == SSH_AEAD_CHACHA20_POLY1305) { + method = "aead-poly1305"; + } else { + method = "aead-gcm"; + } + } else { + /* we must scan the kex entries to find hmac algorithms and set their appropriate structure */ + /* out */ + method = session->next_crypto->kex_methods[SSH_MAC_S_C]; + } + /* HMAC algorithm selection */ + + for (i = 0; ssh_hmactab[i].name != NULL; i++) { + cmp = strcmp(method, ssh_hmactab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_hmactab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "crypt_set_algorithms_server: no hmac algorithm function found for %s", + method); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set HMAC output algorithm to %s", method); + + session->next_crypto->out_hmac = ssh_hmactab[i].hmac_type; + session->next_crypto->out_hmac_etm = ssh_hmactab[i].etm; + + /* in */ + method = session->next_crypto->kex_methods[SSH_CRYPT_C_S]; + + for (i = 0; ssh_ciphertab[i].name; i++) { + cmp = strcmp(method, ssh_ciphertab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_ciphertab[i].name == NULL) { + ssh_set_error(session,SSH_FATAL,"Crypt_set_algorithms_server :" + "no crypto algorithm function found for %s",method); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET,"Set input algorithm %s",method); + + session->next_crypto->in_cipher = cipher_new(i); + if (session->next_crypto->in_cipher == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + if (session->next_crypto->in_cipher->aead_encrypt != NULL){ + /* this cipher has integrated MAC */ + if (session->next_crypto->in_cipher->ciphertype == SSH_AEAD_CHACHA20_POLY1305) { + method = "aead-poly1305"; + } else { + method = "aead-gcm"; + } + } else { + /* we must scan the kex entries to find hmac algorithms and set their appropriate structure */ + method = session->next_crypto->kex_methods[SSH_MAC_C_S]; + } + + for (i = 0; ssh_hmactab[i].name != NULL; i++) { + cmp = strcmp(method, ssh_hmactab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_hmactab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "crypt_set_algorithms_server: no hmac algorithm function found for %s", + method); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set HMAC input algorithm to %s", method); + + session->next_crypto->in_hmac = ssh_hmactab[i].hmac_type; + session->next_crypto->in_hmac_etm = ssh_hmactab[i].etm; + + /* 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; + } + 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; + } + } + + method = session->next_crypto->kex_methods[SSH_COMP_S_C]; + if(strcmp(method,"zlib") == 0){ + SSH_LOG(SSH_LOG_PACKET, "enabling S->C compression"); + session->next_crypto->do_compress_out=1; + } + 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; + } + } + + method = session->next_crypto->kex_methods[SSH_HOSTKEYS]; + session->srv.hostkey = ssh_key_type_from_signature_name(method); + session->srv.hostkey_digest = ssh_key_hash_from_name(method); + + /* setup DH key exchange type */ + 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_server_dh_init(session); + break; +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + ssh_server_dhgex_init(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_server_ecdh_init(session); + break; +#endif +#ifdef HAVE_CURVE25519 + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + ssh_server_curve25519_init(session); + break; +#endif + default: + ssh_set_error(session, + SSH_FATAL, + "crypt_set_algorithms_server: could not find init " + "handler for kex type %d", + session->next_crypto->kex_type); + return SSH_ERROR; + } + return SSH_OK; +} + +#endif /* WITH_SERVER */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..294038e --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,155 @@ +project(libssh-tests C) + +if (BSD OR SOLARIS OR OSX) + find_package(Argp) +endif (BSD OR SOLARIS OR OSX) + +set(TORTURE_LIBRARY torture) + +include_directories(${OPENSSL_INCLUDE_DIR} + ${CMOCKA_INCLUDE_DIR} + ${ZLIB_INCLUDE_DIR} + ${libssh_BINARY_DIR} + ${libssh_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}) + +set(TORTURE_LINK_LIBRARIES + ${CMOCKA_LIBRARY} + ssh::static) + +# create test library +add_library(${TORTURE_LIBRARY} + STATIC + cmdline.c + torture.c + torture_key.c + torture_pki.c + torture_cmocka.c) +target_link_libraries(${TORTURE_LIBRARY} ${TORTURE_LINK_LIBRARIES}) +target_compile_options(${TORTURE_LIBRARY} PRIVATE + -DSSH_PING_EXECUTABLE="${CMAKE_CURRENT_BINARY_DIR}/ssh_ping" +) + +if (ARGP_LIBRARY) + target_link_libraries(${TORTURE_LIBRARY} + ${ARGP_LIBRARY} + ) +endif() + +set(TEST_TARGET_LIBRARIES + ${TORTURE_LIBRARY} + ${TORTURE_LINK_LIBRARIES} +) + +add_subdirectory(unittests) + +if (CLIENT_TESTING OR SERVER_TESTING) + find_package(socket_wrapper 1.1.5 REQUIRED) + find_package(nss_wrapper 1.1.2 REQUIRED) + find_package(uid_wrapper 1.2.0 REQUIRED) + find_package(pam_wrapper 1.0.1 REQUIRED) + + find_program(SSHD_EXECUTABLE + NAME + sshd + PATHS + /sbin + /usr/sbin + /usr/local/sbin) + if (NOT SSHD_EXECUTABLE) + message(SEND_ERROR "Could not find sshd which is required for client testing") + endif() + + find_program(SSH_EXECUTABLE NAMES ssh) + if (SSH_EXECUTABLE) + execute_process(COMMAND ${SSH_EXECUTABLE} -V ERROR_VARIABLE OPENSSH_VERSION_STR) + string(REGEX REPLACE "^.*OpenSSH_([0-9]).[0-9].*$" "\\1" OPENSSH_VERSION_MAJOR "${OPENSSH_VERSION_STR}") + string(REGEX REPLACE "^.*OpenSSH_[0-9].([0-9]).*$" "\\1" OPENSSH_VERSION_MINOR "${OPENSSH_VERSION_STR}") + add_definitions(-DOPENSSH_VERSION_MAJOR=${OPENSSH_VERSION_MAJOR} -DOPENSSH_VERSION_MINOR=${OPENSSH_VERSION_MINOR}) + endif() + + set(LOCAL_USER "nobody") + set(LOCAL_UID "65533") + find_program(ID_EXECUTABLE NAMES id) + find_program(WHO_EXECUTABLE NAMES whoami) + if (ID_EXECUTABLE AND WHO_EXECUTABLE) + execute_process(COMMAND ${WHO_EXECUTABLE} OUTPUT_VARIABLE LOCAL_USER OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${ID_EXECUTABLE} -u OUTPUT_VARIABLE LOCAL_UID OUTPUT_STRIP_TRAILING_WHITESPACE) + endif() + + # chroot_wrapper + add_library(chroot_wrapper SHARED chroot_wrapper.c) + set(CHROOT_WRAPPER_LIBRARY ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}chroot_wrapper${CMAKE_SHARED_LIBRARY_SUFFIX}) + set(TEST_TARGET_LIBRARIES + ${TEST_TARGET_LIBRARIES} + chroot_wrapper + ) + + # ssh_ping + add_executable(ssh_ping ssh_ping.c) + target_compile_options(ssh_ping PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(ssh_ping ssh::ssh) + + # homedir will be used in passwd + set(HOMEDIR ${CMAKE_CURRENT_BINARY_DIR}/home) + + ### Setup nss_wrapper + configure_file(etc/passwd.in ${CMAKE_CURRENT_BINARY_DIR}/etc/passwd @ONLY) + configure_file(etc/shadow.in ${CMAKE_CURRENT_BINARY_DIR}/etc/shadow @ONLY) + configure_file(etc/group.in ${CMAKE_CURRENT_BINARY_DIR}/etc/group @ONLY) + configure_file(etc/hosts.in ${CMAKE_CURRENT_BINARY_DIR}/etc/hosts @ONLY) + + ### Setup pam_wrapper + configure_file(etc/pam_matrix_passdb.in ${CMAKE_CURRENT_BINARY_DIR}/etc/pam_matrix_passdb @ONLY) + configure_file(etc/pam.d/sshd.in ${CMAKE_CURRENT_BINARY_DIR}/etc/pam.d/sshd @ONLY) + + + set(TORTURE_ENVIRONMENT "LD_PRELOAD=${SOCKET_WRAPPER_LIBRARY}:${NSS_WRAPPER_LIBRARY}:${UID_WRAPPER_LIBRARY}:${PAM_WRAPPER_LIBRARY}:${CHROOT_WRAPPER_LIBRARY}") + list(APPEND TORTURE_ENVIRONMENT UID_WRAPPER=1 UID_WRAPPER_ROOT=1) + list(APPEND TORTURE_ENVIRONMENT NSS_WRAPPER_PASSWD=${CMAKE_CURRENT_BINARY_DIR}/etc/passwd) + list(APPEND TORTURE_ENVIRONMENT NSS_WRAPPER_SHADOW=${CMAKE_CURRENT_BINARY_DIR}/etc/shadow) + list(APPEND TORTURE_ENVIRONMENT NSS_WRAPPER_GROUP=${CMAKE_CURRENT_BINARY_DIR}/etc/group) + list(APPEND TORTURE_ENVIRONMENT PAM_WRAPPER_SERVICE_DIR=${CMAKE_CURRENT_BINARY_DIR}/etc/pam.d) + + # Give bob some keys + file(COPY keys/id_rsa DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_rsa.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ecdsa DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ecdsa.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ed25519 DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ed25519.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + + # Allow to auth with bob's public keys on alice account + configure_file(keys/id_rsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys @ONLY) + # append ECDSA public key + file(READ keys/id_ecdsa.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys "${CONTENTS}") + + # append ed25519 public key + file(READ keys/id_ed25519.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys "${CONTENTS}") + + # Copy the signed key to an alternative directory in bob's homedir. + file(COPY keys/certauth/id_rsa DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh_cert/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/certauth/id_rsa.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh_cert/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/certauth/id_rsa-cert.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh_cert/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + + message(STATUS "TORTURE_ENVIRONMENT=${TORTURE_ENVIRONMENT}") +endif () + +if (WITH_BENCHMARKS) + add_subdirectory(benchmarks) +endif () + +if (CLIENT_TESTING) + add_subdirectory(client) +endif () + +if (WITH_SERVER AND SERVER_TESTING) + add_subdirectory(pkd) + add_subdirectory(server) +endif () + +if (FUZZ_TESTING) + add_subdirectory(fuzz) +endif() diff --git a/tests/authentication.c b/tests/authentication.c new file mode 100644 index 0000000..248b646 --- /dev/null +++ b/tests/authentication.c @@ -0,0 +1,74 @@ +/* +This file is distributed in public domain. You can do whatever you want +with its content. +*/ + + +#include +#include +#include +#include + +#include + +#include "tests.h" +static int auth_kbdint(SSH_SESSION *session){ + int err=ssh_userauth_kbdint(session,NULL,NULL); + char *name,*instruction,*prompt,*ptr; + char buffer[128]; + int i,n; + char echo; + while (err==SSH_AUTH_INFO){ + name=ssh_userauth_kbdint_getname(session); + instruction=ssh_userauth_kbdint_getinstruction(session); + n=ssh_userauth_kbdint_getnprompts(session); + if(strlen(name)>0) + printf("%s\n",name); + if(strlen(instruction)>0) + printf("%s\n",instruction); + for(i=0;i&1`" +echo "Ping latency to $DEST": +ping -q -c 1 -n $DEST +echo "Destination $DEST SSHD vesion : `echo | nc $DEST 22 | head -n1`" +echo "ssh login latency :`(time -f user:%U ssh $DEST 'id > /dev/null') 2>&1`" +./generate.py | dd bs=4096 count=100000 | time ssh -c $CIPHER $DEST "dd bs=4096 of=/dev/null" 2>&1 + diff --git a/tests/benchmarks/bench2.sh b/tests/benchmarks/bench2.sh new file mode 100755 index 0000000..01d6777 --- /dev/null +++ b/tests/benchmarks/bench2.sh @@ -0,0 +1,13 @@ +export CIPHER=aes128-cbc +export DEST=localhost + +echo "Upload raw SSH statistics" +echo "local machine: `uname -a`" +echo "Cipher : $CIPHER ; Destination : $DEST (`ssh $DEST uname -a`)" +echo "Local ssh version: `samplessh -V 2>&1`" +echo "Ping latency to $DEST": +ping -q -c 1 -n $DEST +echo "Destination $DEST SSHD vesion : `echo | nc $DEST 22 | head -n1`" +echo "ssh login latency :`(time -f user:%U samplessh $DEST 'id > /dev/null') 2>&1`" +./generate.py | dd bs=4096 count=100000 | strace samplessh -c $CIPHER $DEST "dd bs=4096 of=/dev/null" 2>&1 + diff --git a/tests/benchmarks/bench_raw.c b/tests/benchmarks/bench_raw.c new file mode 100644 index 0000000..db1a057 --- /dev/null +++ b/tests/benchmarks/bench_raw.c @@ -0,0 +1,285 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 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 "benchmarks.h" +#include +#include +#include + +#define PYTHON_PATH "/usr/bin/python" + +const char python_eater[]= +"#!/usr/bin/python\n" +"import sys\n" +"print 'go'\n" +"sys.stdout.flush()\n" +"toread=XXXXXXXXXX\n" +"read=0\n" +"while(read < toread):\n" +" buffersize=toread-read\n" +" if(buffersize > 4096):\n" +" buffersize=4096\n" +" r=len(sys.stdin.read(buffersize))\n" +" read+=r\n" +" if(r<=0):\n" +" print 'error'\n" +" exit()\n" +"print 'done'\n"; + +static char *get_python_eater(unsigned long bytes){ + char *eater=malloc(sizeof(python_eater)); + char *ptr; + char buf[12]; + + memcpy(eater,python_eater,sizeof(python_eater)); + ptr=strstr(eater,"XXXXXXXXXX"); + if(!ptr){ + free(eater); + return NULL; + } + sprintf(buf,"0x%.8lx",bytes); + memcpy(ptr,buf,10); + return eater; +} + +/** @internal + * @brief uploads a script (python or other) at a specific path on the + * remote host + * @param[in] session an active SSH session + * @param[in] path to copy the file + * @param[in] content of the file to copy + * @return 0 on success, -1 on error + */ +static int upload_script(ssh_session session, const char *path, + const char *script){ + ssh_channel channel; + char cmd[128]; + int err; + + channel=ssh_channel_new(session); + if(!channel) + goto error; + if(ssh_channel_open_session(channel) == SSH_ERROR) + goto error; + snprintf(cmd,sizeof(cmd),"cat > %s",path); + if(ssh_channel_request_exec(channel,cmd) == SSH_ERROR) + goto error; + err=ssh_channel_write(channel,script,strlen(script)); + if(err == SSH_ERROR) + goto error; + if(ssh_channel_send_eof(channel) == SSH_ERROR) + goto error; + if(ssh_channel_close(channel) == SSH_ERROR) + goto error; + ssh_channel_free(channel); + return 0; +error: + fprintf(stderr,"Error while copying script : %s\n",ssh_get_error(session)); + return -1; +} + +/** @internal + * @brief benchmarks a raw upload (simple upload in a SSH channel) using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_raw_up (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + char *script; + char cmd[128]; + int err; + ssh_channel channel; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + + bytes = args->datasize * 1024 * 1024; + script =get_python_eater(bytes); + err=upload_script(session,"/tmp/eater.py",script); + free(script); + if(err<0) + return err; + channel=ssh_channel_new(session); + if(channel == NULL) + goto error; + if(ssh_channel_open_session(channel)==SSH_ERROR) + goto error; + snprintf(cmd,sizeof(cmd),"%s /tmp/eater.py", PYTHON_PATH); + if(ssh_channel_request_exec(channel,cmd)==SSH_ERROR) + goto error; + if((err=ssh_channel_read(channel,buffer,sizeof(buffer)-1,0))==SSH_ERROR) + goto error; + buffer[err]=0; + if(!strstr(buffer,"go")){ + fprintf(stderr,"parse error : %s\n",buffer); + ssh_channel_close(channel); + ssh_channel_free(channel); + return -1; + } + if(args->verbose>0) + fprintf(stdout,"Starting upload of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long towrite = bytes - total; + int w; + if(towrite > args->chunksize) + towrite = args->chunksize; + w=ssh_channel_write(channel,buffer,towrite); + if(w == SSH_ERROR) + goto error; + total += w; + } + + if(args->verbose>0) + fprintf(stdout,"Finished upload, now waiting the ack\n"); + + if((err=ssh_channel_read(channel,buffer,5,0))==SSH_ERROR) + goto error; + buffer[err]=0; + if(!strstr(buffer,"done")){ + fprintf(stderr,"parse error : %s\n",buffer); + ssh_channel_close(channel); + ssh_channel_free(channel); + return -1; + } + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"Upload took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + ssh_channel_close(channel); + ssh_channel_free(channel); + return 0; +error: + fprintf(stderr,"Error during raw upload : %s\n",ssh_get_error(session)); + if(channel){ + ssh_channel_close(channel); + ssh_channel_free(channel); + } + return -1; +} + +const char python_giver[] = +"#!/usr/bin/python\n" +"import sys\n" +"r=sys.stdin.read(2)\n" +"towrite=XXXXXXXXXX\n" +"wrote=0\n" +"mtu = 32786\n" +"buf = 'A'*mtu\n" +"while(wrote < towrite):\n" +" buffersize=towrite-wrote\n" +" if(buffersize > mtu):\n" +" buffersize=mtu\n" +" if(buffersize == mtu):\n" +" sys.stdout.write(buf)\n" +" else:\n" +" sys.stdout.write('A'*buffersize)\n" +" wrote+=buffersize\n" +"sys.stdout.flush()\n"; + +static char *get_python_giver(unsigned long bytes){ + char *giver=malloc(sizeof(python_giver)); + char *ptr; + char buf[12]; + + memcpy(giver,python_giver,sizeof(python_giver)); + ptr=strstr(giver,"XXXXXXXXXX"); + if(!ptr){ + free(giver); + return NULL; + } + sprintf(buf,"0x%.8lx",bytes); + memcpy(ptr,buf,10); + return giver; +} + +/** @internal + * @brief benchmarks a raw download (simple upload in a SSH channel) using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_raw_down (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + char *script; + char cmd[128]; + int err; + ssh_channel channel; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + + bytes = args->datasize * 1024 * 1024; + script =get_python_giver(bytes); + err=upload_script(session,"/tmp/giver.py",script); + free(script); + if(err<0) + return err; + channel=ssh_channel_new(session); + if(channel == NULL) + goto error; + if(ssh_channel_open_session(channel)==SSH_ERROR) + goto error; + snprintf(cmd,sizeof(cmd),"%s /tmp/giver.py", PYTHON_PATH); + if(ssh_channel_request_exec(channel,cmd)==SSH_ERROR) + goto error; + if((err=ssh_channel_write(channel,"go",2))==SSH_ERROR) + goto error; + if(args->verbose>0) + fprintf(stdout,"Starting download of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long toread = bytes - total; + int r; + if(toread > args->chunksize) + toread = args->chunksize; + r=ssh_channel_read(channel,buffer,toread,0); + if(r == SSH_ERROR) + goto error; + total += r; + } + + if(args->verbose>0) + fprintf(stdout,"Finished download\n"); + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"Download took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + ssh_channel_close(channel); + ssh_channel_free(channel); + return 0; +error: + fprintf(stderr,"Error during raw upload : %s\n",ssh_get_error(session)); + if(channel){ + ssh_channel_close(channel); + ssh_channel_free(channel); + } + return -1; +} diff --git a/tests/benchmarks/bench_scp.c b/tests/benchmarks/bench_scp.c new file mode 100644 index 0000000..71aba28 --- /dev/null +++ b/tests/benchmarks/bench_scp.c @@ -0,0 +1,150 @@ +/* bench_scp.c + * + * This file is part of the SSH Library + * + * Copyright (c) 2011 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 "benchmarks.h" +#include +#include + +#define SCPDIR "/tmp/" +#define SCPFILE "scpbenchmark" + +/** @internal + * @brief benchmarks a scp upload using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_scp_up (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + ssh_scp scp; + + bytes = args->datasize * 1024 * 1024; + scp = ssh_scp_new(session,SSH_SCP_WRITE,SCPDIR); + if(scp == NULL) + goto error; + if(ssh_scp_init(scp)==SSH_ERROR) + goto error; + if(ssh_scp_push_file(scp,SCPFILE,bytes,0777) != SSH_OK) + goto error; + if(args->verbose>0) + fprintf(stdout,"Starting upload of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long towrite = bytes - total; + int w; + if(towrite > args->chunksize) + towrite = args->chunksize; + w=ssh_scp_write(scp,buffer,towrite); + if(w == SSH_ERROR) + goto error; + total += towrite; + } + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"Upload took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + ssh_scp_close(scp); + ssh_scp_free(scp); + return 0; +error: + fprintf(stderr,"Error during scp upload : %s\n",ssh_get_error(session)); + if(scp){ + ssh_scp_close(scp); + ssh_scp_free(scp); + } + return -1; +} + +/** @internal + * @brief benchmarks a scp download using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_scp_down (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + ssh_scp scp; + int r; + size_t size; + + bytes = args->datasize * 1024 * 1024; + scp = ssh_scp_new(session,SSH_SCP_READ,SCPDIR SCPFILE); + if(scp == NULL) + goto error; + if(ssh_scp_init(scp)==SSH_ERROR) + goto error; + r=ssh_scp_pull_request(scp); + if(r == SSH_SCP_REQUEST_NEWFILE){ + size=ssh_scp_request_get_size(scp); + if(bytes > size){ + printf("Only %zd bytes available (on %lu requested).\n",size,bytes); + bytes = size; + } + if(size > bytes){ + printf("File is %zd bytes (on %lu requested). Will cut the end\n",size,bytes); + } + if(args->verbose>0) + fprintf(stdout,"Starting download of %lu bytes now\n",bytes); + timestamp_init(&ts); + ssh_scp_accept_request(scp); + while(total < bytes){ + unsigned long toread = bytes - total; + if(toread > args->chunksize) + toread = args->chunksize; + r=ssh_scp_read(scp,buffer,toread); + if(r == SSH_ERROR || r == 0) + goto error; + total += r; + } + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"download took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + } else { + fprintf(stderr,"Expected SSH_SCP_REQUEST_NEWFILE, got %d\n",r); + goto error; + } + ssh_scp_close(scp); + ssh_scp_free(scp); + return 0; +error: + fprintf(stderr,"Error during scp download : %s\n",ssh_get_error(session)); + if(scp){ + ssh_scp_close(scp); + ssh_scp_free(scp); + } + return -1; +} diff --git a/tests/benchmarks/bench_sftp.c b/tests/benchmarks/bench_sftp.c new file mode 100644 index 0000000..601ecec --- /dev/null +++ b/tests/benchmarks/bench_sftp.c @@ -0,0 +1,239 @@ +/* bench_sftp.c + * + * This file is part of the SSH Library + * + * Copyright (c) 2011 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 "benchmarks.h" +#include +#include +#include +#include +#include + +#define SFTPDIR "/tmp/" +#define SFTPFILE "scpbenchmark" + +/** @internal + * @brief benchmarks a synchronous sftp upload using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_sync_sftp_up (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + sftp_session sftp; + sftp_file file = NULL; + + bytes = args->datasize * 1024 * 1024; + sftp = sftp_new(session); + if(sftp == NULL) + goto error; + if(sftp_init(sftp)==SSH_ERROR) + goto error; + file = sftp_open(sftp,SFTPDIR SFTPFILE,O_RDWR | O_CREAT | O_TRUNC, 0777); + if(!file) + goto error; + if(args->verbose>0) + fprintf(stdout,"Starting upload of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long towrite = bytes - total; + int w; + if(towrite > args->chunksize) + towrite = args->chunksize; + w=sftp_write(file,buffer,towrite); + if(w == SSH_ERROR) + goto error; + total += w; + } + sftp_close(file); + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"Upload took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + sftp_free(sftp); + return 0; +error: + fprintf(stderr,"Error during scp upload : %s\n",ssh_get_error(session)); + if(file) + sftp_close(file); + if(sftp) + sftp_free(sftp); + return -1; +} + +/** @internal + * @brief benchmarks a synchronous sftp download using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_sync_sftp_down (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + sftp_session sftp; + sftp_file file = NULL; + int r; + + bytes = args->datasize * 1024 * 1024; + sftp = sftp_new(session); + if(sftp == NULL) + goto error; + if(sftp_init(sftp)==SSH_ERROR) + goto error; + file = sftp_open(sftp,SFTPDIR SFTPFILE,O_RDONLY,0); + if(!file) + goto error; + if(args->verbose>0) + fprintf(stdout,"Starting download of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long toread = bytes - total; + if(toread > args->chunksize) + toread = args->chunksize; + r=sftp_read(file,buffer,toread); + if(r == SSH_ERROR) + goto error; + total += r; + /* we had a smaller file */ + if(r==0){ + fprintf(stdout,"File smaller than expected : %lu (expected %lu).\n",total,bytes); + bytes = total; + break; + } + } + sftp_close(file); + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"download took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + sftp_free(sftp); + return 0; +error: + fprintf(stderr,"Error during sftp download : %s\n",ssh_get_error(session)); + if(file) + sftp_close(file); + if(sftp) + sftp_free(sftp); + return -1; +} + +/** @internal + * @brief benchmarks an asynchronous sftp download using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_async_sftp_down (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + sftp_session sftp; + sftp_file file = NULL; + int r,i; + int warned = 0; + unsigned long toread; + int *ids=NULL; + int concurrent_downloads = args->concurrent_requests; + + bytes = args->datasize * 1024 * 1024; + sftp = sftp_new(session); + if(sftp == NULL) + goto error; + if(sftp_init(sftp)==SSH_ERROR) + goto error; + file = sftp_open(sftp,SFTPDIR SFTPFILE,O_RDONLY,0); + if(!file) + goto error; + ids = malloc(concurrent_downloads * sizeof(int)); + if(args->verbose>0) + fprintf(stdout,"Starting download of %lu bytes now, using %d concurrent downloads\n",bytes, + concurrent_downloads); + timestamp_init(&ts); + for (i=0;ichunksize); + if(ids[i]==SSH_ERROR) + goto error; + } + i=0; + while(total < bytes){ + r = sftp_async_read(file, buffer, args->chunksize, ids[i]); + if(r == SSH_ERROR) + goto error; + total += r; + if(r != (int)args->chunksize && total != bytes && !warned){ + fprintf(stderr,"async_sftp_download : receiving short reads (%d, requested %d) " + "the received file will be corrupted and shorted. Adapt chunksize to %d\n", + r, args->chunksize,r); + warned = 1; + } + /* we had a smaller file */ + if(r==0){ + fprintf(stdout,"File smaller than expected : %lu (expected %lu).\n",total,bytes); + bytes = total; + break; + } + toread = bytes - total; + if(toread < args->chunksize * concurrent_downloads){ + /* we've got enough launched downloads */ + ids[i]=-1; + } + if(toread > args->chunksize) + toread = args->chunksize; + ids[i]=sftp_async_read_begin(file,toread); + if(ids[i] == SSH_ERROR) + goto error; + i = (i+1) % concurrent_downloads; + } + sftp_close(file); + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"download took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + sftp_free(sftp); + free(ids); + return 0; +error: + fprintf(stderr,"Error during sftp download : %s\n",ssh_get_error(session)); + if(file) + sftp_close(file); + if(sftp) + sftp_free(sftp); + free(ids); + return -1; +} diff --git a/tests/benchmarks/benchmarks.c b/tests/benchmarks/benchmarks.c new file mode 100644 index 0000000..5e33dd4 --- /dev/null +++ b/tests/benchmarks/benchmarks.c @@ -0,0 +1,400 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 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 "benchmarks.h" +#include + +#include +#include +#include + +struct benchmark benchmarks[]= { + { + .name="benchmark_raw_upload", + .fct=benchmarks_raw_up, + .enabled=0 + }, + { + .name="benchmark_raw_download", + .fct=benchmarks_raw_down, + .enabled=0 + }, + { + .name="benchmark_scp_upload", + .fct=benchmarks_scp_up, + .enabled=0 + }, + { + .name="benchmark_scp_download", + .fct=benchmarks_scp_down, + .enabled=0 + }, + { + .name="benchmark_sync_sftp_upload", + .fct=benchmarks_sync_sftp_up, + .enabled=0 + }, + { + .name="benchmark_sync_sftp_download", + .fct=benchmarks_sync_sftp_down, + .enabled=0 + }, + { + .name="benchmark_async_sftp_download", + .fct=benchmarks_async_sftp_down, + .enabled=0 + } +}; + +#ifdef HAVE_ARGP_H +#include + +const char *argp_program_version = "libssh benchmarks 2011-08-28"; +const char *argp_program_bug_address = "Aris Adamantiadis "; + +static char **cmdline; + +/* Program documentation. */ +static char doc[] = "libssh benchmarks"; + + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Make libssh benchmark more verbose", + .group = 0 + }, + { + .name = "raw-upload", + .key = '1', + .arg = NULL, + .flags = 0, + .doc = "Upload raw data using channel", + .group = 0 + }, + { + .name = "raw-download", + .key = '2', + .arg = NULL, + .flags = 0, + .doc = "Download raw data using channel", + .group = 0 + }, + { + .name = "scp-upload", + .key = '3', + .arg = NULL, + .flags = 0, + .doc = "Upload data using SCP", + .group = 0 + }, + { + .name = "scp-download", + .key = '4', + .arg = NULL, + .flags = 0, + .doc = "Download data using SCP", + .group = 0 + }, + { + .name = "sync-sftp-upload", + .key = '5', + .arg = NULL, + .flags = 0, + .doc = "Upload data using synchronous SFTP", + .group = 0 + + }, + { + .name = "sync-sftp-download", + .key = '6', + .arg = NULL, + .flags = 0, + .doc = "Download data using synchronous SFTP (slow)", + .group = 0 + + }, + { + .name = "async-sftp-download", + .key = '7', + .arg = NULL, + .flags = 0, + .doc = "Download data using asynchronous SFTP (fast)", + .group = 0 + + }, + { + .name = "host", + .key = 'h', + .arg = "HOST", + .flags = 0, + .doc = "Add a host to connect for benchmark (format user@hostname)", + .group = 0 + }, + { + .name = "size", + .key = 's', + .arg = "MBYTES", + .flags = 0, + .doc = "MBytes of data to send/receive per test", + .group = 0 + }, + { + .name = "chunk", + .key = 'c', + .arg = "bytes", + .flags = 0, + .doc = "size of data chunks to send/receive", + .group = 0 + }, + { + .name = "prequests", + .key = 'p', + .arg = "number [20]", + .flags = 0, + .doc = "[async SFTP] number of concurrent requests", + .group = 0 + }, + { + .name = "cipher", + .key = 'C', + .arg = "cipher", + .flags = 0, + .doc = "Cryptographic cipher to be used", + .group = 0 + }, + + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + struct argument_s *arguments = state->input; + + /* arg is currently not used */ + (void) arg; + + switch (key) { + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + benchmarks[key - '1'].enabled = 1; + arguments->ntests ++; + break; + case 'v': + arguments->verbose++; + break; + case 's': + arguments->datasize = atoi(arg); + break; + case 'p': + arguments->concurrent_requests = atoi(arg); + break; + case 'c': + arguments->chunksize = atoi(arg); + break; + case 'C': + arguments->cipher = arg; + break; + case 'h': + if(arguments->nhosts >= MAX_HOSTS_CONNECT){ + fprintf(stderr, "Too much hosts\n"); + return ARGP_ERR_UNKNOWN; + } + arguments->hosts[arguments->nhosts]=arg; + arguments->nhosts++; + break; + case ARGP_KEY_ARG: + /* End processing here. */ + cmdline = &state->argv [state->next - 1]; + state->next = state->argc; + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, NULL, doc, NULL, NULL, NULL}; + +#endif /* HAVE_ARGP_H */ + +static void cmdline_parse(int argc, char **argv, struct argument_s *arguments) { + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ +#ifdef HAVE_ARGP_H + argp_parse(&argp, argc, argv, 0, 0, arguments); +#else /* HAVE_ARGP_H */ + (void) argc; + (void) argv; + arguments->hosts[0]="localhost"; + arguments->nhosts=1; +#endif /* HAVE_ARGP_H */ +} + +static void arguments_init(struct argument_s *arguments){ + memset(arguments,0,sizeof(*arguments)); + arguments->chunksize=32758; + arguments->concurrent_requests=20; + arguments->datasize = 10; +} + +static ssh_session connect_host(const char *host, int verbose, char *cipher){ + ssh_session session=ssh_new(); + if(session==NULL) + goto error; + if(ssh_options_set(session,SSH_OPTIONS_HOST, host)<0) + goto error; + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose); + if(cipher != NULL){ + if (ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher) || + ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher)){ + goto error; + } + } + ssh_options_parse_config(session, NULL); + if(ssh_connect(session)==SSH_ERROR) + goto error; + if(ssh_userauth_autopubkey(session,NULL) != SSH_AUTH_SUCCESS) + goto error; + return session; +error: + fprintf(stderr,"Error connecting to \"%s\": %s\n",host,ssh_get_error(session)); + ssh_free(session); + return NULL; +} + +static char *network_speed(float bps){ + static char buf[128]; + if(bps > 1000*1000*1000){ + /* Gbps */ + snprintf(buf,sizeof(buf),"%f Gbps",bps/(1000*1000*1000)); + } else if(bps > 1000*1000){ + /* Mbps */ + snprintf(buf,sizeof(buf),"%f Mbps",bps/(1000*1000)); + } else if(bps > 1000){ + snprintf(buf,sizeof(buf),"%f Kbps",bps/1000); + } else { + snprintf(buf,sizeof(buf),"%f bps",bps); + } + return buf; +} + +static void do_benchmarks(ssh_session session, struct argument_s *arguments, + const char *hostname){ + float ping_rtt=0.0; + float ssh_rtt=0.0; + float bps=0.0; + int i; + int err; + struct benchmark *b; + + if(arguments->verbose>0) + fprintf(stdout,"Testing ICMP RTT\n"); + err=benchmarks_ping_latency(hostname, &ping_rtt); + if(err == 0){ + fprintf(stdout,"ping RTT : %f ms\n",ping_rtt); + } + err=benchmarks_ssh_latency(session, &ssh_rtt); + if(err==0){ + fprintf(stdout, "SSH RTT : %f ms. Theoretical max BW (win=128K) : %s\n",ssh_rtt,network_speed(128000.0/(ssh_rtt / 1000.0))); + } + for (i=0 ; ienabled){ + err=b->fct(session,arguments,&bps); + if(err==0){ + fprintf(stdout, "%s : %s : %s\n",hostname, b->name, network_speed(bps)); + } + } + } +} + +char *buffer; + +int main(int argc, char **argv){ + struct argument_s arguments; + ssh_session session; + int i; + + arguments_init(&arguments); + cmdline_parse(argc, argv, &arguments); + if (arguments.nhosts==0){ + fprintf(stderr,"At least one host (-h) must be specified\n"); + return EXIT_FAILURE; + } + if (arguments.ntests==0){ + for(i=0; i < BENCHMARK_NUMBER ; ++i){ + benchmarks[i].enabled=1; + } + arguments.ntests=BENCHMARK_NUMBER; + } + buffer=malloc(arguments.chunksize > 1024 ? arguments.chunksize : 1024); + if(buffer == NULL){ + fprintf(stderr,"Allocation of chunk buffer failed\n"); + return EXIT_FAILURE; + } + if (arguments.verbose > 0){ + fprintf(stdout, "Will try hosts "); + for(i=0;i 0) + fprintf(stdout,"Connecting to \"%s\"...\n",arguments.hosts[i]); + session=connect_host(arguments.hosts[i], arguments.verbose, arguments.cipher); + if(session != NULL && arguments.verbose > 0) + fprintf(stdout,"Success\n"); + if(session == NULL){ + fprintf(stderr,"Errors occurred, stopping\n"); + return EXIT_FAILURE; + } + do_benchmarks(session, &arguments, arguments.hosts[i]); + ssh_disconnect(session); + ssh_free(session); + } + return EXIT_SUCCESS; +} + diff --git a/tests/benchmarks/benchmarks.h b/tests/benchmarks/benchmarks.h new file mode 100644 index 0000000..26da09b --- /dev/null +++ b/tests/benchmarks/benchmarks.h @@ -0,0 +1,99 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 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. + */ + +#ifndef BENCHMARKS_H_ +#define BENCHMARKS_H_ + +#include + +/* benchmarks.c */ + +/* maximum number of parallel hosts that may be checked */ +#define MAX_HOSTS_CONNECT 20 + +enum libssh_benchmarks { + BENCHMARK_RAW_UPLOAD=0, + BENCHMARK_RAW_DOWNLOAD, + BENCHMARK_SCP_UPLOAD, + BENCHMARK_SCP_DOWNLOAD, + BENCHMARK_SYNC_SFTP_UPLOAD, + BENCHMARK_SYNC_SFTP_DOWNLOAD, + BENCHMARK_ASYNC_SFTP_DOWNLOAD, + BENCHMARK_NUMBER +}; + +struct argument_s { + const char *hosts[MAX_HOSTS_CONNECT]; + int verbose; + int nhosts; + int ntests; + unsigned int datasize; + unsigned int chunksize; + int concurrent_requests; + char *cipher; +}; + +extern char *buffer; + +typedef int (*bench_fct)(ssh_session session, struct argument_s *args, + float *bps); + +struct benchmark { + const char *name; + bench_fct fct; + int enabled; +}; + +/* latency.c */ + +struct timestamp_struct { + struct timeval timestamp; +}; + +int benchmarks_ping_latency (const char *host, float *average); +int benchmarks_ssh_latency (ssh_session session, float *average); + +void timestamp_init(struct timestamp_struct *ts); +float elapsed_time(struct timestamp_struct *ts); + +/* bench_raw.c */ + +int benchmarks_raw_up (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_raw_down (ssh_session session, struct argument_s *args, + float *bps); + +/* bench_scp.c */ + +int benchmarks_scp_up (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_scp_down (ssh_session session, struct argument_s *args, + float *bps); + +/* bench_sftp.c */ + +int benchmarks_sync_sftp_up (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_sync_sftp_down (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_async_sftp_down (ssh_session session, struct argument_s *args, + float *bps); +#endif /* BENCHMARKS_H_ */ diff --git a/tests/benchmarks/latency.c b/tests/benchmarks/latency.c new file mode 100644 index 0000000..09c50a0 --- /dev/null +++ b/tests/benchmarks/latency.c @@ -0,0 +1,148 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 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 "benchmarks.h" +#include + +#include +#include +#include +#include +#include + +#define PING_PROGRAM "/bin/ping" + +/** @internal + * @brief Calculates the RTT of the host with ICMP ping, and returns the + * average of the calculated RTT. + * @param[in] host hostname to ping. + * @param[out] average average RTT in milliseconds. + * @returns 0 on success, -1 if there is an error. + * @warning relies on an external ping program which may not exist on + * certain OS. + */ +int benchmarks_ping_latency (const char *host, float *average){ + const char *ptr; + char cmd[256]; + char line[1024]; + FILE *fd; + int found=0; + + /* strip out the hostname */ + ptr=strchr(host,'@'); + if(ptr) + ptr++; + else + ptr=host; + + snprintf(cmd,sizeof(cmd),"%s -n -q -c3 %s",PING_PROGRAM, ptr); + fd=popen(cmd,"r"); + if(fd==NULL){ + fprintf(stderr,"Error executing command : %s\n", strerror(errno)); + return -1; + } + + while(!found && fgets(line,sizeof(line),fd)!=NULL){ + if(strstr(line,"rtt")){ + ptr=strchr(line,'='); + if(ptr==NULL) + goto parseerror; + ptr=strchr(ptr,'/'); + if(ptr==NULL) + goto parseerror; + *average=strtof(ptr+1,NULL); + found=1; + break; + } + } + if(!found) + goto parseerror; + pclose(fd); + return 0; + +parseerror: + fprintf(stderr,"Parse error : couldn't locate average in %s",line); + pclose(fd); + return -1; +} + +/** @internal + * @brief initialize a timestamp to the current time. + * @param[out] ts A timestamp_struct pointer. + */ +void timestamp_init(struct timestamp_struct *ts){ + gettimeofday(&ts->timestamp,NULL); +} + +/** @internal + * @brief return the elapsed time since now and the moment ts was initialized. + * @param[in] ts An initialized timestamp_struct pointer. + * @return Elapsed time in milliseconds. + */ +float elapsed_time(struct timestamp_struct *ts){ + struct timeval now; + time_t secdiff; + long usecdiff; /* may be negative */ + + gettimeofday(&now,NULL); + secdiff=now.tv_sec - ts->timestamp.tv_sec; + usecdiff=now.tv_usec - ts->timestamp.tv_usec; + //printf("%d sec diff, %d usec diff\n",secdiff, usecdiff); + return (float) (secdiff*1000) + ((float)usecdiff)/1000; +} + +/** @internal + * @brief Calculates the RTT of the host with SSH channel operations, and + * returns the average of the calculated RTT. + * @param[in] session active SSH session to test. + * @param[out] average average RTT in milliseconds. + * @returns 0 on success, -1 if there is an error. + */ +int benchmarks_ssh_latency(ssh_session session, float *average){ + float times[3]; + struct timestamp_struct ts; + int i; + ssh_channel channel; + channel=ssh_channel_new(session); + if(channel==NULL) + goto error; + if(ssh_channel_open_session(channel)==SSH_ERROR) + goto error; + + for(i=0;i<3;++i){ + timestamp_init(&ts); + if(ssh_channel_request_env(channel,"TEST","test")==SSH_ERROR && + ssh_get_error_code(session)==SSH_FATAL) + goto error; + times[i]=elapsed_time(&ts); + } + ssh_channel_close(channel); + ssh_channel_free(channel); + channel=NULL; + printf("SSH request times : %f ms ; %f ms ; %f ms\n", times[0], times[1], times[2]); + *average=(times[0]+times[1]+times[2])/3; + return 0; +error: + fprintf(stderr,"Error calculating SSH latency : %s\n",ssh_get_error(session)); + if(channel) + ssh_channel_free(channel); + return -1; +} diff --git a/tests/chmodtest.c b/tests/chmodtest.c new file mode 100644 index 0000000..1e6f511 --- /dev/null +++ b/tests/chmodtest.c @@ -0,0 +1,33 @@ +#include + +#include +#include "examples_common.h" +#include + +int main(void) { + ssh_session session; + sftp_session sftp; + char buffer[1024*1024]; + int rc; + + session = connect_ssh("localhost", NULL, 0); + if (session == NULL) { + return 1; + } + + sftp=sftp_new(session); + sftp_init(sftp); + rc=sftp_rename(sftp,"/tmp/test","/tmp/test"); + rc=sftp_rename(sftp,"/tmp/test","/tmp/test"); + rc=sftp_chmod(sftp,"/tmp/test",0644); + if (rc < 0) { + printf("error : %s\n",ssh_get_error(sftp)); + + ssh_disconnect(session); + return 1; + } + + ssh_disconnect(session); + + return 0; +} diff --git a/tests/chroot_wrapper.c b/tests/chroot_wrapper.c new file mode 100644 index 0000000..3545f39 --- /dev/null +++ b/tests/chroot_wrapper.c @@ -0,0 +1,8 @@ +/* silent gcc */ +int chroot(const char *); + +int chroot(const char *path) +{ + (void)path; + return 0; +} diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt new file mode 100644 index 0000000..70b5de3 --- /dev/null +++ b/tests/client/CMakeLists.txt @@ -0,0 +1,68 @@ +project(clienttests C) + +find_package(socket_wrapper) + +set(LIBSSH_CLIENT_TESTS + torture_algorithms + torture_client_config + torture_connect + torture_hostkey + torture_auth + torture_rekey + torture_forward + torture_knownhosts + torture_knownhosts_verify + torture_proxycommand + torture_session + torture_request_env + torture_client_global_requests) + +find_program(SCP_EXECUTABLE NAMES scp) +if (SCP_EXECUTABLE) + set(LIBSSH_CLIENT_TESTS + ${LIBSSH_CLIENT_TESTS} + torture_scp) +endif() + +if (DEFAULT_C_NO_DEPRECATION_FLAGS) + set_source_files_properties(torture_knownhosts.c + PROPERTIES + COMPILE_FLAGS ${DEFAULT_C_NO_DEPRECATION_FLAGS}) +endif() + +if (WITH_SFTP) + if (WITH_BENCHMARKS) + set(SFTP_BENCHMARK_TESTS + torture_sftp_benchmark) + endif() + set(LIBSSH_CLIENT_TESTS + ${LIBSSH_CLIENT_TESTS} + torture_sftp_ext + torture_sftp_canonicalize_path + torture_sftp_dir + torture_sftp_read + torture_sftp_fsync + ${SFTP_BENCHMARK_TESTS}) +endif (WITH_SFTP) + +foreach(_CLI_TEST ${LIBSSH_CLIENT_TESTS}) + add_cmocka_test(${_CLI_TEST} + SOURCES ${_CLI_TEST}.c + COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} + LINK_LIBRARIES ${TORTURE_LIBRARY} + ) + + if (OSX) + set_property( + TEST + ${_CLI_TEST} + PROPERTY + ENVIRONMENT DYLD_FORCE_FLAT_NAMESPACE=1;DYLD_INSERT_LIBRARIES=${SOCKET_WRAPPER_LIBRARY}) + else () + set_property( + TEST + ${_CLI_TEST} + PROPERTY + ENVIRONMENT ${TORTURE_ENVIRONMENT}) + endif() +endforeach() diff --git a/tests/client/torture_algorithms.c b/tests/client/torture_algorithms.c new file mode 100644 index 0000000..19f25db --- /dev/null +++ b/tests/client/torture_algorithms.c @@ -0,0 +1,962 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 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" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) { + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool false_v = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + /* Prevent parsing configuration files that can introduce different + * algorithms then we want to test */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &false_v); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void test_algorithm(ssh_session session, + const char *kex, + const char *cipher, + const char *hmac) { + int rc; + char data[256]; + size_t len_to_test[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 10, + 12, 15, 16, 20, + 31, 32, 33, + 63, 64, 65, + 100, 127, 128 + }; + unsigned int i; + + if (kex != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex); + assert_ssh_return_code(session, rc); + } + + if (cipher != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher); + assert_ssh_return_code(session, rc); + } + + if (hmac != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, hmac); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, hmac); + assert_ssh_return_code(session, rc); + } + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* send ignore packets of all sizes */ + memset(data, 0, sizeof(data)); + for (i = 0; i < (sizeof(len_to_test) / sizeof(size_t)); i++) { + memset(data, 'A', len_to_test[i]); + ssh_send_ignore(session, data); + ssh_handle_packets(session, 50); + } + + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_int_equal(rc, SSH_REQUEST_DENIED); + } + + ssh_disconnect(session); +} + +static void torture_algorithms_aes128_cbc_hmac_sha1(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha1"); +} + +static void torture_algorithms_aes128_cbc_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha2-256"); +} + +static void torture_algorithms_aes128_cbc_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha2-512"); +} + +static void torture_algorithms_aes128_cbc_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes128_cbc_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes128_cbc_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha1(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha1"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha2-256"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha2-512"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha1(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha1"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha2-256"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha2-512"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha1(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha1"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha2-256"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha2-512"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha1(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha1"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha2-256"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha2-512"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha1(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha1"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha2-256"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha2-512"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes128_gcm(void **state) +{ + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-gcm@openssh.com", NULL); +} + +static void torture_algorithms_aes256_gcm(void **state) +{ + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-gcm@openssh.com", NULL); +} + +static void torture_algorithms_3des_cbc_hmac_sha1(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha1"); +} + +static void torture_algorithms_3des_cbc_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha2-256"); +} + +static void torture_algorithms_3des_cbc_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha2-512"); +} + +static void torture_algorithms_3des_cbc_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_3des_cbc_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_3des_cbc_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha2-512-etm@openssh.com"); +} + +#ifdef WITH_BLOWFISH_CIPHER +#if ((OPENSSH_VERSION_MAJOR == 7 && OPENSSH_VERSION_MINOR < 6) || OPENSSH_VERSION_MAJOR <= 6) +static void torture_algorithms_blowfish_cbc_hmac_sha1(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha1"); +} + +static void torture_algorithms_blowfish_cbc_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha2-256"); +} + +static void torture_algorithms_blowfish_cbc_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha2-512"); +} + +static void torture_algorithms_blowfish_cbc_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_blowfish_cbc_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_blowfish_cbc_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha2-512-etm@openssh.com"); +} +#endif +#endif /* WITH_BLOWFISH_CIPHER */ + +static void torture_algorithms_chacha20_poly1305(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + NULL, /*kex*/ + "chacha20-poly1305@openssh.com", + NULL); +} + +static void torture_algorithms_zlib(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_C_S, "zlib"); +#ifdef WITH_ZLIB + assert_int_equal(rc, SSH_OK); +#else + assert_int_equal(rc, SSH_ERROR); +#endif + + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, "zlib"); +#ifdef WITH_ZLIB + assert_int_equal(rc, SSH_OK); +#else + assert_int_equal(rc, SSH_ERROR); +#endif + + rc = ssh_connect(session); +#ifdef WITH_ZLIB + if (ssh_get_openssh_version(session)) { + assert_false(rc == SSH_OK); + ssh_disconnect(session); + return; + } +#endif + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_int_equal(rc, SSH_REQUEST_DENIED); + } + + ssh_disconnect(session); +} + +static void torture_algorithms_zlib_openssh(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_C_S, "zlib@openssh.com"); +#ifdef WITH_ZLIB + assert_int_equal(rc, SSH_OK); +#else + assert_int_equal(rc, SSH_ERROR); +#endif + + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, "zlib@openssh.com"); +#ifdef WITH_ZLIB + assert_int_equal(rc, SSH_OK); +#else + assert_int_equal(rc, SSH_ERROR); +#endif + + rc = ssh_connect(session); +#ifdef WITH_ZLIB + if (ssh_get_openssh_version(session)) { + assert_true(rc==SSH_OK); + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_int_equal(rc, SSH_REQUEST_DENIED); + } + ssh_disconnect(session); + return; + } + assert_false(rc == SSH_OK); +#else + assert_int_equal(rc, SSH_OK); +#endif + + ssh_disconnect(session); +} + +#if defined(HAVE_ECC) +static void torture_algorithms_ecdh_sha2_nistp256(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, "ecdh-sha2-nistp256", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_ecdh_sha2_nistp384(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, "ecdh-sha2-nistp384", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_ecdh_sha2_nistp521(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, "ecdh-sha2-nistp521", NULL/*cipher*/, NULL/*hmac*/); +} +#endif + +#if ((OPENSSH_VERSION_MAJOR == 7 && OPENSSH_VERSION_MINOR >= 3) || OPENSSH_VERSION_MAJOR > 7) +static void torture_algorithms_ecdh_curve25519_sha256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, "curve25519-sha256", NULL/*cipher*/, NULL/*hmac*/); +} +#endif + +#if ((OPENSSH_VERSION_MAJOR == 6 && OPENSSH_VERSION_MINOR >= 5) || OPENSSH_VERSION_MAJOR > 6) +static void torture_algorithms_ecdh_curve25519_sha256_libssh_org(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, "curve25519-sha256@libssh.org", NULL/*cipher*/, NULL/*hmac*/); +} +#endif + +static void torture_algorithms_dh_group1(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, "diffie-hellman-group1-sha1", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_dh_group14(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, "diffie-hellman-group14-sha1", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_dh_group14_sha256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, "diffie-hellman-group14-sha256", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_dh_group16(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, "diffie-hellman-group16-sha512", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_dh_group18(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, "diffie-hellman-group18-sha512", NULL/*cipher*/, NULL/*hmac*/); +} + +#ifdef WITH_GEX +static void torture_algorithms_dh_gex_sha1(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "diffie-hellman-group-exchange-sha1", + NULL, /* cipher */ + NULL); /* hmac */ +} + +static void torture_algorithms_dh_gex_sha256(void **state) +{ + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, + "diffie-hellman-group-exchange-sha256", + NULL, /* cipher */ + NULL); /* hmac */ +} +#endif /* WITH_GEX */ + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_gcm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_gcm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha2_512_etm, + session_setup, + session_teardown), +#ifdef WITH_BLOWFISH_CIPHER +#if ((OPENSSH_VERSION_MAJOR == 7 && OPENSSH_VERSION_MINOR < 6) || OPENSSH_VERSION_MAJOR <= 6) + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha2_512_etm, + session_setup, + session_teardown), +#endif +#endif /* WITH_BLOWFISH_CIPHER */ + cmocka_unit_test_setup_teardown(torture_algorithms_chacha20_poly1305, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_zlib, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_zlib_openssh, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_group1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_group14, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_group14_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_group16, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_group18, + session_setup, + session_teardown), +#ifdef WITH_GEX + cmocka_unit_test_setup_teardown(torture_algorithms_dh_gex_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_gex_sha256, + session_setup, + session_teardown), +#endif /* WITH_GEX */ +#if ((OPENSSH_VERSION_MAJOR == 7 && OPENSSH_VERSION_MINOR >= 3) || OPENSSH_VERSION_MAJOR > 7) + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_curve25519_sha256, + session_setup, + session_teardown), +#endif +#if ((OPENSSH_VERSION_MAJOR == 6 && OPENSSH_VERSION_MINOR >= 5) || OPENSSH_VERSION_MAJOR > 6) + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_curve25519_sha256_libssh_org, + session_setup, + session_teardown), +#endif +#if defined(HAVE_ECC) + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sha2_nistp256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sha2_nistp384, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sha2_nistp521, + session_setup, + session_teardown), +#endif + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/tests/client/torture_auth.c b/tests/client/torture_auth.c new file mode 100644 index 0000000..5a3d2e0 --- /dev/null +++ b/tests/client/torture_auth.c @@ -0,0 +1,958 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 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" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +/* agent_is_running */ +#include "agent.c" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, true); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool b = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static int pubkey_setup(void **state) +{ + int rc; + + rc = session_setup(state); + if (rc != 0) { + return rc; + } + + /* Make sure we do not interfere with another ssh-agent */ + unsetenv("SSH_AUTH_SOCK"); + unsetenv("SSH_AGENT_PID"); + + return 0; +} + +static int agent_setup(void **state) +{ + struct torture_state *s = *state; + char ssh_agent_cmd[4096]; + char ssh_agent_sock[1024]; + char ssh_agent_pidfile[1024]; + char bob_ssh_key[1024]; + struct passwd *pwd; + int rc; + + rc = pubkey_setup(state); + if (rc != 0) { + return rc; + } + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(ssh_agent_sock, + sizeof(ssh_agent_sock), + "%s/agent.sock", + s->socket_dir); + + snprintf(ssh_agent_pidfile, + sizeof(ssh_agent_pidfile), + "%s/agent.pid", + s->socket_dir); + + /* Production ready code!!! */ + snprintf(ssh_agent_cmd, + sizeof(ssh_agent_cmd), + "eval `ssh-agent -a %s`; echo $SSH_AGENT_PID > %s", + ssh_agent_sock, ssh_agent_pidfile); + + /* run ssh-agent and ssh-add as the normal user */ + unsetenv("UID_WRAPPER_ROOT"); + + rc = system(ssh_agent_cmd); + assert_return_code(rc, errno); + + setenv("SSH_AUTH_SOCK", ssh_agent_sock, 1); + setenv("TORTURE_SSH_AGENT_PIDFILE", ssh_agent_pidfile, 1); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "ssh-add %s/.ssh/id_rsa", + pwd->pw_dir); + + rc = system(bob_ssh_key); + assert_return_code(rc, errno); + + return 0; +} + +static int agent_cert_setup(void **state) +{ + char bob_alt_ssh_key[1024]; + struct passwd *pwd; + int rc; + + rc = agent_setup(state); + if (rc != 0) { + return rc; + } + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + /* remove all keys, load alternative key + cert */ + snprintf(bob_alt_ssh_key, + sizeof(bob_alt_ssh_key), + "ssh-add -D && ssh-add %s/.ssh_cert/id_rsa", + pwd->pw_dir); + + rc = system(bob_alt_ssh_key); + assert_return_code(rc, errno); + + return 0; +} + +static int agent_teardown(void **state) +{ + const char *ssh_agent_pidfile; + int rc; + + rc = session_teardown(state); + if (rc != 0) { + return rc; + } + + ssh_agent_pidfile = getenv("TORTURE_SSH_AGENT_PIDFILE"); + assert_non_null(ssh_agent_pidfile); + + /* kill agent pid */ + torture_terminate_process(ssh_agent_pidfile); + + unlink(ssh_agent_pidfile); + + unsetenv("TORTURE_SSH_AGENT_PIDFILE"); + unsetenv("SSH_AUTH_SOCK"); + + return 0; +} + +static void torture_auth_none(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } +} + +static void torture_auth_none_nonblocking(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + ssh_set_blocking(session,0); + + do { + rc = ssh_userauth_none(session,NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + +} + +static void torture_auth_autopubkey(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + /* Authenticate as alice with bob his pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_auth_autopubkey_nonblocking(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session,0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_auth_kbdint(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_INTERACTIVE); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 1); + + rc = ssh_userauth_kbdint_setanswer(session, 0, TORTURE_SSH_USER_BOB_PASSWORD); + assert_false(rc < 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + /* Sometimes, SSH server send an empty query at the end of exchange */ + if(rc == SSH_AUTH_INFO) { + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 0); + rc = ssh_userauth_kbdint(session, NULL, NULL); + } + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_auth_kbdint_nonblocking(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session,0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_INTERACTIVE); + + do { + rc = ssh_userauth_kbdint(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 1); + rc = ssh_userauth_kbdint_setanswer(session, 0, TORTURE_SSH_USER_BOB_PASSWORD); + assert_false(rc < 0); + + do { + rc = ssh_userauth_kbdint(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + /* Sometimes, SSH server send an empty query at the end of exchange */ + if(rc == SSH_AUTH_INFO) { + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 0); + do { + rc = ssh_userauth_kbdint(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + } + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_auth_password(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + rc = ssh_userauth_password(session, NULL, TORTURE_SSH_USER_BOB_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_auth_password_nonblocking(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session,0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + do { + rc = ssh_userauth_password(session, NULL, TORTURE_SSH_USER_BOB_PASSWORD); + } while(rc==SSH_AUTH_AGAIN); + + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_auth_agent(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + return; + } + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_agent(session, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_auth_agent_nonblocking(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + return; + } + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + ssh_set_blocking(session,0); + + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_auth_cert(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_key privkey = NULL; + ssh_key cert = NULL; + char bob_ssh_key[1024]; + char bob_ssh_cert[2048]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh_cert/id_rsa", + pwd->pw_dir); + snprintf(bob_ssh_cert, + sizeof(bob_ssh_cert), + "%s-cert.pub", + bob_ssh_key); + + /* cert has been signed for login as alice */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_import_cert_file(bob_ssh_cert, &cert); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_try_publickey(session, NULL, cert); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(cert); +} + +static void torture_auth_agent_cert(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + /* Skip this test if in FIPS mode. + * + * OpenSSH agent has a bug which makes it to not use SHA2 in signatures when + * using certificates. It always uses SHA1. + * + * This should be removed as soon as OpenSSH agent bug is fixed. + * (see https://gitlab.com/libssh/libssh-mirror/merge_requests/34) */ + if (ssh_fips_mode()) { + skip(); + } else { + /* After the bug is solved, this also should be removed */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-rsa-cert-v01@openssh.com"); + assert_int_equal(rc, SSH_OK); + } + + /* Setup loads a different key, tests are exactly the same. */ + torture_auth_agent(state); +} + +static void torture_auth_agent_cert_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + /* Skip this test if in FIPS mode. + * + * OpenSSH agent has a bug which makes it to not use SHA2 in signatures when + * using certificates. It always uses SHA1. + * + * This should be removed as soon as OpenSSH agent bug is fixed. + * (see https://gitlab.com/libssh/libssh-mirror/merge_requests/34) */ + if (ssh_fips_mode()) { + skip(); + } else { + /* After the bug is solved, this also should be removed */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-rsa-cert-v01@openssh.com"); + assert_int_equal(rc, SSH_OK); + } + + torture_auth_agent_nonblocking(state); +} + +static void torture_auth_pubkey_types(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Disable RSA key types for authentication */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp384"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Now enable it and retry */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "rsa-sha2-512,ssh-rsa"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_auth_pubkey_types_ecdsa(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* We have only the 256b key -- whitelisting only larger should fail */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp384"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Verify we can use also ECDSA keys with their various names */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp256"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + +} + +static void torture_auth_pubkey_types_ed25519(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd; + int rc; + + if (ssh_fips_mode()) { + skip(); + } + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_ed25519", + pwd->pw_dir); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Import the ED25519 private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + /* Enable only RSA keys -- authentication should fail */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-rsa"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Verify we can use also ed25519 keys */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-ed25519"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +static void torture_auth_pubkey_types_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Disable RSA key types for authentication */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Now enable it and retry */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "rsa-sha2-512,ssh-rsa"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + +} + +static void torture_auth_pubkey_types_ecdsa_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* We have only the 256b key -- whitelisting only larger should fail */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp384"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Verify we can use also ECDSA key to authenticate */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp256"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + +} + +static void torture_auth_pubkey_types_ed25519_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd; + int rc; + + if (ssh_fips_mode()) { + skip(); + } + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_ed25519", + pwd->pw_dir); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Import the ED25519 private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + /* Enable only RSA keys -- authentication should fail */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-rsa"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Verify we can use also ED25519 key to authenticate */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-ed25519"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_auth_none, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_none_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_password, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_password_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_kbdint, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_kbdint_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_nonblocking, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent, + agent_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_nonblocking, + agent_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert_nonblocking, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_nonblocking, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ecdsa, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ecdsa_nonblocking, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ed25519, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ed25519_nonblocking, + pubkey_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/tests/client/torture_client_config.c b/tests/client/torture_client_config.c new file mode 100644 index 0000000..c413619 --- /dev/null +++ b/tests/client/torture_client_config.c @@ -0,0 +1,237 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include "torture.h" +#include "libssh/session.h" +#include "libssh/misc.h" + +#define LIBSSH_SSH_CONFIG "libssh_config" + +#define TORTURE_CONFIG_USER "test-user" + +#define CIPHERS "aes256-gcm@openssh.com,chacha20-poly1305@openssh.com" +#define CIPHERS2 "aes256-cbc,aes128-ctr" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int setup_config_files(void **state) +{ + struct torture_state *s = *state; + int verbosity; + struct passwd *pwd; + char *filename = NULL; + int rc; + + /* Work under the bob's UID to be able to load his configuration file */ + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + filename = ssh_path_expand_tilde("~/.ssh/config"); + torture_write_file(filename, "Ciphers "CIPHERS"\nTestBogus1\nUser "TORTURE_CONFIG_USER); + free(filename); + + torture_write_file(LIBSSH_SSH_CONFIG, "Ciphers "CIPHERS2"\nTestBogus2\n"); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + return 0; +} + +static int teardown(void **state) +{ + struct torture_state *s = *state; + char *filename; + + filename = ssh_path_expand_tilde("~/.ssh/config"); + if (filename != NULL) { + if (strlen(filename) > 0) { + unlink(filename); + } + SAFE_FREE(filename); + } + + unlink(LIBSSH_SSH_CONFIG); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +/* This tests makes sure that parsing both system-wide and per-user + * configuration files retains OpenSSH semantics (the per-user overrides + * the system-wide values). + * This function ssh_options_parse_config() has hardcoded path to the + * system-wide configuration file so this might not test anything at all + * if this system-wide file does not overwrite this option. + */ +static void torture_client_config_system(void **state) +{ + struct torture_state *s = *state; + int ret = 0; + + char *fips_ciphers = NULL; + + if (ssh_fips_mode()) { + fips_ciphers = ssh_keep_fips_algos(SSH_CRYPT_C_S, CIPHERS); + assert_non_null(fips_ciphers); + } + + /* The first tests assumes there is system-wide configuration file + * setting Ciphers to some non-default value. We do not have any control + * of that in this test case. + */ + ret = ssh_options_parse_config(s->ssh.session, NULL); + assert_ssh_return_code(s->ssh.session, ret); + + assert_non_null(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S]); + assert_non_null(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S], + fips_ciphers); + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C], + fips_ciphers); + } else { + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S], + CIPHERS); + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C], + CIPHERS); + } + + /* Make sure the configuration was processed and user modified */ + assert_string_equal(s->ssh.session->opts.username, TORTURE_CONFIG_USER); + + SAFE_FREE(fips_ciphers); +} + +/* This tests makes sure that parsing both system-wide and per-user + * configuration files retains OpenSSH semantics (the per-user overrides + * the system-wide values). + * The function ssh_options_parse_config() has hardcoded path to the + * system-wide configuraion file so we try to emmulate the behavior by parsing + * the files separately in the same order. + */ +static void torture_client_config_emulate(void **state) +{ + struct torture_state *s = *state; + char *filename = NULL; + int ret = 0; + + char *fips_ciphers = NULL; + + if (ssh_fips_mode()) { + fips_ciphers = ssh_keep_fips_algos(SSH_CRYPT_C_S, CIPHERS); + assert_non_null(fips_ciphers); + } + + /* The first tests assumes there is system-wide configuration file + * setting Ciphers to some non-default value. We do not have any control + * of that in this test case + */ + filename = ssh_path_expand_tilde("~/.ssh/config"); + ret = ssh_options_parse_config(s->ssh.session, filename); + free(filename); + assert_ssh_return_code(s->ssh.session, ret); + + ret = ssh_options_parse_config(s->ssh.session, LIBSSH_SSH_CONFIG); + assert_ssh_return_code(s->ssh.session, ret); + + assert_non_null(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S]); + assert_non_null(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S], + fips_ciphers); + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C], + fips_ciphers); + } else { + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S], + CIPHERS); + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C], + CIPHERS); + } + /* Make sure the configuration was processed and user modified */ + assert_string_equal(s->ssh.session->opts.username, TORTURE_CONFIG_USER); + + SAFE_FREE(fips_ciphers); +} + +/* This verifies that configuration files are parsed by default. + */ +static void torture_client_config_autoparse(void **state) +{ + struct torture_state *s = *state; + int ret = 0; + + ret = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + /* Make sure the configuration was processed and user modified */ + assert_string_equal(s->ssh.session->opts.username, TORTURE_CONFIG_USER); +} + +/* This verifies that we are able to suppress parsing of the configuration files + * on connect using an option. + */ +static void torture_client_config_suppress(void **state) +{ + struct torture_state *s = *state; + bool b = false; + int ret = 0; + + ret = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, ret); + + ret = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + /* Make sure the configuration was not processed and user modified */ + assert_string_equal(s->ssh.session->opts.username, "bob"); +} + + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_client_config_system, + setup_config_files, + teardown), + cmocka_unit_test_setup_teardown(torture_client_config_emulate, + setup_config_files, + teardown), + cmocka_unit_test_setup_teardown(torture_client_config_autoparse, + setup_config_files, + teardown), + cmocka_unit_test_setup_teardown(torture_client_config_suppress, + setup_config_files, + teardown), + }; + + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + return rc; +} diff --git a/tests/client/torture_client_global_requests.c b/tests/client/torture_client_global_requests.c new file mode 100644 index 0000000..1320ea0 --- /dev/null +++ b/tests/client/torture_client_global_requests.c @@ -0,0 +1,152 @@ +/* + * torture_client_global_requests.c - Tests for client global requests + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/channels.h" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, true); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool b = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static int authenticate(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_password(session, NULL, TORTURE_SSH_USER_BOB_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + return rc; +} + +static void torture_unknown_request(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + + rc = authenticate(state); + assert_ssh_return_code(session, rc); + + /* Request asking for reply */ + rc = ssh_global_request(session, "unknown-request-00@test.com", NULL, 1); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + /* Request and don't ask for reply */ + rc = ssh_global_request(session, "another-bad-req-00@test.com", NULL, 0); + assert_ssh_return_code(session, rc); + + /* Open channel to make sure the session is still working */ + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + ssh_channel_close(channel); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_unknown_request, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/tests/client/torture_connect.c b/tests/client/torture_connect.c new file mode 100644 index 0000000..0ad8ef5 --- /dev/null +++ b/tests/client/torture_connect.c @@ -0,0 +1,205 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 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" + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#include +#include +#include +#include +#include +#include + +/* Should work until Apnic decides to assign it :) */ +#define BLACKHOLE "1.1.1.1" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, BLACKHOLE); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_connect_nonblocking(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + ssh_set_blocking(session,0); + + do { + rc = ssh_connect(session); + assert_ssh_return_code_not_equal(session, rc, SSH_ERROR); + } while(rc == SSH_AGAIN); + + assert_ssh_return_code(session, rc); +} + +#if 0 /* This does not work with socket_wrapper */ +static void torture_connect_timeout(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + struct timeval before, after; + int rc; + long timeout = 2; + time_t sec; + suseconds_t usec; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, BLACKHOLE); + assert_true(rc == SSH_OK); + rc = ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &timeout); + assert_true(rc == SSH_OK); + + rc = gettimeofday(&before, NULL); + assert_true(rc == 0); + rc = ssh_connect(session); + assert_true(rc == SSH_ERROR); + rc = gettimeofday(&after, NULL); + assert_true(rc == 0); + sec = after.tv_sec - before.tv_sec; + usec = after.tv_usec - before.tv_usec; + /* Borrow a second for the missing usecs, but don't bother calculating */ + if (usec < 0) + sec--; + assert_in_range(sec, 1, 3); +} +#endif + +static void torture_connect_double(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +static void torture_connect_failure(void **state) { + /* + * The intent of this test is to check that a fresh + * ssh_new/ssh_disconnect/ssh_free sequence doesn't crash/leak + * and the behavior of a double ssh_disconnect + */ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + + ssh_disconnect(session); +} + +static void torture_connect_socket(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + + int rc; + int sock_fd = 0; + struct sockaddr_in server_addr = { + .sin_family = AF_INET, + .sin_port = htons(22), + .sin_addr.s_addr = inet_addr(TORTURE_SSH_SERVER), + }; + + sock_fd = socket(AF_INET, SOCK_STREAM, 0); + assert_true(sock_fd > 2); + + rc = connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); + assert_return_code(rc, errno); + + ssh_options_set(session, SSH_OPTIONS_FD, &sock_fd); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_connect_nonblocking, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_double, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_failure, session_setup, session_teardown), +#if 0 + cmocka_unit_test_setup_teardown(torture_connect_timeout, session_setup, session_teardown), +#endif + cmocka_unit_test_setup_teardown(torture_connect_socket, session_setup, session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} diff --git a/tests/client/torture_forward.c b/tests/client/torture_forward.c new file mode 100644 index 0000000..dcbdcbd --- /dev/null +++ b/tests/client/torture_forward.c @@ -0,0 +1,117 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_ssh_forward(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + int dport; + int bound_port; + int rc; + int verbosity = SSH_LOG_TRACE; + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + rc = ssh_channel_listen_forward(session, "127.0.0.21", 8080, &bound_port); + assert_ssh_return_code(session, rc); + + c = ssh_channel_accept_forward(session, 10, &dport); + /* We do not get a listener and run into the timeout here */ + assert_null(c); + + ssh_channel_send_eof(c); + ssh_channel_close(c); +} + +int torture_run_tests(void) { + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_ssh_forward, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} diff --git a/tests/client/torture_hostkey.c b/tests/client/torture_hostkey.c new file mode 100644 index 0000000..574a840 --- /dev/null +++ b/tests/client/torture_hostkey.c @@ -0,0 +1,242 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_hostkey_rsa(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char rsa[] = "ssh-rsa"; + + int rc; + + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, &rsa); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +static void torture_hostkey_ed25519(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char ed[] = "ssh-ed25519"; + + int rc; + + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, &ed); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +#ifdef HAVE_DSA +static void torture_hostkey_dss(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char rsa[] = "ssh-dss"; + + int rc; + + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, &rsa); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} +#endif /* HAVE_DSA */ + +#ifdef HAVE_ECC +static void torture_hostkey_ecdsa(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char ecdsa[] = "ecdsa-sha2-nistp521"; + + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, &ecdsa); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} +#endif + +static void torture_hostkey_rsa_sha256(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char rsa[] = "rsa-sha2-256"; + + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, &rsa); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +static void torture_hostkey_rsa_sha512(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char rsa[] = "rsa-sha2-512"; + + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, &rsa); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_hostkey_rsa, session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_hostkey_ed25519, session_setup, + session_teardown), +#ifdef HAVE_ECC + cmocka_unit_test_setup_teardown(torture_hostkey_ecdsa, session_setup, + session_teardown), +#endif +#ifdef HAVE_DSA + cmocka_unit_test_setup_teardown(torture_hostkey_dss, session_setup, + session_teardown), +#endif + /* the client is able to handle SHA2 extension (if negotiated) */ + cmocka_unit_test_setup_teardown(torture_hostkey_rsa_sha256, + session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_hostkey_rsa_sha512, + session_setup, session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} diff --git a/tests/client/torture_knownhosts.c b/tests/client/torture_knownhosts.c new file mode 100644 index 0000000..fcc5484 --- /dev/null +++ b/tests/client/torture_knownhosts.c @@ -0,0 +1,505 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 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" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "torture_key.h" + +#include +#include +#include + +#include "session.c" +#include "known_hosts.c" + +#define TMP_FILE_TEMPLATE "known_hosts_XXXXXX" + +#define BADRSA "AAAAB3NzaC1yc2EAAAADAQABAAABAQChm5" \ + "a6Av65O8cKtx5YXOnui3wJnYE6A6J/I4kZSAibbn14Jcl+34VJQwv96f25AxNmo" \ + "NwoiZV93IzdypQmiuieh6s6wB9WhYjU9K/6CkIpNhpCxswA90b3ePjS7LnR9B9J" \ + "slPSbG1H0KC1c5lb7G3utXteXtM+4YvCvpN5VdC4CpghT+p0cwN2Na8Md5vRItz" \ + "YgIytryNn7LLiwYfoSxvWigFrTTZsrVtCOYyNgklmffpGdzuC43wdANvTewfI9G" \ + "o71r8EXmEc228CrYPmb8Scv3mpXFK/BosohSGkPlEHu9lf3YjnknBicDaVtJOYp" \ + "wnXJPjZo2EhG79HxDRpjJHH" +#define BADED25519 "AAAAC3NzaC1lZDI1NTE5AAAAIE74wHmKKkrxpW/dZ69pKPlMoWG9VvWfrNnUkWRQqaDa" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool process_config = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, + &process_config); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + + +static void torture_knownhosts_port(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + char buffer[200]; + char *p; + FILE *file; + int rc; + bool process_config = false; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + session->opts.port = 1234; + rc = ssh_write_knownhost(session); + assert_ssh_return_code(session, rc); + + file = fopen(known_hosts_file, "r"); + assert_non_null(file); + p = fgets(buffer, sizeof(buffer), file); + assert_non_null(p); + fclose(file); + buffer[sizeof(buffer) - 1] = '\0'; + assert_non_null(strstr(buffer,"[127.0.0.10]:1234 ")); + + ssh_disconnect(session); + ssh_free(session); + + /* Now, connect back to the ssh server and verify the known host line */ + s->ssh.session = session = ssh_new(); + + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + free(known_hosts_file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + session->opts.port = 1234; + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_OK); +} + +static void torture_knownhosts_wildcard(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + const char *key = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + key = torture_get_testkey_pub(SSH_KEYTYPE_RSA); + fprintf(file, "[127.0.0.10]:* %s\n", key); + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + free(known_hosts_file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_OK); +} + +static void torture_knownhosts_standard_port(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + const char *key = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + key = torture_get_testkey_pub(SSH_KEYTYPE_RSA); + fprintf(file, "[127.0.0.10]:22 %s\n", key); + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + free(known_hosts_file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_OK); +} + +static void torture_knownhosts_fail(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + free(known_hosts_file); + + fprintf(file, "127.0.0.10 ssh-rsa %s\n", BADRSA); + fclose(file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_CHANGED); +} + +static void torture_knownhosts_other(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + free(known_hosts_file); + + fprintf(file, "127.0.0.10 ssh-rsa %s\n", BADRSA); + fclose(file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_FOUND_OTHER); +} + +static void torture_knownhosts_other_auto(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_NOT_KNOWN); + + rc = ssh_write_knownhost(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + ssh_free(session); + + /* connect again and check host key */ + session = ssh_new(); + assert_non_null(session); + + s->ssh.session = session; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* ssh-rsa is the default but libssh should try ssh-ed25519 instead */ + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_OK); + + /* session will be freed by session_teardown() */ + free(known_hosts_file); +} + +static void torture_knownhosts_conflict(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, "127.0.0.10 ssh-rsa %s\n", BADRSA); + fprintf(file, "127.0.0.10 ssh-ed25519 %s\n", BADED25519); + fclose(file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_CHANGED); + + rc = ssh_write_knownhost(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + ssh_free(session); + + /* connect again and check host key */ + session = ssh_new(); + assert_non_null(session); + + s->ssh.session = session; + + ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_OK); + + /* session will be freed by session_teardown() */ + free(known_hosts_file); +} + +static void torture_knownhosts_no_hostkeychecking(void **state) +{ + + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + enum ssh_known_hosts_e found; + int strict_host_key_checking = 0; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + free(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_UNKNOWN); + + rc = ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &strict_host_key_checking); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_knownhosts_wildcard, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_standard_port, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_port, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_fail, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_other, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_other_auto, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_conflict, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_no_hostkeychecking, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} diff --git a/tests/client/torture_knownhosts_verify.c b/tests/client/torture_knownhosts_verify.c new file mode 100644 index 0000000..8596334 --- /dev/null +++ b/tests/client/torture_knownhosts_verify.c @@ -0,0 +1,519 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "torture_key.h" + +#include +#include + +#include "knownhosts.c" + +#define TMP_FILE_TEMPLATE "known_hosts_XXXXXX" + +#define BAD_RSA "AAAAB3NzaC1yc2EAAAADAQABAAABAQDXvXuawzaArEwkLIXTz/EWywLOC" \ + "tqQL3P9yKkrhz6AplXP2PhOh5pyxa1VfGKe453jNeYBJ0ROto3BshXgZX" \ + "bo86oLXTkbe0gO5xi3r5WjXxjOFvRRTLot5fPLNDOv9+TnsPmkNn0iIey" \ + "PnfrcPIyjWt5zSWUfkNC8oNHxsiSshjpbJvTXSDipukpUy41d7jg4uWGu" \ + "onMTF7yu7HfuHqq7lhb0WlwSpfbqAbfYARBddcdcARyhix4RMWZZqVY20" \ + "H3Vsjq8bjKC+NJXFce1PRg+qcOWQdlXEei4dkzAvHvfQRx1TjzkrBZ6B6" \ + "thmZtyeb9IsiB0tg2g0JN2VTAGkxqp" + +const char template[] = "temp_dir_XXXXXX"; + +static int sshd_group_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_group_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + int rc; + + bool process_config = false; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, + &process_config); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +#define KNOWN_HOST_ENTRY_ECDSA "127.0.0.10 ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl17sFnTTrT8wYo1FCzE07wV262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee16VwhJGsMzGGh1FJ0XXhRJjUbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOOB7EmFZd18g==\n" +#define KNOWN_HOST_ENTRY_ED25519 "127.0.0.10 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCYiOyvMYLtkgoEyEKlLV+klM+BU6Nh3PmAiqX\n" +static void torture_knownhosts_export(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char *entry = NULL; + char *p = NULL; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_session_export_known_hosts_entry(session, &entry); + assert_ssh_return_code(session, rc); + + p = strstr(entry, "ssh-ed25519"); + if (p != NULL) { + assert_string_equal(entry, KNOWN_HOST_ENTRY_ED25519); + } else { + assert_string_equal(entry, KNOWN_HOST_ENTRY_ECDSA); + } + SAFE_FREE(entry); +} + +static void torture_knownhosts_write_and_verify(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + enum ssh_known_hosts_e found; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_session_update_known_hosts(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); +} + +static void torture_knownhosts_precheck(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + struct ssh_list *algo_list = NULL; + struct ssh_iterator *it = NULL; + size_t algo_count; + const char *algo = NULL; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_ED25519)); + + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_ECDSA_P521)); + + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + free(known_hosts_file); + + algo_list = ssh_known_hosts_get_algorithms(session); + assert_non_null(algo_list); + + algo_count = ssh_list_count(algo_list); + assert_int_equal(algo_count, 3); + + it = ssh_list_get_iterator(algo_list); + assert_non_null(it); + algo = ssh_iterator_value(const char *, it); + assert_string_equal(algo, "ssh-rsa"); + + ssh_list_remove(algo_list, it); + + it = ssh_list_get_iterator(algo_list); + assert_non_null(it); + algo = ssh_iterator_value(const char *, it); + assert_string_equal(algo, "ssh-ed25519"); + + ssh_list_remove(algo_list, it); + + it = ssh_list_get_iterator(algo_list); + assert_non_null(it); + algo = ssh_iterator_value(const char *, it); + assert_string_equal(algo, "ecdsa-sha2-nistp521"); + + ssh_list_free(algo_list); +} + +static void torture_knownhosts_duplicate(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + struct ssh_list *algo_list = NULL; + struct ssh_iterator *it = NULL; + size_t algo_count; + const char *algo = NULL; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + free(known_hosts_file); + + algo_list = ssh_known_hosts_get_algorithms(session); + assert_non_null(algo_list); + + algo_count = ssh_list_count(algo_list); + assert_int_equal(algo_count, 1); + + it = ssh_list_get_iterator(algo_list); + assert_non_null(it); + algo = ssh_iterator_value(const char *, it); + assert_string_equal(algo, "ssh-rsa"); + + ssh_list_free(algo_list); +} + +static void torture_knownhosts_other(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + enum ssh_known_hosts_e found; + FILE *file = NULL; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + fclose(file); + free(known_hosts_file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OTHER); +} + +static void torture_knownhosts_unknown(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + enum ssh_known_hosts_e found; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_UNKNOWN); + + rc = ssh_session_update_known_hosts(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + ssh_free(session); + + /* connect again and check host key */ + session = ssh_new(); + assert_non_null(session); + + s->ssh.session = session; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* ssh-rsa is the default but libssh should try ssh-ed25519 instead */ + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* session will be freed by session_teardown() */ + free(known_hosts_file); +} + +static void torture_knownhosts_conflict(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + enum ssh_known_hosts_e found; + FILE *file = NULL; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, + "127.0.0.10 %s %s\n", + "ssh-rsa", + BAD_RSA); + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_CHANGED); + + rc = ssh_session_update_known_hosts(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + ssh_free(session); + + /* connect again and check host key */ + session = ssh_new(); + assert_non_null(session); + + s->ssh.session = session; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* session will be freed by session_teardown() */ + free(known_hosts_file); +} + +static void torture_knownhosts_new_file(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + enum ssh_known_hosts_e found; + int rc; + + char new_known_hosts[256]; + char *tmp_dir = NULL; + ssize_t count = 0; + + /* Create a disposable directory */ + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + count = snprintf(new_known_hosts, sizeof(new_known_hosts), + "%s/a/b/c/d/known_hosts", tmp_dir); + assert_return_code(count, errno); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, new_known_hosts); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_session_update_known_hosts(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* Cleanup */ + torture_rmdirs(tmp_dir); + + SAFE_FREE(tmp_dir); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_knownhosts_export, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_write_and_verify, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_precheck, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_other, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_unknown, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_conflict, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_duplicate, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_new_file, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_group_setup, sshd_group_teardown); + + ssh_finalize(); + return rc; +} diff --git a/tests/client/torture_proxycommand.c b/tests/client/torture_proxycommand.c new file mode 100644 index 0000000..77dd8ad --- /dev/null +++ b/tests/client/torture_proxycommand.c @@ -0,0 +1,178 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#include "libssh/priv.h" + +#include +#include +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_options_set_proxycommand(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *address = torture_server_address(AF_INET); + int port = torture_server_port(); + char command[255] = {0}; + struct stat sb; + int rc; + socket_t fd; + + rc = stat("/bin/nc", &sb); + if (rc != 0 || (sb.st_mode & S_IXOTH) == 0) { + SSH_LOG(SSH_LOG_WARNING, "Could not find /bin/nc: Skipping the test"); + skip(); + } + + rc = snprintf(command, sizeof(command), "/bin/nc %s %d", address, port); + assert_true((size_t)rc < sizeof(command)); + + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, command); + assert_int_equal(rc, 0); + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + fd = ssh_get_fd(session); + assert_true(fd != SSH_INVALID_SOCKET); + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); +} + +static void torture_options_set_proxycommand_notexist(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, "this_command_does_not_exist"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); +} + +static void torture_options_set_proxycommand_ssh(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *address = torture_server_address(AF_INET); + char command[255] = {0}; + int rc; + socket_t fd; + + rc = snprintf(command, sizeof(command), + "ssh -oStrictHostKeyChecking=no -W [%%h]:%%p alice@%s", + address); + assert_true((size_t)rc < sizeof(command)); + + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, command); + assert_int_equal(rc, 0); + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + fd = ssh_get_fd(session); + assert_true(fd != SSH_INVALID_SOCKET); + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); +} + +static void torture_options_set_proxycommand_ssh_stderr(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *address = torture_server_address(AF_INET); + char command[255] = {0}; + int rc; + socket_t fd; + + /* The -vvv switches produce the desired output on the standard error */ + rc = snprintf(command, sizeof(command), + "ssh -vvv -oStrictHostKeyChecking=no -W [%%h]:%%p alice@%s", + address); + assert_true((size_t)rc < sizeof(command)); + + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, command); + assert_int_equal(rc, 0); + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + fd = ssh_get_fd(session); + assert_true(fd != SSH_INVALID_SOCKET); + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_options_set_proxycommand, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_options_set_proxycommand_notexist, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_options_set_proxycommand_ssh, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_options_set_proxycommand_ssh_stderr, + session_setup, + session_teardown), + }; + + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/tests/client/torture_rekey.c b/tests/client/torture_rekey.c new file mode 100644 index 0000000..7c9d812 --- /dev/null +++ b/tests/client/torture_rekey.c @@ -0,0 +1,545 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Authors: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/sftp.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/crypto.h" + +#include +#include +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool b = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + /* Authenticate as alice with bob's pubkey */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + /* Make sure we do not interfere with another ssh-agent */ + unsetenv("SSH_AUTH_SOCK"); + unsetenv("SSH_AGENT_PID"); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_free(s->ssh.session); + + return 0; +} + +/* Check that the default limits for rekeying are enforced. + * the limits are too high for testsuite to verify so + * we should be fine with checking the values in internal + * structures + */ +static void torture_rekey_default(void **state) +{ + struct torture_state *s = *state; + int rc; + struct ssh_crypto_struct *c = NULL; + + /* Define preferred ciphers: */ + if (ssh_fips_mode()) { + /* We do not have any FIPS allowed cipher with different block size */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_CIPHERS_C_S, + "aes128-gcm@openssh.com"); + } else { + /* (out) C->S has 8B block */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_CIPHERS_C_S, + "chacha20-poly1305@openssh.com"); + } + assert_ssh_return_code(s->ssh.session, rc); + /* (in) S->C has 16B block */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_CIPHERS_S_C, + "aes128-cbc"); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + c = s->ssh.session->current_crypto; + /* The blocks limit is set correctly */ + /* For S->C (in) we have 16B block => 2**(L/4) blocks */ + assert_int_equal(c->in_cipher->max_blocks, + (uint64_t)1 << (2 * c->in_cipher->blocksize)); + if (ssh_fips_mode()) { + /* We do not have any FIPS allowed cipher with different block size */ + assert_int_equal(c->in_cipher->max_blocks, + (uint64_t)1 << (2 * c->in_cipher->blocksize)); + } else { + /* The C->S (out) we have 8B block => 1 GB limit */ + assert_int_equal(c->out_cipher->max_blocks, + ((uint64_t)1 << 30) / c->out_cipher->blocksize); + } + + ssh_disconnect(s->ssh.session); +} + +/* We lower the rekey limits manually and check that the rekey + * really happens when sending data + */ +static void torture_rekey_send(void **state) +{ + struct torture_state *s = *state; + int rc; + char data[256]; + unsigned int i; + uint64_t bytes = 2048; /* 2KB (more than the authentication phase) */ + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + /* The blocks limit is set correctly */ + c = s->ssh.session->current_crypto; + assert_int_equal(c->in_cipher->max_blocks, + bytes / c->in_cipher->blocksize); + assert_int_equal(c->out_cipher->max_blocks, + bytes / c->out_cipher->blocksize); + /* We should have less encrypted packets than transfered (first are not encrypted) */ + assert_true(c->out_cipher->packets < s->ssh.session->send_seq); + assert_true(c->in_cipher->packets < s->ssh.session->recv_seq); + /* Copy the initial secret hash = session_id so we know we changed keys later */ + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + + /* OpenSSH can not rekey before authentication so authenticate here */ + rc = ssh_userauth_none(s->ssh.session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(s->ssh.session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* send ignore packets of up to 1KB to trigger rekey */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 128); + for (i = 0; i < 16; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 50); + } + + /* The rekey limit was restored in the new crypto to the same value */ + c = s->ssh.session->current_crypto; + assert_int_equal(c->in_cipher->max_blocks, bytes / c->in_cipher->blocksize); + assert_int_equal(c->out_cipher->max_blocks, bytes / c->out_cipher->blocksize); + /* Check that the secret hash is different than initially */ + assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); + free(secret_hash); + + ssh_disconnect(s->ssh.session); +} + +#ifdef WITH_SFTP +static void session_setup_sftp(void **state) +{ + struct torture_state *s = *state; + int rc; + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + /* OpenSSH can not rekey before authentication so authenticate here */ + rc = ssh_userauth_none(s->ssh.session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(s->ssh.session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Initialize SFTP session */ + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); +} + +uint64_t bytes = 2048; /* 2KB */ + +static int session_setup_sftp_client(void **state) +{ + struct torture_state *s = *state; + int rc; + + session_setup(state); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); + assert_ssh_return_code(s->ssh.session, rc); + + session_setup_sftp(state); + + return 0; +} + +#define MAX_XFER_BUF_SIZE 16384 + +/* To trigger rekey by receiving data, the easiest thing is probably to + * use sftp + */ +static void torture_rekey_recv(void **state) +{ + struct torture_state *s = *state; + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + + char libssh_tmp_file[] = "/tmp/libssh_sftp_test_XXXXXX"; + char buf[MAX_XFER_BUF_SIZE]; + ssize_t bytesread; + ssize_t byteswritten; + int fd; + sftp_file file; + mode_t mask; + + /* The blocks limit is set correctly */ + c = s->ssh.session->current_crypto; + assert_int_equal(c->in_cipher->max_blocks, bytes / c->in_cipher->blocksize); + assert_int_equal(c->out_cipher->max_blocks, bytes / c->out_cipher->blocksize); + /* We should have less encrypted packets than transfered (first are not encrypted) */ + assert_true(c->out_cipher->packets < s->ssh.session->send_seq); + assert_true(c->in_cipher->packets < s->ssh.session->recv_seq); + /* Copy the initial secret hash = session_id so we know we changed keys later */ + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + + /* Download a file */ + file = sftp_open(s->ssh.tsftp->sftp, "/usr/bin/ssh", O_RDONLY, 0); + assert_non_null(file); + + mask = umask(S_IRWXO | S_IRWXG); + fd = mkstemp(libssh_tmp_file); + umask(mask); + unlink(libssh_tmp_file); + + for (;;) { + bytesread = sftp_read(file, buf, MAX_XFER_BUF_SIZE); + if (bytesread == 0) { + break; /* EOF */ + } + assert_false(bytesread < 0); + + byteswritten = write(fd, buf, bytesread); + assert_int_equal(byteswritten, bytesread); + } + + close(fd); + + /* The rekey limit was restored in the new crypto to the same value */ + c = s->ssh.session->current_crypto; + assert_int_equal(c->in_cipher->max_blocks, bytes / c->in_cipher->blocksize); + assert_int_equal(c->out_cipher->max_blocks, bytes / c->out_cipher->blocksize); + /* Check that the secret hash is different than initially */ + assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); + free(secret_hash); + + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); +} +#endif /* WITH_SFTP */ + +/* Rekey time requires rekey after specified time and is off by default. + * Setting the time to small enough value and waiting, we should trigger + * rekey on the first sent packet afterward. + */ +static void torture_rekey_time(void **state) +{ + struct torture_state *s = *state; + int rc; + char data[256]; + unsigned int i; + uint32_t time = 3; /* 3 seconds */ + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_TIME, &time); + assert_ssh_return_code(s->ssh.session, rc); + /* The time is internally stored in microseconds */ + assert_int_equal(time * 1000, s->ssh.session->opts.rekey_time); + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + + /* OpenSSH can not rekey before authentication so authenticate here */ + rc = ssh_userauth_none(s->ssh.session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(s->ssh.session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Send some data. This should not trigger rekey yet */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 8); + for (i = 0; i < 3; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 50); + } + + /* Check that the secret hash is the same */ + c = s->ssh.session->current_crypto; + assert_memory_equal(secret_hash, c->secret_hash, c->digest_len); + + /* Wait some more time */ + sleep(3); + + /* send some more data to trigger rekey and handle the + * key exchange "in background" */ + for (i = 0; i < 8; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 50); + } + + /* Check that the secret hash is different than initially */ + c = s->ssh.session->current_crypto; + assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); + free(secret_hash); + + ssh_disconnect(s->ssh.session); +} + +/* We lower the rekey limits manually and check that the rekey + * really happens when sending data + */ +static void torture_rekey_server_send(void **state) +{ + struct torture_state *s = *state; + int rc; + char data[256]; + unsigned int i; + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + const char *sshd_config = "RekeyLimit 2K none"; + + torture_update_sshd_config(state, sshd_config); + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + + /* OpenSSH can not rekey before authentication so authenticate here */ + rc = ssh_userauth_none(s->ssh.session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(s->ssh.session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* send ignore packets of up to 1KB to trigger rekey */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 128); + for (i = 0; i < 20; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 50); + } + + /* Check that the secret hash is different than initially */ + c = s->ssh.session->current_crypto; + assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); + free(secret_hash); + + ssh_disconnect(s->ssh.session); +} + +#ifdef WITH_SFTP +static int session_setup_sftp_server(void **state) +{ + const char *sshd_config = "RekeyLimit 2K none"; + + session_setup(state); + + torture_update_sshd_config(state, sshd_config); + + session_setup_sftp(state); + + return 0; +} + +static void torture_rekey_server_recv(void **state) +{ + struct torture_state *s = *state; + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + char libssh_tmp_file[] = "/tmp/libssh_sftp_test_XXXXXX"; + char buf[MAX_XFER_BUF_SIZE]; + ssize_t bytesread; + ssize_t byteswritten; + int fd; + sftp_file file; + mode_t mask; + + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + + /* Download a file */ + file = sftp_open(s->ssh.tsftp->sftp, "/usr/bin/ssh", O_RDONLY, 0); + assert_non_null(file); + + mask = umask(S_IRWXO | S_IRWXG); + fd = mkstemp(libssh_tmp_file); + umask(mask); + unlink(libssh_tmp_file); + + for (;;) { + bytesread = sftp_read(file, buf, MAX_XFER_BUF_SIZE); + if (bytesread == 0) { + break; /* EOF */ + } + assert_false(bytesread < 0); + + byteswritten = write(fd, buf, bytesread); + assert_int_equal(byteswritten, bytesread); + } + + close(fd); + + /* Check that the secret hash is different than initially */ + c = s->ssh.session->current_crypto; + assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); + free(secret_hash); + + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); +} +#endif /* WITH_SFTP */ + + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_rekey_default, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_rekey_time, + session_setup, + session_teardown), +#ifdef WITH_SFTP + cmocka_unit_test_setup_teardown(torture_rekey_recv, + session_setup_sftp_client, + session_teardown), +#endif /* WITH_SFTP */ + cmocka_unit_test_setup_teardown(torture_rekey_send, + session_setup, + session_teardown), + /* Note, that this modifies the sshd_config */ + cmocka_unit_test_setup_teardown(torture_rekey_server_send, + session_setup, + session_teardown), +#ifdef WITH_SFTP + cmocka_unit_test_setup_teardown(torture_rekey_server_recv, + session_setup_sftp_server, + session_teardown), +#endif /* WITH_SFTP */ + /* TODO verify the two rekey are possible and the states are not broken after rekey */ + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/tests/client/torture_request_env.c b/tests/client/torture_request_env.c new file mode 100644 index 0000000..21806df --- /dev/null +++ b/tests/client/torture_request_env.c @@ -0,0 +1,137 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_request_env(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + char buffer[4096] = {0}; + int nbytes; + int rc; + int lang_found = 0; + + c = ssh_channel_new(session); + assert_non_null(c); + + rc = ssh_channel_open_session(c); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_env(c, "LC_LIBSSH", "LIBSSH_EXPORTED_VARIABLE"); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(c, "echo $LC_LIBSSH"); + assert_ssh_return_code(session, rc); + + nbytes = ssh_channel_read(c, buffer, sizeof(buffer) - 1, 0); + printf("nbytes=%d\n", nbytes); + while (nbytes > 0) { +#if 1 + rc = fwrite(buffer, 1, nbytes, stdout); + assert_int_equal(rc, nbytes); +#endif + buffer[nbytes]='\0'; + if (strstr(buffer, "LIBSSH_EXPORTED_VARIABLE")) { + lang_found = 1; + break; + } + + nbytes = ssh_channel_read(c, buffer, sizeof(buffer), 0); + } + assert_int_equal(lang_found, 1); + + ssh_channel_close(c); +} + +int torture_run_tests(void) { + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_request_env, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} + diff --git a/tests/client/torture_scp.c b/tests/client/torture_scp.c new file mode 100644 index 0000000..8f080af --- /dev/null +++ b/tests/client/torture_scp.c @@ -0,0 +1,571 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * 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. + */ + +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "libssh/scp.h" + +#include +#include +#include +#include +#include + +#define BUF_SIZE 1024 + +#define TEMPLATE BINARYDIR "/tests/home/alice/temp_dir_XXXXXX" + +struct scp_st { + struct torture_state *s; + char *tmp_dir; + char *tmp_dir_basename; +}; + +static int sshd_setup(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ts = (struct scp_st *)calloc(1, sizeof(struct scp_st)); + assert_non_null(ts); + + torture_setup_sshd_server((void **)&s, false); + assert_non_null(s); + + ts->s = s; + + *state = ts; + + return 0; +} + +static int sshd_teardown(void **state) +{ + struct scp_st *ts = NULL; + + ts = *((struct scp_st **)state); + assert_non_null(ts); + assert_non_null(ts->s); + + torture_teardown_sshd_server((void **)&(ts->s)); + + SAFE_FREE(ts); + + return 0; +} + +static int session_setup(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + char *tmp_dir = NULL; + char *tmp_dir_basename = NULL; + + struct passwd *pwd; + + int rc; + + assert_non_null(state); + + ts = *state; + + assert_non_null(ts); + assert_non_null(ts->s); + + s = ts->s; + + /* Create temporary directory for alice */ + tmp_dir = torture_make_temp_dir(TEMPLATE); + assert_non_null(tmp_dir); + ts->tmp_dir = tmp_dir; + + tmp_dir_basename = ssh_basename(tmp_dir); + assert_non_null(tmp_dir_basename); + ts->tmp_dir_basename = tmp_dir_basename; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + return 0; +} + +static int session_teardown(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + assert_non_null(state); + ts = *((struct scp_st **)state); + + assert_non_null(ts->s); + s = ts->s; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + assert_non_null(ts->tmp_dir); + torture_rmdirs(ts->tmp_dir); + + SAFE_FREE(ts->tmp_dir); + SAFE_FREE(ts->tmp_dir_basename); + + return 0; +} + +static void torture_scp_upload(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ssh_session session = NULL; + ssh_scp scp = NULL; + + char expected_a[BUF_SIZE]; + char buf[BUF_SIZE]; + FILE *file = NULL; + size_t len = 0; + int rc; + + assert_non_null(state); + ts = *state; + + assert_non_null(ts->s); + s = ts->s; + + session = s->ssh.session; + assert_non_null(session); + + assert_non_null(ts->tmp_dir_basename); + assert_non_null(ts->tmp_dir); + + /* Upload file "a" to alice's temp dir */ + + /* When writing the file_name must be the directory name */ + scp = ssh_scp_new(session, SSH_SCP_WRITE, ts->tmp_dir_basename); + assert_non_null(scp); + + rc = ssh_scp_init(scp); + assert_ssh_return_code(session, rc); + + /* Init buffer content to be written */ + memset(expected_a, 'A', BUF_SIZE); + + /* For ssh_scp_push_file(), the file_name is the name of the file without + * path */ + rc = ssh_scp_push_file(scp, "a", BUF_SIZE, 0644); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_write(scp, expected_a, BUF_SIZE); + assert_ssh_return_code(session, rc); + + /* Cleanup */ + ssh_scp_close(scp); + ssh_scp_free(scp); + + /* Open file and check content */ + snprintf(buf, BUF_SIZE, "%s/a", ts->tmp_dir); + + file = fopen(buf, "r"); + assert_non_null(file); + + len = fread(buf, BUF_SIZE, 1, file); + assert_int_equal(len, 1); + assert_memory_equal(buf, expected_a, BUF_SIZE); + + fclose(file); +} + +static void torture_scp_upload_recursive(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ssh_session session = NULL; + ssh_scp scp = NULL; + + char expected_b[BUF_SIZE]; + char buf[BUF_SIZE]; + FILE *file = NULL; + size_t len = 0; + + int rc; + + assert_non_null(state); + ts = *state; + + assert_non_null(ts->s); + s = ts->s; + + session = s->ssh.session; + assert_non_null(session); + + assert_non_null(ts->tmp_dir_basename); + assert_non_null(ts->tmp_dir); + + /* Upload directory "test_dir" containing file "b" to alice's temp dir */ + + /* When writing the file_name must be the directory name */ + scp = ssh_scp_new(session, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, + ts->tmp_dir_basename); + assert_non_null(scp); + + rc = ssh_scp_init(scp); + assert_ssh_return_code(session, rc); + + /* Push directory where the new file will be copied */ + rc = ssh_scp_push_directory(scp, "test_dir", 0755); + assert_ssh_return_code(session, rc); + + memset(expected_b, 'B', BUF_SIZE); + + /* For ssh_scp_push_file(), the file_name is the name of the file without + * path */ + rc = ssh_scp_push_file(scp, "b", BUF_SIZE, 0644); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_write(scp, expected_b, BUF_SIZE); + assert_ssh_return_code(session, rc); + + /* Leave the directory */ + rc = ssh_scp_leave_directory(scp); + assert_ssh_return_code(session, rc); + + /* Cleanup */ + ssh_scp_close(scp); + ssh_scp_free(scp); + + /* Open file and check content */ + snprintf(buf, BUF_SIZE, "%s/test_dir/b", ts->tmp_dir); + + file = fopen(buf, "r"); + assert_non_null(file); + + len = fread(buf, BUF_SIZE, 1, file); + assert_int_equal(len, 1); + assert_memory_equal(buf, expected_b, BUF_SIZE); + + fclose(file); +} + +static void torture_scp_download(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ssh_session session = NULL; + ssh_scp scp = NULL; + + char expected_a[BUF_SIZE]; + char buf[BUF_SIZE]; + const char *remote_file = NULL; + + FILE *file = NULL; + int fd = 0; + + size_t size; + + int mode; + int rc; + + assert_non_null(state); + + ts = *state; + + assert_non_null(ts); + assert_non_null(ts->s); + + s = ts->s; + + session = s->ssh.session; + assert_non_null(session); + + assert_non_null(ts->tmp_dir_basename); + assert_non_null(ts->tmp_dir); + + /* Create file "a" for alice */ + memset(expected_a, 'A', BUF_SIZE); + + snprintf(buf, BUF_SIZE, "%s/a", ts->tmp_dir); + + fd = open(buf, O_WRONLY | O_CREAT, 0644); + assert_true(fd > 0); + + file = fdopen(fd, "w"); + assert_non_null(file); + + size = fwrite(expected_a, 1, BUF_SIZE, file); + assert_int_equal(size, BUF_SIZE); + fclose(file); + + /* Construct the file path */ + snprintf(buf, BUF_SIZE, "%s/a", ts->tmp_dir_basename); + + /* When reading, the location is the file path */ + scp = ssh_scp_new(session, SSH_SCP_READ, buf); + assert_non_null(scp); + + rc = ssh_scp_init(scp); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_pull_request(scp); + assert_int_equal(rc, SSH_SCP_REQUEST_NEWFILE); + + size = ssh_scp_request_get_size(scp); + assert_int_equal(size, BUF_SIZE); + + mode = ssh_scp_request_get_permissions(scp); + assert_int_equal(mode, 0644); + + remote_file = ssh_scp_request_get_filename(scp); + assert_non_null(remote_file); + assert_string_equal(remote_file, "a"); + + rc = ssh_scp_accept_request(scp); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_read(scp, buf, BUF_SIZE); + assert_int_equal(rc, size); + + assert_memory_equal(expected_a, buf, BUF_SIZE); + + /* Cleanup */ + ssh_scp_close(scp); + ssh_scp_free(scp); +} + +static void torture_scp_download_recursive(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ssh_session session = NULL; + ssh_scp scp = NULL; + + char expected_b[BUF_SIZE]; + char buf[BUF_SIZE]; + const char *remote_file = NULL; + FILE *file = NULL; + int fd = 0; + + size_t size; + + int mode; + int rc; + + assert_non_null(state); + ts = *state; + + assert_non_null(ts->s); + s = ts->s; + + session = s->ssh.session; + assert_non_null(session); + + assert_non_null(ts->tmp_dir_basename); + assert_non_null(ts->tmp_dir); + + /* Create file "b" for alice */ + memset(expected_b, 'B', BUF_SIZE); + + snprintf(buf, BUF_SIZE, "%s/b", ts->tmp_dir); + + fd = open(buf, O_WRONLY | O_CREAT, 0644); + assert_true(fd > 0); + + file = fdopen(fd, "w"); + assert_non_null(file); + + size = fwrite(expected_b, 1, BUF_SIZE, file); + assert_int_equal(size, BUF_SIZE); + fclose(file); + + /* Copy the directory containing the file "b" */ + scp = ssh_scp_new(session, SSH_SCP_READ | SSH_SCP_RECURSIVE, + ts->tmp_dir_basename); + assert_non_null(scp); + + rc = ssh_scp_init(scp); + assert_ssh_return_code(session, rc); + + /* Receive the directory */ + rc = ssh_scp_pull_request(scp); + assert_int_equal(rc, SSH_SCP_REQUEST_NEWDIR); + + mode = ssh_scp_request_get_permissions(scp); + assert_int_equal(mode, 0700); + + remote_file = ssh_scp_request_get_filename(scp); + assert_non_null(remote_file); + assert_string_equal(remote_file, ts->tmp_dir_basename); + + rc = ssh_scp_accept_request(scp); + assert_ssh_return_code(session, rc); + + /* Receive the file "b" */ + rc = ssh_scp_pull_request(scp); + assert_int_equal(rc, SSH_SCP_REQUEST_NEWFILE); + + size = ssh_scp_request_get_size(scp); + assert_int_equal(size, BUF_SIZE); + + mode = ssh_scp_request_get_permissions(scp); + assert_int_equal(mode, 0644); + + remote_file = ssh_scp_request_get_filename(scp); + assert_non_null(remote_file); + assert_string_equal(remote_file, "b"); + + rc = ssh_scp_accept_request(scp); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_read(scp, buf, BUF_SIZE); + assert_int_equal(rc, size); + + /* Check if the content was the expected */ + assert_memory_equal(expected_b, buf, BUF_SIZE); + + /* Receive end of directory */ + rc = ssh_scp_pull_request(scp); + assert_int_equal(rc, SSH_SCP_REQUEST_ENDDIR); + + /* Receive end of communication */ + rc = ssh_scp_pull_request(scp); + assert_int_equal(rc, SSH_SCP_REQUEST_EOF); + + /* Cleanup */ + ssh_scp_close(scp); + ssh_scp_free(scp); +} + +static void torture_scp_upload_newline(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ssh_session session = NULL; + ssh_scp scp = NULL; + + FILE *file = NULL; + + char buf[1024]; + char *rs = NULL; + int rc; + + assert_non_null(state); + ts = *state; + + assert_non_null(ts->s); + s = ts->s; + + session = s->ssh.session; + assert_non_null(session); + + assert_non_null(ts->tmp_dir_basename); + assert_non_null(ts->tmp_dir); + + /* Upload recursively trying to inject protocol messages */ + + /* When writing the file_name must be the directory name */ + scp = ssh_scp_new(session, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, + ts->tmp_dir_basename); + assert_non_null(scp); + + rc = ssh_scp_init(scp); + assert_ssh_return_code(session, rc); + + /* Push directory where the new file will be copied */ + rc = ssh_scp_push_directory(scp, "test_inject", 0755); + assert_ssh_return_code(session, rc); + + /* Try to push file with injected protocol messages */ + rc = ssh_scp_push_file(scp, "original\nreplacedC0777 8 injected", 8, 0644); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_write(scp, "original", 8); + assert_ssh_return_code(session, rc); + + /* Leave the directory */ + rc = ssh_scp_leave_directory(scp); + assert_ssh_return_code(session, rc); + + /* Cleanup */ + ssh_scp_close(scp); + ssh_scp_free(scp); + + /* Open the file and check content */ + snprintf(buf, BUF_SIZE, "%s/test_inject/" + "original\\nreplacedC0777 8 injected", + ts->tmp_dir); + file = fopen(buf, "r"); + assert_non_null(file); + + rs = fgets(buf, 1024, file); + assert_non_null(rs); + assert_string_equal(buf, "original"); + + fclose(file); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_scp_upload, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_scp_upload_recursive, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_scp_download, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_scp_download_recursive, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_scp_upload_newline, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/tests/client/torture_session.c b/tests/client/torture_session.c new file mode 100644 index 0000000..b5ed7a6 --- /dev/null +++ b/tests/client/torture_session.c @@ -0,0 +1,136 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2012 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" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +#define BUFLEN 4096 +static char buffer[BUFLEN]; + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_channel_read_error(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + int fd; + int i; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(channel, "hexdump -C /dev/urandom"); + assert_ssh_return_code(session, rc); + + /* send crap and for server to send us a disconnect */ + fd = ssh_get_fd(session); + assert_true(fd > 2); + rc = write(fd, "AAAA", 4); + assert_int_equal(rc, 4); + + for (i=0;i<20;++i){ + rc = ssh_channel_read(channel,buffer,sizeof(buffer),0); + if (rc == SSH_ERROR) + break; + } +#if OPENSSH_VERSION_MAJOR == 6 && OPENSSH_VERSION_MINOR >= 7 + /* With openssh 6.7 this doesn't produce and error anymore */ + assert_ssh_return_code(session, rc); +#else + assert_ssh_return_code_equal(session, rc, SSH_ERROR); +#endif + + ssh_channel_free(channel); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_channel_read_error, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/tests/client/torture_sftp_benchmark.c b/tests/client/torture_sftp_benchmark.c new file mode 100644 index 0000000..6b2bea5 --- /dev/null +++ b/tests/client/torture_sftp_benchmark.c @@ -0,0 +1,133 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +#define MAX_XFER_BUF_SIZE 16384 + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_benchmark_write_read(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + ssh_session session = s->ssh.session; + sftp_file file = NULL; + struct stat sb = { + .st_size = 0, + }; + uint8_t buf_16k[MAX_XFER_BUF_SIZE]; + char local_path[1024] = {0}; + ssize_t bwritten, nread; + size_t i; + int rc; + + memset(buf_16k, 'X', sizeof(buf_16k)); + + snprintf(local_path, sizeof(local_path), "%s/128M.dat", t->testdir); + + file = sftp_open(sftp, local_path, O_CREAT|O_WRONLY|O_TRUNC, 0644); + assert_non_null(file); + + /* Write 128M */ + for (i = 0; i < 0x2000; i++) { + bwritten = sftp_write(file, buf_16k, sizeof(buf_16k)); + assert_int_equal(bwritten, sizeof(buf_16k)); + } + + rc = sftp_close(file); + assert_ssh_return_code(session, rc); + + /* Check that 128M has been written */ + rc = stat(local_path, &sb); + assert_int_equal(sb.st_size, 0x8000000); + + file = sftp_open(sftp, local_path, O_RDONLY, 0); + assert_non_null(file); + + for (;;) { + nread = sftp_read(file, buf_16k, sizeof(buf_16k)); + if (nread == 0) { + break; /* EOF */ + } + assert_int_equal(nread, sizeof(buf_16k)); + } + + rc = sftp_close(file); + assert_ssh_return_code(session, rc); + + unlink(local_path); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_benchmark_write_read, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/tests/client/torture_sftp_canonicalize_path.c b/tests/client/torture_sftp_canonicalize_path.c new file mode 100644 index 0000000..8d5f907 --- /dev/null +++ b/tests/client/torture_sftp_canonicalize_path.c @@ -0,0 +1,97 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_canonicalize_path(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *pwd = NULL; + char *canonicalized_path = NULL; + + pwd = getpwnam(TORTURE_SSH_USER_ALICE); + assert_non_null(pwd); + + canonicalized_path = sftp_canonicalize_path(t->sftp, "."); + assert_non_null(canonicalized_path); + + assert_string_equal(canonicalized_path, pwd->pw_dir); + + SSH_STRING_FREE_CHAR(canonicalized_path); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_canonicalize_path, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} + diff --git a/tests/client/torture_sftp_dir.c b/tests/client/torture_sftp_dir.c new file mode 100644 index 0000000..1c4f839 --- /dev/null +++ b/tests/client/torture_sftp_dir.c @@ -0,0 +1,104 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_mkdir(void **state) { + struct torture_state *s = *state; + + struct torture_sftp *t = s->ssh.tsftp; + char tmpdir[128] = {0}; + int rc; + + assert_non_null(t); + + snprintf(tmpdir, sizeof(tmpdir) - 1, "%s/mkdir_test", t->testdir); + + rc = sftp_mkdir(t->sftp, tmpdir, 0755); + if(rc != SSH_OK) + fprintf(stderr,"error:%s\n",ssh_get_error(t->sftp->session)); + assert_true(rc == 0); + + /* check if it really has been created */ + assert_true(torture_isdir(tmpdir)); + + rc = sftp_rmdir(t->sftp, tmpdir); + assert_true(rc == 0); + + /* check if it has been deleted */ + assert_false(torture_isdir(tmpdir)); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_mkdir, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/tests/client/torture_sftp_ext.c b/tests/client/torture_sftp_ext.c new file mode 100644 index 0000000..ad4bbaa --- /dev/null +++ b/tests/client/torture_sftp_ext.c @@ -0,0 +1,35 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +static void torture_sftp_ext_new(void **state) { + sftp_ext x; + + (void) state; + + x = sftp_ext_new(); + assert_non_null(x); + assert_int_equal(x->count, 0); + assert_null(x->name); + assert_null(x->data); + + sftp_ext_free(x); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_sftp_ext_new), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/tests/client/torture_sftp_fsync.c b/tests/client/torture_sftp_fsync.c new file mode 100644 index 0000000..ac685b9 --- /dev/null +++ b/tests/client/torture_sftp_fsync.c @@ -0,0 +1,137 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +#define MAX_XFER_BUF_SIZE 16384 + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_fsync(void **state) { + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char libssh_tmp_file[] = "/tmp/libssh_sftp_test_XXXXXX"; + char buf[MAX_XFER_BUF_SIZE] = {0}; + char buf_verify[MAX_XFER_BUF_SIZE] = {0}; + size_t count; + size_t bytesread; + ssize_t byteswritten; + int fd; + sftp_file file; + mode_t mask; + int rc; + FILE *fp; + struct stat sb; + + mask = umask(S_IRWXO | S_IRWXG); + fd = mkstemp(libssh_tmp_file); + umask(mask); + assert_return_code(fd, errno); + close(fd); + unlink(libssh_tmp_file); + + file = sftp_open(t->sftp, libssh_tmp_file, O_WRONLY | O_CREAT, 0600); + assert_non_null(file); + + rc = lstat(libssh_tmp_file, &sb); + assert_return_code(rc, errno); + + snprintf(buf, sizeof(buf), "libssh fsync test\n"); + count = strlen(buf) + 1; + + byteswritten = sftp_write(file, buf, count); + assert_int_equal(byteswritten, count); + + rc = sftp_fsync(file); + assert_return_code(rc, errno); + + fp = fopen(libssh_tmp_file, "r"); + assert_non_null(fp); + + rc = fstat(fileno(fp), &sb); + assert_return_code(rc, errno); + + bytesread = fread(buf_verify, sizeof(buf_verify), 1, fp); + if (bytesread == 0) { + if (!feof(fp)) { + assert_int_equal(bytesread, count); + } + } + assert_string_equal(buf, buf_verify); + + sftp_close(file); + fclose(fp); + unlink(libssh_tmp_file); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_fsync, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/tests/client/torture_sftp_read.c b/tests/client/torture_sftp_read.c new file mode 100644 index 0000000..2208fba --- /dev/null +++ b/tests/client/torture_sftp_read.c @@ -0,0 +1,121 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +#define MAX_XFER_BUF_SIZE 16384 + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_read_blocking(void **state) { + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char libssh_tmp_file[] = "/tmp/libssh_sftp_test_XXXXXX"; + char buf[MAX_XFER_BUF_SIZE]; + ssize_t bytesread; + ssize_t byteswritten; + int fd; + sftp_file file; + mode_t mask; + + file = sftp_open(t->sftp, "/usr/bin/ssh", O_RDONLY, 0); + assert_non_null(file); + + mask = umask(S_IRWXO | S_IRWXG); + fd = mkstemp(libssh_tmp_file); + umask(mask); + unlink(libssh_tmp_file); + + for (;;) { + bytesread = sftp_read(file, buf, MAX_XFER_BUF_SIZE); + if (bytesread == 0) { + break; /* EOF */ + } + assert_false(bytesread < 0); + + byteswritten = write(fd, buf, bytesread); + assert_int_equal(byteswritten, bytesread); + } + + close(fd); + sftp_close(file); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + /* This test is intentionally running twice to trigger a bug in OpenSSH + * or in pam_wrapper, causing the second invocation to fail. + * See: https://bugs.libssh.org/T122 + */ + cmocka_unit_test_setup_teardown(torture_sftp_read_blocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_read_blocking, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/tests/cmdline.c b/tests/cmdline.c new file mode 100644 index 0000000..ad58af7 --- /dev/null +++ b/tests/cmdline.c @@ -0,0 +1,72 @@ +#include "config.h" +#include "torture.h" + +#ifdef HAVE_ARGP_H +#include + +const char *argp_program_version = "libssh test 0.2"; +const char *argp_program_bug_address = ""; + +static char **cmdline; + +/* Program documentation. */ +static char doc[] = "libssh test test"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Make libssh test more verbose", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + struct argument_s *arguments = state->input; + + /* arg is currently not used */ + (void) arg; + + switch (key) { + case 'v': + arguments->verbose++; + break; + case ARGP_KEY_ARG: + /* End processing here. */ + arguments->pattern = state->argv[state->next - 1]; + cmdline = &state->argv [state->next - 1]; + state->next = state->argc; + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +/* static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; */ +static struct argp argp = {options, parse_opt, NULL, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +void torture_cmdline_parse(int argc, char **argv, struct argument_s *arguments) { + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ +#ifdef HAVE_ARGP_H + argp_parse(&argp, argc, argv, 0, 0, arguments); +#else + (void) argc; + (void) argv; + (void) arguments; +#endif /* HAVE_ARGP_H */ +} diff --git a/tests/connection.c b/tests/connection.c new file mode 100644 index 0000000..889c511 --- /dev/null +++ b/tests/connection.c @@ -0,0 +1,31 @@ +/* +This file is distributed in public domain. You can do whatever you want +with its content. +*/ + +#include +#include +#include "tests.h" +SSH_OPTIONS *set_opts(int argc, char **argv){ + SSH_OPTIONS *options=ssh_options_new(); + char *host=NULL; + if(ssh_options_getopt(options,&argc, argv)){ + fprintf(stderr,"error parsing command line :%s\n",ssh_get_error(options)); + return NULL; + } + int i; + while((i=getopt(argc,argv,""))!=-1){ + switch(i){ + default: + fprintf(stderr,"unknown option %c\n",optopt); + } + } + if(optind < argc) + host=argv[optind++]; + if(host==NULL){ + fprintf(stderr,"must provide an host name\n"); + return NULL; + } + ssh_options_set_host(options,host); + return options; +} diff --git a/tests/ctest-default.cmake b/tests/ctest-default.cmake new file mode 100644 index 0000000..dbf53e3 --- /dev/null +++ b/tests/ctest-default.cmake @@ -0,0 +1,72 @@ +## The directory to run ctest in. +set(CTEST_DIRECTORY "$ENV{HOME}/workspace/tmp/dashboards/libssh") + +## The hostname of the machine +set(CTEST_SITE "host.libssh.org") +## The buildname +set(CTEST_BUILD_NAME "Linux_2.6-GCC_4.5-x86_64-default") + +## The Makefile generator to use +set(CTEST_CMAKE_GENERATOR "Unix Makefiles") + +## The Build configuration to use. +set(CTEST_BUILD_CONFIGURATION "Debug") + +## The build options for the project +set(CTEST_BUILD_OPTIONS "-DUNIT_TESTING=ON -WITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON -DDEBUG_CRYPTO=ON -DWITH_GCRYPT=OFF") + +#set(CTEST_CUSTOM_MEMCHECK_IGNORE torture_rand) + +## The Model to set: Nightly, Continous, Experimental +set(CTEST_MODEL "Experimental") + +## The branch +#set(CTEST_GIT_BRANCH "--branch v0-5") + +## Wether to enable memory checking. +set(WITH_MEMCHECK FALSE) + +## Wether to enable code coverage. +set(WITH_COVERAGE FALSE) + +####################################################################### + +if (WITH_COVERAGE AND NOT WIN32) + set(CTEST_BUILD_CONFIGURATION "Profiling") +endif (WITH_COVERAGE AND NOT WIN32) + +set(CTEST_SOURCE_DIRECTORY "${CTEST_DIRECTORY}/${CTEST_BUILD_NAME}/source") +set(CTEST_BINARY_DIRECTORY "${CTEST_DIRECTORY}/${CTEST_BUILD_NAME}/build") + +set(CTEST_MEMORYCHECK_SUPPRESSIONS_FILE ${CMAKE_SOURCE_DIR}/tests/valgrind.supp) +set(CTEST_MEMORYCHECK_COMMAND_OPTIONS " --trace-children-skip=${SSHD_EXECUTABLE}") + +find_program(CTEST_GIT_COMMAND NAMES git) +find_program(CTEST_COVERAGE_COMMAND NAMES gcov) +find_program(CTEST_MEMORYCHECK_COMMAND NAMES valgrind) + +if(NOT EXISTS "${CTEST_SOURCE_DIRECTORY}") + set(CTEST_CHECKOUT_COMMAND "${CTEST_GIT_COMMAND} clone ${CTEST_GIT_BRANCH} git://git.libssh.org/projects/libssh.git ${CTEST_SOURCE_DIRECTORY}") +endif() + +set(CTEST_UPDATE_COMMAND "${CTEST_GIT_COMMAND}") + +set(CTEST_CONFIGURE_COMMAND "${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE:STRING=${CTEST_BUILD_CONFIGURATION}") +set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} -DUNIT_TESTING:BOOL=ON ${CTEST_BUILD_OPTIONS}") +set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} \"-G${CTEST_CMAKE_GENERATOR}\"") +set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} \"${CTEST_SOURCE_DIRECTORY}\"") + +ctest_empty_binary_directory(${CTEST_BINARY_DIRECTORY}) + +ctest_start(${CTEST_MODEL} TRACK ${CTEST_MODEL}) +ctest_update(SOURCE ${CTEST_SOURCE_DIRECTORY}) +ctest_configure(BUILD ${CTEST_BINARY_DIRECTORY}) +ctest_build(BUILD ${CTEST_BINARY_DIRECTORY}) +ctest_test(BUILD ${CTEST_BINARY_DIRECTORY}) +if (WITH_COVERAGE) + ctest_coverage(BUILD ${CTEST_BINARY_DIRECTORY}) +endif () +if (WITH_MEMCHECK) + ctest_memcheck(BUILD ${CTEST_BINARY_DIRECTORY}) +endif () +ctest_submit() diff --git a/tests/etc/group.in b/tests/etc/group.in new file mode 100644 index 0000000..8e13294 --- /dev/null +++ b/tests/etc/group.in @@ -0,0 +1,5 @@ +users:x:9000: +sshd:x:65531: +nobody:x:65533: +nogroup:x:65534:nobody +root:x:65532: diff --git a/tests/etc/hosts.in b/tests/etc/hosts.in new file mode 100644 index 0000000..2ab6979 --- /dev/null +++ b/tests/etc/hosts.in @@ -0,0 +1,2 @@ +127.0.0.10 server.libssh.site +127.0.0.21 client.libssh.site diff --git a/tests/etc/pam.d/sshd.in b/tests/etc/pam.d/sshd.in new file mode 100644 index 0000000..57c66f9 --- /dev/null +++ b/tests/etc/pam.d/sshd.in @@ -0,0 +1,4 @@ +auth required @PAM_WRAPPER_MODULE_DIR@/pam_matrix.so passdb=@CMAKE_CURRENT_BINARY_DIR@/etc/pam_matrix_passdb +account required @PAM_WRAPPER_MODULE_DIR@/pam_matrix.so passdb=@CMAKE_CURRENT_BINARY_DIR@/etc/pam_matrix_passdb +password required @PAM_WRAPPER_MODULE_DIR@/pam_matrix.so passdb=@CMAKE_CURRENT_BINARY_DIR@/etc/pam_matrix_passdb +session required @PAM_WRAPPER_MODULE_DIR@/pam_matrix.so passdb=@CMAKE_CURRENT_BINARY_DIR@/etc/pam_matrix_passdb diff --git a/tests/etc/pam_matrix_passdb.in b/tests/etc/pam_matrix_passdb.in new file mode 100644 index 0000000..8891fcf --- /dev/null +++ b/tests/etc/pam_matrix_passdb.in @@ -0,0 +1,2 @@ +bob:secret:sshd +alice:secret:sshd diff --git a/tests/etc/passwd.in b/tests/etc/passwd.in new file mode 100644 index 0000000..a3ddc97 --- /dev/null +++ b/tests/etc/passwd.in @@ -0,0 +1,6 @@ +bob:x:5000:9000:bob gecos:@HOMEDIR@/bob:/bin/sh +alice:x:5001:9000:alice gecos:@HOMEDIR@/alice:/bin/sh +sshd:x:65530:65531:sshd:@HOMEDIR@:/sbin/nologin +nobody:x:65533:65534:nobody gecos:@HOMEDIR@:/bin/false +root:x:65534:65532:root gecos:@HOMEDIR@:/bin/false +@LOCAL_USER@:x:@LOCAL_UID@:9000:local user:@HOMEDIR@:/bin/false diff --git a/tests/etc/shadow.in b/tests/etc/shadow.in new file mode 100644 index 0000000..5c0e3d8 --- /dev/null +++ b/tests/etc/shadow.in @@ -0,0 +1,2 @@ +alice:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: +bob:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: diff --git a/tests/fuzz/CMakeLists.txt b/tests/fuzz/CMakeLists.txt new file mode 100644 index 0000000..5c1e63b --- /dev/null +++ b/tests/fuzz/CMakeLists.txt @@ -0,0 +1,9 @@ +project(fuzzing CXX) + +if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + add_executable(ssh_server_fuzzer ssh_server_fuzzer.cpp) + set_target_properties(ssh_server_fuzzer + PROPERTIES + COMPILE_FLAGS "-fsanitize=fuzzer" + LINK_FLAGS "-fsanitize=fuzzer") +endif() diff --git a/tests/fuzz/ssh_server_fuzzer.cpp b/tests/fuzz/ssh_server_fuzzer.cpp new file mode 100644 index 0000000..d6321dd --- /dev/null +++ b/tests/fuzz/ssh_server_fuzzer.cpp @@ -0,0 +1,101 @@ +/* +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ +*/ + +#include +#include +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include +#include + +static const char kRSAPrivateKeyPEM[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEowIBAAKCAQEArAOREUWlBXJAKZ5hABYyxnRayDZP1bJeLbPVK+npxemrhHyZ\n" + "gjdbY3ADot+JRyWjvll2w2GI+3blt0j+x/ZWwjMKu/QYcycYp5HL01goxOxuusZb\n" + "i+KiHRGB6z0EMdXM7U82U7lA/j//HyZppyDjUDniWabXQJge8ksGXGTiFeAJ/687\n" + "uV+JJcjGPxAGFQxzyjitf/FrL9S0WGKZbyqeGDzyeBZ1NLIuaiOORyLGSW4duHLD\n" + "N78EmsJnwqg2gJQmRSaD4BNZMjtbfiFcSL9Uw4XQFTsWugUDEY1AU4c5g11nhzHz\n" + "Bi9qMOt5DzrZQpD4j0gA2LOHpHhoOdg1ZuHrGQIDAQABAoIBAFJTaqy/jllq8vZ4\n" + "TKiD900wBvrns5HtSlHJTe80hqQoT+Sa1cWSxPR0eekL32Hjy9igbMzZ83uWzh7I\n" + "mtgNODy9vRdznfgO8CfTCaBfAzQsjFpr8QikMT6EUI/LpiRL1UaGsNOlSEvnSS0Z\n" + "b1uDzAdrjL+nsEHEDJud+K9jwSkCRifVMy7fLfaum+YKpdeEz7K2Mgm5pJ/Vg+9s\n" + "vI2V1q7HAOI4eUVTgJNHXy5ediRJlajQHf/lNUzHKqn7iH+JRl01gt62X8roG62b\n" + "TbFylbheqMm9awuSF2ucOcx+guuwhkPir8BEMb08j3hiK+TfwPdY0F6QH4OhiKK7\n" + "MTqTVgECgYEA0vmmu5GOBtwRmq6gVNCHhdLDQWaxAZqQRmRbzxVhFpbv0GjbQEF7\n" + "tttq3fjDrzDf6CE9RtZWw2BUSXVq+IXB/bXb1kgWU2xWywm+OFDk9OXQs8ui+MY7\n" + "FiP3yuq3YJob2g5CCsVQWl2CHvWGmTLhE1ODll39t7Y1uwdcDobJN+ECgYEA0LlR\n" + "hfMjydWmwqooU9TDjXNBmwufyYlNFTH351amYgFUDpNf35SMCP4hDosUw/zCTDpc\n" + "+1w04BJJfkH1SNvXSOilpdaYRTYuryDvGmWC66K2KX1nLErhlhs17CwzV997nYgD\n" + "H3OOU4HfqIKmdGbjvWlkmY+mLHyG10bbpOTbujkCgYAc68xHejSWDCT9p2KjPdLW\n" + "LYZGuOUa6y1L+QX85Vlh118Ymsczj8Z90qZbt3Zb1b9b+vKDe255agMj7syzNOLa\n" + "/MseHNOyq+9Z9gP1hGFekQKDIy88GzCOYG/fiT2KKJYY1kuHXnUdbiQgSlghODBS\n" + "jehD/K6DOJ80/FVKSH/dAQKBgQDJ+apTzpZhJ2f5k6L2jDq3VEK2ACedZEm9Kt9T\n" + "c1wKFnL6r83kkuB3i0L9ycRMavixvwBfFDjuY4POs5Dh8ip/mPFCa0hqISZHvbzi\n" + "dDyePJO9zmXaTJPDJ42kfpkofVAnfohXFQEy+cguTk848J+MmMIKfyE0h0QMabr9\n" + "86BUsQKBgEVgoi4RXwmtGovtMew01ORPV9MOX3v+VnsCgD4/56URKOAngiS70xEP\n" + "ONwNbTCWuuv43HGzJoVFiAMGnQP1BAJ7gkHkjSegOGKkiw12EPUWhFcMg+GkgPhc\n" + "pOqNt/VMBPjJ/ysHJqmLfQK9A35JV6Cmdphe+OIl28bcKhAOz8Dw\n" + "-----END RSA PRIVATE KEY-----\n"; + + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + int socket_fds[2]; + int res = socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds); + assert(res >= 0); + ssize_t send_res = send(socket_fds[1], data, size, 0); + assert(send_res == size); + res = shutdown(socket_fds[1], SHUT_WR); + assert(res == 0); + + int fd = open("/tmp/libssh_fuzzer_private_key", O_WRONLY | O_CREAT, S_IRWXU); + assert(fd >= 0); + ssize_t write_res = write(fd, kRSAPrivateKeyPEM, strlen(kRSAPrivateKeyPEM)); + assert(write_res == strlen(kRSAPrivateKeyPEM)); + close(fd); + + ssh_bind sshbind = ssh_bind_new(); + ssh_session session = ssh_new(); + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, "/tmp/libssh_fuzzer_private_key"); + + res = ssh_bind_accept_fd(sshbind, session, socket_fds[0]); + assert(res == SSH_OK); + + if (ssh_handle_key_exchange(session) == SSH_OK) { + while (true) { + ssh_message message = ssh_message_get(session); + if (!message) { + break; + } + ssh_message_free(message); + } + } + + close(socket_fds[0]); + close(socket_fds[1]); + + ssh_disconnect(session); + ssh_free(session); + ssh_bind_free(sshbind); + + return 0; +} diff --git a/tests/generate.py b/tests/generate.py new file mode 100755 index 0000000..08c2d5b --- /dev/null +++ b/tests/generate.py @@ -0,0 +1,10 @@ +#!/usr/bin/python +import os +a="" +for i in xrange(4096): + a+=chr(i % 256); +while True: + try: + os.write(1,a) + except: + exit(0) diff --git a/tests/keys/certauth/id_rsa b/tests/keys/certauth/id_rsa new file mode 100644 index 0000000..aa86ac2 --- /dev/null +++ b/tests/keys/certauth/id_rsa @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEiwIBAAKB/QMTSsCQqarOIauonYgjAt8E+lgSWBU/43ITyDDzLM4IS4wCcqXB +1Fagz386FU1B2AcUqlPZ1+7RlaXkqgKr4nGHv00U/GG+YAUgUAw1G12kI4cvrnWr +FIXwcq+VTJNej5pHxEqcRLw7ZBorpqm2UsY5KLr5R3uMNap7koj1Hbt9lKsvfDn6 +HjM4qY0ygx8hxf/4wCzIh5V4k9/UAMkqI2CM9c3yEE2aWh/4MDOnAFj+0T2sMAo8 +jyOZ6v+W7hmEtsUc9mEv+5B+hhVeYO/RwxketJAQRPYDSPSi1mjtv9fnzGk15q/l +Hb2V/HP/pyIpao19A4daR0a4ia9Hk4UCAwEAAQKB/QKEaPxjrKzlWoQSWRdUaQY5 +Idyy7yw9hiMa9BK1COh/u66XVlY86Fwb9puR5Fu/WF67WIuX1PpizJXkLBBRtuDs +lvY2BjrPQ/MONtc3JPYp4vbFXYxtAzh6zrTPhMVfcjV7Jr1XWZ+lEVOmhR2G4gvk +P2WDozIKWub3jMLTt4afgHCGaKfKEUpKjFkiAalz8oLVv8qV1FVPPDT2PWeKMuE3 +XfoN7YUaP6+aPlNnjIv/3BDsrPsiKZ+AKXcERdPvVQa/LypzW08cqC6sIJKWVmQI +3KgoYs9VvbDXfQ8jKfcsTApZkSDaLX6tf3Ei+76R0lbV4L1rpypa25qj9YECfwHP +N+v/6yObJFL5/1rEuT7CFbfP8g5J8qUVufcPRKv//ChluLuWNxgLJmIv2ZffWwhe +GKHlT98QPgFvsMSOyLeut4beZYKDSeVNvEt9eCBjOax2jOBGo3hv8j/Fs8yAfZOV +Ardv2qUszubM+DVwjJzb3vaZyEesRucJISqkJeUCfwGzGdMp0LXrZ7aaQGgHj/P2 +DKGq0E2gnj/EBapatjxKm4hMRn/vkTWjCDCryTnvJqkW/00tr4GqWXoeilBFD270 +RcvbOe9LQmGlHIYzgwc5nfLDBQyeNnHRmkeD9LQRUfdTdHj4jf+35pHlsVUT0Rnl +IMNoRA6V07bySFdI3SECfiF+1rbrxuhaCRIA0Ax3pL0eGuuTgksAm8VlbCMTgSiC +kF1CrXXgSAHOZb02C9Bf4cwEFfjh/KxM/4eXDa+Rfg7JQJxmVLivqEAlxIOvIxBp +xDnSWAljmrrllozyQnBsJDbbOm6BLf5+e5wIuryHvnP7vHNEU0J24g/78PxrrQJ/ +AVD4OzYzUfESzbUBFJBmyIZSmhJ0aOpwJOpniNvgLymI8zI/l22uhF/TQ/6HRbsV +sfcBmoA7YKzRx2ZHsIsLvN6p/4u1fsJGkuERCk5yt/HDhfPLwU321IeEeMaVia+w +T1/u4JF/SADhLTU69az3UJrHmQ7zRmh7I0DZDeB8gQJ+XqIqutPeerNtbqMjXGW8 +TdpqZAzAQAv6dPgaH0W0OzJe2hP9uy0D84H5f8Im/irJh/AXo/QL3obXqopyeLf0 +HfcUUnEZEBPlqsirZFtPClD+HL6Orf1je0oVV/aQssPkQl6/aXBNd+kS27U3NBML +LmRhC4+Q+/M5MlRggLtn +-----END RSA PRIVATE KEY----- diff --git a/tests/keys/certauth/id_rsa-cert.pub b/tests/keys/certauth/id_rsa-cert.pub new file mode 100644 index 0000000..615d39f --- /dev/null +++ b/tests/keys/certauth/id_rsa-cert.pub @@ -0,0 +1 @@ +ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgHZLan4ufbTFWr8Hl/8JvZTLYa0eNNm2qov9zPlK7qfwAAAADAQABAAAA/QMTSsCQqarOIauonYgjAt8E+lgSWBU/43ITyDDzLM4IS4wCcqXB1Fagz386FU1B2AcUqlPZ1+7RlaXkqgKr4nGHv00U/GG+YAUgUAw1G12kI4cvrnWrFIXwcq+VTJNej5pHxEqcRLw7ZBorpqm2UsY5KLr5R3uMNap7koj1Hbt9lKsvfDn6HjM4qY0ygx8hxf/4wCzIh5V4k9/UAMkqI2CM9c3yEE2aWh/4MDOnAFj+0T2sMAo8jyOZ6v+W7hmEtsUc9mEv+5B+hhVeYO/RwxketJAQRPYDSPSi1mjtv9fnzGk15q/lHb2V/HP/pyIpao19A4daR0a4ia9Hk4UAAAAAAAAAAAAAAAEAAAATdG9ydHVyZV9hdXRoX2NhcmxvcwAAAAkAAAAFYWxpY2UAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBAKcDafm8fNluz8a9GQaWgk1XUJcchLleeubTke6xQlJbI+rcjWIIwd1gDuh7Mdr0YIVhsh6dpg/L4bpRJBGNhDPxK8BmjTpIU14lKxrWQAirHN09P2QGtGtgrf09lA+xhV9E+pkF2Zz6PCt/P3sgUQnJcwjjsWhMaSASrt67fPanH+10hnfgjkevkMMHGJxmLiOW7JFQkd9I+gHHKEXs6Q9fhtiStzr3WN4hAPG5uXrnRZgseAV9p3TFPMEgUTpdRvnkOnkCBF169KiyjU97QgoXHExWk/rrgsJtgrTou/qRyi18WWm9S1HXLHyNOgZxKirmxLNPC9dIcJBD1kDWG8UAAAEPAAAAB3NzaC1yc2EAAAEAhNLOXT0jyz/Web0HUyrtPCvUZsLkDyBWCNoNTfsxGVoYsE4WCpNwqQO1A4NT5AtIE+R7rn9wfjvXM7sYh6hJyq3HVEWhts1SkQVU7sQBrImTIrj2cWKR3gmQ+ehsgNFGhcFZTK77ugw1fMfzZRvKVTkRWhe6v92wQOtkoINtf3f1fK6xY+vLwAA/E4VdaRJmhwAaNpy3PfMAJytkCLjcjUSWHYDha4hs98/EBPduGNNNiZdyG7lcpSvvq9HBDxzOiHBa/We9m38/Dk4TNVkZ/wrtBFQxH75if6SgGa/feGJrKQHBru7sPh8dO4R1AmZaoLmRzMnzZOtB0oEXmBqHmw== libssh_torture_auth diff --git a/tests/keys/certauth/id_rsa.pub b/tests/keys/certauth/id_rsa.pub new file mode 100644 index 0000000..4cbfc4c --- /dev/null +++ b/tests/keys/certauth/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAA/QMTSsCQqarOIauonYgjAt8E+lgSWBU/43ITyDDzLM4IS4wCcqXB1Fagz386FU1B2AcUqlPZ1+7RlaXkqgKr4nGHv00U/GG+YAUgUAw1G12kI4cvrnWrFIXwcq+VTJNej5pHxEqcRLw7ZBorpqm2UsY5KLr5R3uMNap7koj1Hbt9lKsvfDn6HjM4qY0ygx8hxf/4wCzIh5V4k9/UAMkqI2CM9c3yEE2aWh/4MDOnAFj+0T2sMAo8jyOZ6v+W7hmEtsUc9mEv+5B+hhVeYO/RwxketJAQRPYDSPSi1mjtv9fnzGk15q/lHb2V/HP/pyIpao19A4daR0a4ia9Hk4U= libssh_torture_auth diff --git a/tests/keys/id_ecdsa b/tests/keys/id_ecdsa new file mode 100644 index 0000000..7a1827c --- /dev/null +++ b/tests/keys/id_ecdsa @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIHbL0nzpzLS3ImIlhEffbDzPlIw/tn5QcfB64PbSiBl6oAoGCCqGSM49 +AwEHoUQDQgAERzA8X8OP7C3W/e1UNLh+21xIZVBiQ7i4Qb4xoOebRWuwzitEZon/ +8Dz+VpE29krJgCagqSt5RLllOx8eS2i8fw== +-----END EC PRIVATE KEY----- diff --git a/tests/keys/id_ecdsa.pub b/tests/keys/id_ecdsa.pub new file mode 100644 index 0000000..43b613b --- /dev/null +++ b/tests/keys/id_ecdsa.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEcwPF/Dj+wt1v3tVDS4fttcSGVQYkO4uEG+MaDnm0VrsM4rRGaJ//A8/laRNvZKyYAmoKkreUS5ZTsfHktovH8= comment diff --git a/tests/keys/id_ed25519 b/tests/keys/id_ed25519 new file mode 100644 index 0000000..2759f43 --- /dev/null +++ b/tests/keys/id_ed25519 @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCLo6vx1lX6ZZoe05lWTkuwrJUZN0T8hEer5UF9KPhOVgAAAKg+IRNSPiET +UgAAAAtzc2gtZWQyNTUxOQAAACCLo6vx1lX6ZZoe05lWTkuwrJUZN0T8hEer5UF9KPhOVg +AAAED2zFg52qYItoZaSUnir4VKubTxJveL9D2oWK7Prg/O24ujq/HWVfplmh7TmVZOS7Cs +lRk3RPyER6vlQX0o+E5WAAAAHmpqZWxlbkB0NDcwcy5qamVsZW4ucmVkaGF0LmNvbQECAw +QFBgc= +-----END OPENSSH PRIVATE KEY----- diff --git a/tests/keys/id_ed25519.pub b/tests/keys/id_ed25519.pub new file mode 100644 index 0000000..accd5b6 --- /dev/null +++ b/tests/keys/id_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIujq/HWVfplmh7TmVZOS7CslRk3RPyER6vlQX0o+E5W jjelen@t470s.jjelen.redhat.com diff --git a/tests/keys/id_rsa b/tests/keys/id_rsa new file mode 100644 index 0000000..0e3db26 --- /dev/null +++ b/tests/keys/id_rsa @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAs/fIz+Gy89XjUhXebNq2rs1K0RJEZXDbfcofb13ESDJ9fSbd ++RlCUM6i+foEVV23xWdands2Bkn72tGinJadIgOWU+koxp+gRmtJLE/ONiAKJQqR +K3eOJNBH6At96NMqJ4Gwv56G4W68uPgDwUVQwVIc6Ec6Rypiz7DE+S++gSvBQIVU +Qt7XkPcXOyN5zuj5qDhZM4l6V7Og9LWj4/2IF5acvKjfIMpeqmnp9z6BTYZQtuQU +6b1VuEYhXyvM6LkMwndsVGh46kDKMmRQhyA8A6qvLAliMMq88k5gR4AeJeTnqDR/ +Q120I6PMqYNl6TLTk8/KonOOPDR/XXGb3iF67wIDAQABAoIBAAVoL2dXf5nl1jOU +Jp+cnpp33oSTiOyHTIDl/rXI2mnU4oJNFaQzRxPIcYsTIOgzrZ7HsShG+sOLm36C +h+EugUARXYXd3nTBPP6AoK0tJKPpqIReYegtal7exxpIphrFpWGUeuv25lSFkDP6 +d5pp67gzMF0mLrEOq/NTe0eFULLuwa6+IKXU7deiU90pzi4jrjcIWNoGHSw1YYAZ +TC8KAxA/tYH9myya5krRCjA9B345DJ9Wd71wX+RZNgbSkIri/6dDTtvsYvqcQKo0 +OZ3MUDJnKmkfPLP84qZPRoEwUI1gts1WUdoNK6LK7yOJmPL5FMyTwZx3XtDw3gAv +TVhI7ikCgYEA5Ay0TCySPQAaC14WtjgIAmTa19mAtOFpbRxToi40WjXk3R6mMqyp +biAcNecdZRC6zzgAUp8g1O3Yc1d9fG/3FpM5eUbIer7mMLTRuQQysoJY2Ayw9OEA +qPHS/K6LPOD09aZo14fRUqVO8rwMbHtq2yhH8p3FM8WZRe5ms8zpyLUCgYEAygZ3 +RTMWbgcGdNoaPa5Ms9KRqAxKJLin2fE99KowZeJfvZN24sXExawQdy4BKVYT0H6e +MNEIPiEBVA4a1GDk/tyOrEt684IsidROngJaGbqb+SYm6feQAioYu0wkG/I2hS12 +/Z/aK6wFz5hWzBv/YvJqC7xD1YwZm1QXDyAiL5MCgYAXz8fHqGPAoNEXXMSsVB9p ++JPtM9W/jUXP0cRdy8tFnBkAiaG66tJqIEoxyqcEFYIb/vHxrpHkCc2vBXSh2KMJ +JWg75IssXeB1N3wqgGi2wOt7659SgmfqPA3WunbpbWfGepC56IGPypj6uW3mqeBX +b9ZLW/PqWviNF757iarjfQKBgGxKBPqRxM8bcumF0xUG7dRh5XN3ivKeDFL1Tels +pF6odftPJSWvLqdqcLUBctvuaNaUWEUAdvOei3C70sPOYFEAdnWCTBhkyWzj4XQu +/I7YCS0Gt0soSQfv+qvCx4Q3U+QVF7ghTDemkMLS/IuR4lXubMt3kcDQxRUOgQG5 +jrmDAoGALauF7ZyzEnQgsgMVzfm9znl5I2aIsLgdsAv3lINVrvtTKhddp7cdd+2j +dwZlaMnLET/3MY/Cvf13vEsS+bdNXjsdQidqBL8pe5PXY/pafBhtduQuvGzlHJA5 +CEBnwB0SdtsXbzSpOAPZqea4Nz9MkQ8LMsINdPpxCuFhjeYa9Ow= +-----END RSA PRIVATE KEY----- diff --git a/tests/keys/id_rsa.pub b/tests/keys/id_rsa.pub new file mode 100644 index 0000000..15a35b3 --- /dev/null +++ b/tests/keys/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCz98jP4bLz1eNSFd5s2rauzUrREkRlcNt9yh9vXcRIMn19Jt35GUJQzqL5+gRVXbfFZ1qd2zYGSfva0aKclp0iA5ZT6SjGn6BGa0ksT842IAolCpErd44k0EfoC33o0yongbC/nobhbry4+APBRVDBUhzoRzpHKmLPsMT5L76BK8FAhVRC3teQ9xc7I3nO6PmoOFkziXpXs6D0taPj/YgXlpy8qN8gyl6qaen3PoFNhlC25BTpvVW4RiFfK8zouQzCd2xUaHjqQMoyZFCHIDwDqq8sCWIwyrzyTmBHgB4l5OeoNH9DXbQjo8ypg2XpMtOTz8qic448NH9dcZveIXrv asn@krikkit.cryptomilk.site diff --git a/tests/keys/ssh_host_dsa_key b/tests/keys/ssh_host_dsa_key new file mode 100644 index 0000000..9ee8bd8 --- /dev/null +++ b/tests/keys/ssh_host_dsa_key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQD7vBS+d/eJP6wK2VQw+8AIfgCw9IR50utLRkkrWbfDdiM7V+fp +tJYKCyqZT9j9ANhqicB2tuqAI6WJBZMaGekxWfI30JxPkHZrrwbdFzlRbjav07lg +IKqWgcz81iVPmfn5savEoobiSFjJNMmYcizjKZgGmyNUzlJjzF7u5qD08wIVAPFp +6VKuv8VxNjENciUZCdEDRW/lAoGBAN/BFSBRSP9frsHID6e2NeKHqs8JDUWhCTE9 +/WQKqUUbxO2UU98CfHuf1mNlaSsrOxaBdvTeURcZZc1svhyGr1VG+NbNTDDlzTgA +UlrzNML61TYcFXQVxgifUy+Tmh8FRGCa6Ko/EsX4ZWLTto5w1u5cPpgzSbLMco9T +AeeNLgYNAoGAJRuawWN3+NezI7+bBe42Kjg4gVUlpS+8TTlYFbwrM1Esab7gvxHB +/b2apbk9xIAkkqsnb+EPrXTLUdE2Y7XkEuGLLSTus2UlZKobBGBX/Ioysg5W9Fk/ +2MhI4YssRb2alar8d+gmAHPaT+D+NDd90PBfY3HqcXFEK+eDTWo1JNICFBLdsuoO +6pObeFSOYbr38kJzZ0xG +-----END DSA PRIVATE KEY----- diff --git a/tests/keys/ssh_host_dsa_key.pub b/tests/keys/ssh_host_dsa_key.pub new file mode 100644 index 0000000..5fcb413 --- /dev/null +++ b/tests/keys/ssh_host_dsa_key.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAPu8FL5394k/rArZVDD7wAh+ALD0hHnS60tGSStZt8N2IztX5+m0lgoLKplP2P0A2GqJwHa26oAjpYkFkxoZ6TFZ8jfQnE+QdmuvBt0XOVFuNq/TuWAgqpaBzPzWJU+Z+fmxq8SihuJIWMk0yZhyLOMpmAabI1TOUmPMXu7moPTzAAAAFQDxaelSrr/FcTYxDXIlGQnRA0Vv5QAAAIEA38EVIFFI/1+uwcgPp7Y14oeqzwkNRaEJMT39ZAqpRRvE7ZRT3wJ8e5/WY2VpKys7FoF29N5RFxllzWy+HIavVUb41s1MMOXNOABSWvM0wvrVNhwVdBXGCJ9TL5OaHwVEYJroqj8SxfhlYtO2jnDW7lw+mDNJssxyj1MB540uBg0AAACAJRuawWN3+NezI7+bBe42Kjg4gVUlpS+8TTlYFbwrM1Esab7gvxHB/b2apbk9xIAkkqsnb+EPrXTLUdE2Y7XkEuGLLSTus2UlZKobBGBX/Ioysg5W9Fk/2MhI4YssRb2alar8d+gmAHPaT+D+NDd90PBfY3HqcXFEK+eDTWo1JNI= asn@magrathea diff --git a/tests/keys/ssh_host_ecdsa_key b/tests/keys/ssh_host_ecdsa_key new file mode 100644 index 0000000..1fcd836 --- /dev/null +++ b/tests/keys/ssh_host_ecdsa_key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIB9v2n1oaXvBECf0gDPxTibeUPvvkI1anNWDAIkNjs5JoAoGCCqGSM49 +AwEHoUQDQgAEqkTqNu7gRegPJRy0WiseJz9NAdBimzyNSzNwI5eAkEqv9D6Y95KL +7DBEnDQ2p08iOLw+vN1PKHsCM7b/ONbYVg== +-----END EC PRIVATE KEY----- diff --git a/tests/keys/ssh_host_ecdsa_key.pub b/tests/keys/ssh_host_ecdsa_key.pub new file mode 100644 index 0000000..460153a --- /dev/null +++ b/tests/keys/ssh_host_ecdsa_key.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKpE6jbu4EXoDyUctForHic/TQHQYps8jUszcCOXgJBKr/Q+mPeSi+wwRJw0NqdPIji8PrzdTyh7AjO2/zjW2FY= asn@magrathea diff --git a/tests/keys/ssh_host_key b/tests/keys/ssh_host_key new file mode 100644 index 0000000..41d3bd6 Binary files /dev/null and b/tests/keys/ssh_host_key differ diff --git a/tests/keys/ssh_host_key.pub b/tests/keys/ssh_host_key.pub new file mode 100644 index 0000000..ae7cc23 --- /dev/null +++ b/tests/keys/ssh_host_key.pub @@ -0,0 +1 @@ +2048 65537 25221975523736997039149017470335977198642717886559395625730372192276493838727011206749822289920387480933533054627057418868711378045090730895752530916661328094497437687453813456961487210492465678475508526337829331199296553120728607984859224949182503917312492825658971738208505685553964707412720244524969161284321098487507924676797222812771309962906894332072854924265623785469343453142982185436565166155021228521252914913227554455102103918367844210755391318078654400527927267478149210805219779896806429660492177158822689909493046725157917529664436252598971135251689616517266344945600782273453037452082373553352939812279 asn@magrathea diff --git a/tests/keys/ssh_host_rsa_key b/tests/keys/ssh_host_rsa_key new file mode 100644 index 0000000..5032fb7 --- /dev/null +++ b/tests/keys/ssh_host_rsa_key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAqzabeU0oKbHDwdlqindABvtzgWCvXdHJ+d2Ew6te2LXjkwju +y7u6B7y63NZRy57ccrE4YSeWItVoZn+DWN+guU354Ss/tzQ9/thUmLrvtKNvJwuF +F5Ch7Q4BsrXGsb2GSv+7W5tpx3yAqH1TvKQj/MmQVX+/9KtXEnh7/vpiCqoOXQAR +zvIIQxoo1aUQBHAkRW7Yw0Ds7AjC9uV1ns4xdBXPQmDk23pWvCq7E+7rOEbKRrjj +lApS4lJYy0oEXsFdqUPd+PtT1gG0nIElHFQtsTgUvwYQmJEzrBxv41odwRvxbMjg +THPr4SLRRRSuPwICWyvxqPpa7EsfMBnbPEIUzQIDAQABAoIBAEUO15MLvgFjRDQy +P7jt9JNcZPBwUQukjLUN1nkd7Dm407wAxGDErXplc3GTuJZK01wngzgcwX/3WA7P +q+jy+l8DxqA904tPtRnPo/+elwTjTvgOu3YPzmBRX/n3O9eBPGOP1sBSZU4jN7m+ +I0JZanKR0nfJ+WD0o0A9/LWRxG3MFIntBamtT6pgee8sAu44IvW0o7tHJabMq02J +Z/ndrJmox34wq6SMFANax+N1x9sZa60bL7gEoDWQJNKOaMrbtOaIoTGFIc4hFqoA +SzjNqcGsHPWs44cw0mNkUGq37jEvaCwzAp+U80ma1skBhXuJL9sQOxl1v5qW91c/ +Cnm5WYECgYEA4DPvqbLt+VdyTtmCQ370yiCk4OPPMPzbM65IVKgQL/rN6HdNShTO +uLF6P8XC8vNP2OSydJeFt+kMKd7E/4o5LfvEqUGXZJDkB7fLjrOjyZU3bxtIx95x +qYGWRcWbd3sHzlBJGuFVSE7GREE+lqhkSu4ry4l/GAKxSymAXgGd/9ECgYEAw37L +ppZIavcLE2rZgXHoqMiJzeGzsidJbkHss4k7ubLe8vyBMiv0HC2anxPa2+yNWuF2 ++pEr84bllh149VKeild24UEBAR2w/P41ggWqiUP7PKllh+huWzG4+KNFbfUP4dd0 +4LkVgfsCz32qD8qxXNCxJCZ8H2fmjKsYw/oCID0CgYAiuSh3GdUtdtOnTpyUI4d5 +/pBKnD2skpzIZkehhN3s8GUPidqYjJxvkl0in1hQFErbhp/02rrE/vz5Rx0vjpLI +gmO06wmtc5s9bsPB+CR3xfpt5MXi3pqv6/gAGli3qoBM/bY0yY1Rw5GFZK1y2+Wc +jUKPJV5fs5sNzwGojYuQ4QKBgQCNNgqOo2Fd+mLCvNyt1wTy3iBEWfL+DcjJ3s7G +hKtioKTQqbn87qjercZRf/sH/t/ANLpHlhNETj2KaHGV6v7f+PvDC7xY/QR6SnmG +GOetTTCuCcJwIGGOd+UfnHgrS+gT/xjKtoalpBXMoP31eDkTTR+XeEESQm/TTkeO +UAm3FQKBgD8Y7CLHpyZZ+eOnxRSPU4m4AWAEp7JOwHDRWWQeUornrXDYgD87d2M9 +iIAEuOzNggA56Nm3AzBOPRj4HkBh57ToVKPswHwB0oWvrtSjpLkkU6q8xRG3XuJD +2AskDaZONzIDoJGfZ3+W7YbKELK7DPtFXL15sOfBmpoEkI9RA5vM +-----END RSA PRIVATE KEY----- diff --git a/tests/keys/ssh_host_rsa_key.pub b/tests/keys/ssh_host_rsa_key.pub new file mode 100644 index 0000000..efa2191 --- /dev/null +++ b/tests/keys/ssh_host_rsa_key.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrNpt5TSgpscPB2WqKd0AG+3OBYK9d0cn53YTDq17YteOTCO7Lu7oHvLrc1lHLntxysThhJ5Yi1Whmf4NY36C5TfnhKz+3ND3+2FSYuu+0o28nC4UXkKHtDgGytcaxvYZK/7tbm2nHfICofVO8pCP8yZBVf7/0q1cSeHv++mIKqg5dABHO8ghDGijVpRAEcCRFbtjDQOzsCML25XWezjF0Fc9CYOTbela8KrsT7us4RspGuOOUClLiUljLSgRewV2pQ934+1PWAbScgSUcVC2xOBS/BhCYkTOsHG/jWh3BG/FsyOBMc+vhItFFFK4/AgJbK/Go+lrsSx8wGds8QhTN asn@magrathea diff --git a/tests/keys/user_ca b/tests/keys/user_ca new file mode 100644 index 0000000..dc9d80c --- /dev/null +++ b/tests/keys/user_ca @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEApwNp+bx82W7Pxr0ZBpaCTVdQlxyEuV565tOR7rFCUlsj6tyN +YgjB3WAO6Hsx2vRghWGyHp2mD8vhulEkEY2EM/ErwGaNOkhTXiUrGtZACKsc3T0/ +ZAa0a2Ct/T2UD7GFX0T6mQXZnPo8K38/eyBRCclzCOOxaExpIBKu3rt89qcf7XSG +d+COR6+QwwcYnGYuI5bskVCR30j6AccoRezpD1+G2JK3OvdY3iEA8bm5eudFmCx4 +BX2ndMU8wSBROl1G+eQ6eQIEXXr0qLKNT3tCChccTFaT+uuCwm2CtOi7+pHKLXxZ +ab1LUdcsfI06BnEqKubEs08L10hwkEPWQNYbxQIDAQABAoIBACW2AaHgS5iVCtln +LVVterKX+pyEVfu9N6cTMqpg4AbUiYGol0wBijTAUd1wo8s6zuiPLLb5BdwfPzLg +y3IjMCzCUgy5mz4Dwr9JSThgFElgyb2y7LNbSDXOuLqrwtjgTqs6WhNfXMmzPw7b +Rqw4mdPJ5u2k7BQO3NXfIhks4ISYzpzNAwj1a2NMphvkZyvfRnWiQ0pvEXQCxwuR +74iGpPFeyFjjku/O4TiHZllPmDdD3ERalkf8RIudQ5gcbL4fRoONTzfZHtmARWoP +Jury4Zfr5b3VGSnkUDaGlzilXvBusAZOCaaU7chvOPVjXMbSAUEpFBmnRHk5dfrH +fCXECcECgYEA0KMtV3IzwMToVdvzcMQc1ovDvKZAQPneLTxFgNpOeycOhzulzY9p +3fRi5QUOA/Ff+LcCL86APqwoEYe4bgam6mwGFFhv1usf4ulbLNk8ZeR51CG6emPt +tLpg6PThxhMnNpu+StrBAOxeo9pZGd+Plt6d4vfoalOHVkPlSv7OC9kCgYEAzO1I +HuZAQkVdKLGuZlf8E4VEaiMBKdl5+H+8w9peOOax6nqAIrwp2d0aZ52LDjwg7d3C +eSmxu0U1jsbzexVVePr/NmdJOu3+gB0GvlzRjS1xT+MCZIye5a7Nxc7lBp5rFmgV +dJTA6XXRoykinZIxz068SHqtNhNOzO4hUmPDN80CgYAlxOR4aBwmUX8dy+uOBnKS +BEsy44XOPW2TEs4iPWLnuHJQ2ONzCvtHSu58NyYKYK/W/opOzTs6HUBDrCYfBOVC +mrufA0N7zKTBFy2COPFOIMZNOK3haiWmCfdxNKOKj/0RTbBtLJyz5hZb4zMuE+KS +lUpPxEE2vlhJrZDcurPiQQKBgQCIEqMKCX/vwVlLlTglsxSp7ZrxEw9Jt6O68y7n +qc9Y3y6ScQc2iVUM2jkXRlA4goqnB9KDW8EthZY7mTXBq/fWXmwqtsi0faW5cgyx +SLbIlL0h+63yEEHOZ5UxXOFM1NJszW45vDCglOBABCd9E79JVZHGWtc7CfUQNKsh +pybQnQKBgHbPnITR7esVQYLq3PHSsdOdkFiiVf3D7wHiNZcXWjJvUqMF4tH5XAzY +QafKqKk0FzO92ZOhQeB5xauFY5wzsa+Xl8cQkyvtWngFIKbWydEehZWVgXcedxEC +xjbZWKmsYDqBYi3bw9Dxb0AvT+kDtq0Azi8QTDAvRwylvtkYj/V8 +-----END RSA PRIVATE KEY----- diff --git a/tests/pkd/CMakeLists.txt b/tests/pkd/CMakeLists.txt new file mode 100644 index 0000000..85a5777 --- /dev/null +++ b/tests/pkd/CMakeLists.txt @@ -0,0 +1,55 @@ +project(pkd C) + +if (WITH_SERVER AND UNIX AND NOT WIN32) + + include_directories(${libssh_SOURCE_DIR}/include + ${CMOCKA_INCLUDE_DIR} + ${ZLIB_INCLUDE_DIR} + ${CMAKE_BINARY_DIR} + ${libssh_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}) + +set(pkd_hello_src + pkd_daemon.c + pkd_hello.c + pkd_keyutil.c + pkd_util.c +) + +set(pkd_libs + ${CMOCKA_LIBRARY} + ssh::static + ${ARGP_LIBRARIES} + pthread +) + +add_executable(pkd_hello ${pkd_hello_src}) +target_compile_options(pkd_hello PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) +target_link_libraries(pkd_hello ${pkd_libs}) + +# +# pkd_hello_i1 runs only one iteration per algorithm combination for +# sake of speeding up overall test run time. More iterations can be +# specified with `-i` and may be helpful for chasing down bugs that +# are not 100% reproducible. +# +add_test(pkd_hello_i1 ${CMAKE_CURRENT_BINARY_DIR}/pkd_hello -e -o -i1 -w /tmp/pkd_socket_wrapper_XXXXXX) + +# +# pkd_hello_rekey is used to test server-side implementation of rekeying. +# +add_test(pkd_hello_rekey ${CMAKE_CURRENT_BINARY_DIR}/pkd_hello -t torture_pkd_openssh_rsa_rsa_default -i1 --rekey=16 -v -v -v -w /tmp/pkd_socket_wrapper_XXXXXX) + +# +# Configure environment for cwrap socket wrapper. +# +find_package(socket_wrapper 1.1.5 REQUIRED) +if (OSX) + set(PKD_ENVIRONMENT "DYLD_FORCE_FLAT_NAMESPACE=1;DYLD_INSERT_LIBRARIES=${SOCKET_WRAPPER_LIBRARY}") +else () + set(PKD_ENVIRONMENT "LD_PRELOAD=${SOCKET_WRAPPER_LIBRARY}") +endif () +message(STATUS "PKD_ENVIRONMENT=${PKD_ENVIRONMENT}") +set_property(TEST pkd_hello_i1 PROPERTY ENVIRONMENT ${PKD_ENVIRONMENT}) + +endif (WITH_SERVER AND UNIX AND NOT WIN32) diff --git a/tests/pkd/pkd_client.h b/tests/pkd/pkd_client.h new file mode 100644 index 0000000..474ca17 --- /dev/null +++ b/tests/pkd/pkd_client.h @@ -0,0 +1,112 @@ +/* + * pkd_client.h -- macros for generating client-specific command + * invocations for use with pkd testing + * + * (c) 2014, 2018 Jon Simons + */ + +#ifndef __PKD_CLIENT_H__ +#define __PKD_CLIENT_H__ + +#include "config.h" + +/* OpenSSH */ + +#define OPENSSH_BINARY "ssh" +#define OPENSSH_KEYGEN "ssh-keygen" + +#define OPENSSH_HOSTKEY_ALGOS_DEFAULT "ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa" +#define OPENSSH_PKACCEPTED_DEFAULT "ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa,ssh-rsa-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com" + +#if HAVE_ECC +#define OPENSSH_HOSTKEY_ALGOS_ECDSA ",ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521" +#define OPENSSH_PKACCEPTED_ECDSA ",ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com" +#else /* HAVE_ECC */ +#define OPENSSH_HOSTKEY_ALGOS_ECDSA "" +#define OPENSSH_PKACCEPTED_ECDSA "" +#endif /* HAVE_ECC */ + +#if HAVE_DSA +#define OPENSSH_HOSTKEY_ALGOS_DSA ",ssh-dss" +#define OPENSSH_PKACCEPTED_DSA ",ssh-dss,ssh-dss-cert-v01@openssh.com" +#else /* HAVE_DSA */ +#define OPENSSH_HOSTKEY_ALGOS_DSA "" +#define OPENSSH_PKACCEPTED_DSA "" +#endif /* HAVE_DSA */ + +#define OPENSSH_HOSTKEY_ALGOS \ + "-o HostKeyAlgorithms=" \ + OPENSSH_HOSTKEY_ALGOS_DEFAULT \ + OPENSSH_HOSTKEY_ALGOS_ECDSA \ + OPENSSH_HOSTKEY_ALGOS_DSA + +#define OPENSSH_PKACCEPTED_TYPES \ + "-o PubkeyAcceptedKeyTypes=" \ + OPENSSH_PKACCEPTED_DEFAULT \ + OPENSSH_PKACCEPTED_ECDSA \ + OPENSSH_PKACCEPTED_DSA + +#define OPENSSH_CMD_START(hostkey_algos) \ + OPENSSH_BINARY " " \ + "-o UserKnownHostsFile=/dev/null " \ + "-o StrictHostKeyChecking=no " \ + "-F /dev/null " \ + hostkey_algos " " \ + OPENSSH_PKACCEPTED_TYPES " " \ + "-i " CLIENT_ID_FILE " " \ + "1> %s.out " \ + "2> %s.err " \ + "-vvv " + +#define OPENSSH_CMD_END "-p 1234 localhost ls" + +#define OPENSSH_CMD \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) OPENSSH_CMD_END + +#define OPENSSH_KEX_CMD(kexalgo) \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) "-o KexAlgorithms=" kexalgo " " OPENSSH_CMD_END + +#define OPENSSH_CIPHER_CMD(ciphers) \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) "-c " ciphers " " OPENSSH_CMD_END + +#define OPENSSH_MAC_CMD(macs) \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) "-c aes128-ctr,aes192-ctr,aes256-ctr,aes256-cbc,aes192-cbc,aes128-cbc -o MACs=" macs " " OPENSSH_CMD_END + +#define OPENSSH_HOSTKEY_CMD(hostkeyalgo) \ + OPENSSH_CMD_START("-o HostKeyAlgorithms=" hostkeyalgo " ") OPENSSH_CMD_END + +#define OPENSSH_CERT_CMD \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) "-o CertificateFile=" CLIENT_ID_FILE "-cert.pub " OPENSSH_CMD_END + +#define OPENSSH_SHA256_CERT_CMD \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) "-o CertificateFile=" CLIENT_ID_FILE "-sha256-cert.pub " OPENSSH_CMD_END + +/* Dropbear */ + +#define DROPBEAR_BINARY "dbclient" +#define DROPBEAR_KEYGEN "dropbearkey" + +#define DROPBEAR_CMD_START \ + DROPBEAR_BINARY " " \ + "-y -y " \ + "-i " CLIENT_ID_FILE " " \ + "1> %s.out " \ + "2> %s.err " + +#define DROPBEAR_CMD_END "-p 1234 localhost ls" + +#define DROPBEAR_CMD \ + DROPBEAR_CMD_START DROPBEAR_CMD_END + +#if 0 /* dbclient does not expose control over kex algo */ +#define DROPBEAR_KEX_CMD(kexalgo) \ + DROPBEAR_CMD +#endif + +#define DROPBEAR_CIPHER_CMD(ciphers) \ + DROPBEAR_CMD_START "-c " ciphers " " DROPBEAR_CMD_END + +#define DROPBEAR_MAC_CMD(macs) \ + DROPBEAR_CMD_START "-m " macs " " DROPBEAR_CMD_END + +#endif /* __PKD_CLIENT_H__ */ diff --git a/tests/pkd/pkd_daemon.c b/tests/pkd/pkd_daemon.c new file mode 100644 index 0000000..910b374 --- /dev/null +++ b/tests/pkd/pkd_daemon.c @@ -0,0 +1,584 @@ +/* + * pkd_daemon.c -- a sample public-key testing daemon using libssh + * + * Uses public key authentication to establish an exec channel and + * echo back payloads to the user. + * + * (c) 2014 Jon Simons + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "torture.h" // for ssh_fips_mode() +#include "pkd_daemon.h" + +#include // for cmocka +#include + +static int pkdout_enabled; +static int pkderr_enabled; + +static void pkdout(const char *fmt, ...) PRINTF_ATTRIBUTE(1, 2); +static void pkderr(const char *fmt, ...) PRINTF_ATTRIBUTE(1, 2); + +static void pkdout(const char *fmt, ...) { + va_list vargs; + if (pkdout_enabled) { + va_start(vargs, fmt); + vfprintf(stdout, fmt, vargs); + va_end(vargs); + } +} + +static void pkderr(const char *fmt, ...) { + va_list vargs; + if (pkderr_enabled) { + va_start(vargs, fmt); + vfprintf(stderr, fmt, vargs); + va_end(vargs); + } +} + +/* + * pkd state: only one thread can run pkd at a time --------------------- + */ + +static struct { + int rc; + pthread_t tid; + int keep_going; + volatile int pkd_ready; +} ctx; + +static struct { + int server_fd; + int req_exec_received; + int close_received; + int eof_received; +} pkd_state; + +static void pkd_sighandler(int signum) { + (void) signum; +} + +static int pkd_init_libssh(void) +{ + int rc = ssh_threads_set_callbacks(ssh_threads_get_pthread()); + return (rc == SSH_OK) ? 0 : 1; +} + +static int pkd_init_server_fd(short port) { + int rc = 0; + int yes = 1; + struct sockaddr_in addr; + + int server_fd = socket(PF_INET, SOCK_STREAM, 0); + if (server_fd < 0) { + rc = -1; + goto out; + } + + rc = setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); + if (rc != 0) { + goto outclose; + } + + memset(&addr, 0x0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = INADDR_ANY; + rc = bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)); + if (rc != 0) { + goto outclose; + } + + rc = listen(server_fd, 128); + if (rc == 0) { + goto out; + } + +outclose: + close(server_fd); + server_fd = -1; +out: + pkd_state.server_fd = server_fd; + return rc; +} + +static int pkd_accept_fd(void) +{ + int fd = -1; + struct sockaddr_in addr; + socklen_t len = sizeof(addr); + + do { + fd = accept(pkd_state.server_fd, (struct sockaddr *) &addr, &len); + } while ((ctx.keep_going != 0) && (fd < 0) && (errno == EINTR)); + + return fd; +} + +static void pkd_eof(ssh_session session, + ssh_channel channel, + void *userdata) { + (void) session; + (void) channel; + (void) userdata; + pkdout("pkd_eof\n"); + pkd_state.eof_received = 1; +} + +static void pkd_chan_close(ssh_session session, + ssh_channel channel, + void *userdata) { + (void) session; + (void) channel; + (void) userdata; + pkdout("pkd_chan_close\n"); + pkd_state.close_received = 1; +} + +static int pkd_req_exec(ssh_session s, + ssh_channel c, + const char *cmd, + void *userdata) { + (void) s; + (void) c; + (void) cmd; + (void) userdata; + /* assumes pubkey authentication has already succeeded */ + pkdout("pkd_req_exec\n"); + pkd_state.req_exec_received = 1; + return 0; +} + +/* assumes there is only ever a single channel */ +static struct ssh_channel_callbacks_struct pkd_channel_cb = { + .channel_eof_function = pkd_eof, + .channel_close_function = pkd_chan_close, + .channel_exec_request_function = pkd_req_exec, +}; + +static int pkd_auth_pubkey_cb(ssh_session s, + const char *user, + ssh_key key, + char state, + void *userdata) { + (void) s; + (void) user; + (void) key; + (void) state; + (void) userdata; + pkdout("pkd_auth_pubkey_cb keytype %s, state: %d\n", + ssh_key_type_to_char(ssh_key_type(key)), state); + if ((state == SSH_PUBLICKEY_STATE_NONE) || + (state == SSH_PUBLICKEY_STATE_VALID)) { + return SSH_AUTH_SUCCESS; + } + return SSH_AUTH_DENIED; +} + +static int pkd_service_request_cb(ssh_session session, + const char *service, + void *userdata) { + (void) session; + (void) userdata; + pkdout("pkd_service_request_cb: %s\n", service); + return (0 == (strcmp(service, "ssh-userauth"))) ? 0 : -1; +} + +static ssh_channel pkd_channel_openreq_cb(ssh_session s, + void *userdata) { + ssh_channel c = NULL; + ssh_channel *out = (ssh_channel *) userdata; + + /* assumes pubkey authentication has already succeeded */ + pkdout("pkd_channel_openreq_cb\n"); + + c = ssh_channel_new(s); + if (c == NULL) { + pkderr("ssh_channel_new: %s\n", ssh_get_error(s)); + return NULL; + } + + ssh_callbacks_init(&pkd_channel_cb); + pkd_channel_cb.userdata = userdata; + if (ssh_set_channel_callbacks(c, &pkd_channel_cb) != SSH_OK) { + pkderr("ssh_set_channel_callbacks: %s\n", ssh_get_error(s)); + ssh_channel_free(c); + c = NULL; + } + + *out = c; + + return c; +} + +static struct ssh_server_callbacks_struct pkd_server_cb = { + .auth_pubkey_function = pkd_auth_pubkey_cb, + .service_request_function = pkd_service_request_cb, + .channel_open_request_session_function = pkd_channel_openreq_cb, +}; + +static int pkd_exec_hello(int fd, struct pkd_daemon_args *args) +{ + int rc = -1; + ssh_bind b = NULL; + ssh_session s = NULL; + ssh_event e = NULL; + ssh_channel c = NULL; + enum ssh_bind_options_e opts = -1; + + int level = args->opts.libssh_log_level; + enum pkd_hostkey_type_e type = args->type; + const char *hostkeypath = args->hostkeypath; + const char *default_kex = NULL; + char *all_kex = NULL; + size_t kex_len = 0; + const char *all_ciphers = NULL; + const uint64_t rekey_data_limit = args->rekey_data_limit; + bool process_config = false; + + pkd_state.eof_received = 0; + pkd_state.close_received = 0; + pkd_state.req_exec_received = 0; + + b = ssh_bind_new(); + if (b == NULL) { + pkderr("ssh_bind_new\n"); + goto outclose; + } + + if (type == PKD_RSA) { + opts = SSH_BIND_OPTIONS_RSAKEY; + } else if (type == PKD_ED25519) { + opts = SSH_BIND_OPTIONS_HOSTKEY; +#ifdef HAVE_DSA + } else if (type == PKD_DSA) { + opts = SSH_BIND_OPTIONS_DSAKEY; +#endif + } else if (type == PKD_ECDSA) { + opts = SSH_BIND_OPTIONS_ECDSAKEY; + } else { + pkderr("unknown hostkey type: %d\n", type); + rc = -1; + goto outclose; + } + + rc = ssh_bind_options_set(b, opts, hostkeypath); + if (rc != 0) { + pkderr("ssh_bind_options_set: %s\n", ssh_get_error(b)); + goto outclose; + } + + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_LOG_VERBOSITY, &level); + if (rc != 0) { + pkderr("ssh_bind_options_set log verbosity: %s\n", ssh_get_error(b)); + goto outclose; + } + + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_PROCESS_CONFIG, + &process_config); + if (rc != 0) { + pkderr("ssh_bind_options_set process config: %s\n", ssh_get_error(b)); + goto outclose; + } + + if (!ssh_fips_mode()) { + /* Add methods not enabled by default */ +#define GEX_SHA1 "diffie-hellman-group-exchange-sha1" + default_kex = ssh_kex_get_default_methods(SSH_KEX); + kex_len = strlen(default_kex) + strlen(GEX_SHA1) + 2; + all_kex = malloc(kex_len); + if (all_kex == NULL) { + pkderr("Failed to alloc more memory.\n"); + goto outclose; + } + snprintf(all_kex, kex_len, "%s," GEX_SHA1, default_kex); + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_KEY_EXCHANGE, all_kex); + free(all_kex); + if (rc != 0) { + pkderr("ssh_bind_options_set kex methods: %s\n", ssh_get_error(b)); + goto outclose; + } + + /* Enable all supported ciphers */ + all_ciphers = ssh_kex_get_supported_method(SSH_CRYPT_C_S); + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_CIPHERS_C_S, all_ciphers); + if (rc != 0) { + pkderr("ssh_bind_options_set Ciphers C-S: %s\n", ssh_get_error(b)); + goto outclose; + } + + all_ciphers = ssh_kex_get_supported_method(SSH_CRYPT_S_C); + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_CIPHERS_S_C, all_ciphers); + if (rc != 0) { + pkderr("ssh_bind_options_set Ciphers S-C: %s\n", ssh_get_error(b)); + goto outclose; + } + } + + s = ssh_new(); + if (s == NULL) { + pkderr("ssh_new\n"); + goto outclose; + } + + rc = ssh_options_set(s, SSH_OPTIONS_REKEY_DATA, &rekey_data_limit); + if (rc != 0) { + pkderr("ssh_options_set rekey data: %s\n", ssh_get_error(s)); + goto outclose; + } + + /* + * ssh_bind_accept loads host key as side-effect. If this + * succeeds, the given 'fd' will be closed upon 'ssh_free(s)'. + */ + rc = ssh_bind_accept_fd(b, s, fd); + if (rc != SSH_OK) { + pkderr("ssh_bind_accept_fd: %s\n", ssh_get_error(b)); + goto outclose; + } + + /* accept only publickey-based auth */ + ssh_set_auth_methods(s, SSH_AUTH_METHOD_PUBLICKEY); + + /* initialize callbacks */ + ssh_callbacks_init(&pkd_server_cb); + pkd_server_cb.userdata = &c; + rc = ssh_set_server_callbacks(s, &pkd_server_cb); + if (rc != SSH_OK) { + pkderr("ssh_set_server_callbacks: %s\n", ssh_get_error(s)); + goto out; + } + + /* first do key exchange */ + rc = ssh_handle_key_exchange(s); + if (rc != SSH_OK) { + pkderr("ssh_handle_key_exchange: %s\n", ssh_get_error(s)); + goto out; + } + + /* setup and pump event to carry out exec channel */ + e = ssh_event_new(); + if (e == NULL) { + pkderr("ssh_event_new\n"); + goto out; + } + + rc = ssh_event_add_session(e, s); + if (rc != SSH_OK) { + pkderr("ssh_event_add_session\n"); + goto out; + } + + /* poll until exec channel established */ + while ((ctx.keep_going != 0) && + (rc != SSH_ERROR) && (pkd_state.req_exec_received == 0)) { + rc = ssh_event_dopoll(e, -1 /* infinite timeout */); + } + + if (rc == SSH_ERROR) { + pkderr("ssh_event_dopoll\n"); + goto out; + } else if (c == NULL) { + pkderr("poll loop exited but exec channel not ready\n"); + rc = -1; + goto out; + } + + rc = ssh_channel_write(c, args->payload.buf, args->payload.len); + if (rc != (int)args->payload.len) { + pkderr("ssh_channel_write partial (%d != %zd)\n", rc, args->payload.len); + } + + rc = ssh_channel_request_send_exit_status(c, 0); + if (rc != SSH_OK) { + pkderr("ssh_channel_request_send_exit_status: %s\n", + ssh_get_error(s)); + goto out; + } + + rc = ssh_channel_send_eof(c); + if (rc != SSH_OK) { + pkderr("ssh_channel_send_eof: %s\n", ssh_get_error(s)); + goto out; + } + + rc = ssh_channel_close(c); + if (rc != SSH_OK) { + pkderr("ssh_channel_close: %s\n", ssh_get_error(s)); + goto out; + } + + while ((ctx.keep_going != 0) && + (pkd_state.eof_received == 0) && + (pkd_state.close_received == 0)) { + rc = ssh_event_dopoll(e, 1000 /* milliseconds */); + if (rc == SSH_ERROR) { + /* log, but don't consider this fatal */ + pkdout("ssh_event_dopoll for eof + close: %s\n", ssh_get_error(s)); + rc = 0; + break; + } else { + rc = 0; + } + } + + while ((ctx.keep_going != 0) && + (ssh_is_connected(s))) { + rc = ssh_event_dopoll(e, 1000 /* milliseconds */); + if (rc == SSH_ERROR) { + /* log, but don't consider this fatal */ + pkdout("ssh_event_dopoll for session connection: %s\n", ssh_get_error(s)); + rc = 0; + break; + } else { + rc = 0; + } + } + goto out; + +outclose: + close(fd); +out: + if (c != NULL) { + ssh_channel_free(c); + } + if (e != NULL) { + ssh_event_remove_session(e, s); + ssh_event_free(e); + } + if (s != NULL) { + ssh_disconnect(s); + ssh_free(s); + } + if (b != NULL) { + ssh_bind_free(b); + } + return rc; +} + +/* + * main loop ------------------------------------------------------------ + */ + +static void *pkd_main(void *args) { + int rc = -1; + struct pkd_daemon_args *a = (struct pkd_daemon_args *) args; + + struct sigaction act = { .sa_handler = pkd_sighandler, }; + + pkd_state.server_fd = -1; + pkd_state.req_exec_received = 0; + pkd_state.close_received = 0; + pkd_state.eof_received = 0; + + /* SIGUSR1 is used to interrupt 'pkd_accept_fd'. */ + rc = sigaction(SIGUSR1, &act, NULL); + if (rc != 0) { + pkderr("sigaction: %d\n", rc); + goto out; + } + + /* Ignore SIGPIPE */ + signal(SIGPIPE, SIG_IGN); + + rc = pkd_init_libssh(); + if (rc != 0) { + pkderr("pkd_init_libssh: %d\n", rc); + goto out; + } + + rc = pkd_init_server_fd(1234); + if (rc != 0) { + pkderr("pkd_init_server_fd: %d\n", rc); + goto out; + } + + ctx.pkd_ready = 1; + + while (ctx.keep_going != 0) { + int fd = pkd_accept_fd(); + if (fd < 0) { + if (ctx.keep_going != 0) { + pkderr("pkd_accept_fd"); + rc = -1; + } else { + rc = 0; + } + break; + } + + rc = pkd_exec_hello(fd, a); + if (rc != 0) { + pkderr("pkd_exec_hello: %d\n", rc); + break; + } + } + + if (pkd_state.server_fd != -1) { + close(pkd_state.server_fd); + } + pkd_state.server_fd = -1; +out: + ctx.rc = rc; + + return NULL; +} + +/* + * pkd start and stop used by setup/teardown test scaffolding ----------- + */ + +int pkd_start(struct pkd_daemon_args *args) { + int rc = 0; + + pkdout_enabled = args->opts.log_stdout; + pkderr_enabled = args->opts.log_stderr; + + /* Initialize the pkd context. */ + ctx.rc = -1; + ctx.keep_going = 1; + ctx.pkd_ready = 0; + rc = pthread_create(&ctx.tid, NULL, &pkd_main, args); + assert_int_equal(rc, 0); + + /* Busy-spin until pkd thread is ready. */ + while (ctx.pkd_ready == 0); + + return rc; +} + +void pkd_stop(struct pkd_result *out) { + int rc = 0; + + ctx.keep_going = 0; + close(pkd_state.server_fd); + + rc = pthread_kill(ctx.tid, SIGUSR1); + assert_int_equal(rc, 0); + + rc = pthread_join(ctx.tid, NULL); + assert_int_equal(rc, 0); + + assert_non_null(out); + out->ok = (ctx.rc == 0); + + return; +} diff --git a/tests/pkd/pkd_daemon.h b/tests/pkd/pkd_daemon.h new file mode 100644 index 0000000..493326c --- /dev/null +++ b/tests/pkd/pkd_daemon.h @@ -0,0 +1,57 @@ +/* + * pkd_daemon.h -- tests use this interface to start, stop pkd + * instances and get results + * + * (c) 2014 Jon Simons + */ + +#ifndef __PKD_DAEMON_H__ +#define __PKD_DAEMON_H__ + +#include "config.h" + +enum pkd_hostkey_type_e { + PKD_RSA, +#ifdef HAVE_DSA + PKD_DSA, +#endif + PKD_ED25519, + PKD_ECDSA +}; + +struct pkd_daemon_args { + enum pkd_hostkey_type_e type; + const char *hostkeypath; + + struct { + const uint8_t *buf; + size_t len; + } payload; + + uint64_t rekey_data_limit; + + struct { + int list; + + int log_stdout; + int log_stderr; + int libssh_log_level; + + const char *testname; + const char *testmatch; + unsigned int iterations; + + struct { + char *mkdtemp_str; + } socket_wrapper; + } opts; +}; + +struct pkd_result { + int ok; +}; + +int pkd_start(struct pkd_daemon_args *args); +void pkd_stop(struct pkd_result *out); + +#endif /* __PKD_DAEMON_H__ */ diff --git a/tests/pkd/pkd_hello.c b/tests/pkd/pkd_hello.c new file mode 100644 index 0000000..9ba4587 --- /dev/null +++ b/tests/pkd/pkd_hello.c @@ -0,0 +1,1055 @@ +/* + * pkd_hello.c -- + * + * (c) 2014, 2017-2018 Jon Simons + */ +#include "config.h" + +#include // for cmocka +#include // for cmocka +#include +#include +#include // for cmocka +#include + +#include "libssh/priv.h" +#include "torture.h" // for ssh_fips_mode() + +#include "pkd_client.h" +#include "pkd_daemon.h" +#include "pkd_keyutil.h" +#include "pkd_util.h" + +#define DEFAULT_ITERATIONS 10 +static struct pkd_daemon_args pkd_dargs; + +static uint8_t default_payload_buf[] = { + 'h', 'e', 'l', 'l', 'o', '\n', +}; + +static size_t default_payload_len = sizeof(default_payload_buf); + +#ifdef HAVE_ARGP_H +#include +#define PROGNAME "pkd_hello" +#define ARGP_PROGNAME "libssh " PROGNAME +const char *argp_program_version = ARGP_PROGNAME " 2017-07-12"; +const char *argp_program_bug_address = "Jon Simons "; + +static char doc[] = \ + "\nExample usage:\n\n" + " " PROGNAME "\n" + " Run all tests with default number of iterations.\n" + " " PROGNAME " --list\n" + " List available individual test names.\n" + " " PROGNAME " -i 1000 -t torture_pkd_rsa_ecdh_sha2_nistp256\n" + " Run only the torture_pkd_rsa_ecdh_sha2_nistp256 testcase 1000 times.\n" + " " PROGNAME " -i 1000 -m curve25519\n" + " Run all tests with the string 'curve25519' 1000 times.\n" + " " PROGNAME " -v -v -v -v -e -o\n" + " Run all tests with maximum libssh and pkd logging.\n" +; + +static struct argp_option options[] = { + { "buffer", 'b', "string", 0, + "Use the given string for test buffer payload contents", 0 }, + { "stderr", 'e', NULL, 0, + "Emit pkd stderr messages", 0 }, + { "list", 'l', NULL, 0, + "List available individual test names", 0 }, + { "iterations", 'i', "number", 0, + "Run each test for the given number of iterations (default is 10)", 0 }, + { "match", 'm', "testmatch", 0, + "Run all tests with the given string", 0 }, + { "socket-wrapper-dir", 'w', "", 0, + "Run in socket-wrapper mode using the given mkdtemp directory template", 0 }, + { "stdout", 'o', NULL, 0, + "Emit pkd stdout messages", 0 }, + { "rekey", 'r', "limit", 0, + "Set the given rekey data limit, in bytes, using SSH_OPTIONS_REKEY_DATA", 0 }, + { "test", 't', "testname", 0, + "Run tests matching the given testname", 0 }, + { "verbose", 'v', NULL, 0, + "Increase libssh verbosity (can be used multiple times)", 0 }, + { NULL, 0, NULL, 0, + NULL, 0 }, +}; + +static error_t parse_opt(int key, char *arg, struct argp_state *state) { + (void) arg; + (void) state; + + switch(key) { + case 'b': + pkd_dargs.payload.buf = (uint8_t *) arg; + pkd_dargs.payload.len = strlen(arg); + break; + case 'e': + pkd_dargs.opts.log_stderr = 1; + break; + case 'l': + pkd_dargs.opts.list = 1; + break; + case 'i': + pkd_dargs.opts.iterations = atoi(arg); + break; + case 'm': + pkd_dargs.opts.testmatch = arg; + break; + case 'o': + pkd_dargs.opts.log_stdout = 1; + break; + case 'r': + pkd_dargs.rekey_data_limit = atoi(arg); + break; + case 't': + pkd_dargs.opts.testname = arg; + break; + case 'v': + pkd_dargs.opts.libssh_log_level += 1; + break; + case 'w': + pkd_dargs.opts.socket_wrapper.mkdtemp_str = arg; + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +static struct argp parser = { + options, + parse_opt, + NULL, + doc, + NULL, + NULL, + NULL +}; +#endif /* HAVE_ARGP_H */ + +static struct pkd_state *torture_pkd_setup(enum pkd_hostkey_type_e type, + const char *hostkeypath) { + int rc = 0; + + pkd_dargs.type = type; + pkd_dargs.hostkeypath = hostkeypath; + + rc = pkd_start(&pkd_dargs); + assert_int_equal(rc, 0); + + return NULL; +} + +static int torture_pkd_teardown(void **state) { + struct pkd_result result = { .ok = 0 }; + + (void) state; + + pkd_stop(&result); + assert_int_equal(result.ok, 1); + + return 0; +} + +/* + * one setup for each server keytype ------------------------------------ + */ + +static int torture_pkd_setup_noop(void **state) { + *state = (void *) torture_pkd_setup(PKD_RSA, NULL /*path*/); + + return 0; +} + +static int torture_pkd_setup_rsa(void **state) { + setup_rsa_key(); + *state = (void *) torture_pkd_setup(PKD_RSA, LIBSSH_RSA_TESTKEY); + + return 0; +} + +static int torture_pkd_setup_ed25519(void **state) { + setup_ed25519_key(); + *state = (void *) torture_pkd_setup(PKD_ED25519, LIBSSH_ED25519_TESTKEY); + + return 0; +} + +#ifdef HAVE_DSA +static int torture_pkd_setup_dsa(void **state) { + setup_dsa_key(); + *state = (void *) torture_pkd_setup(PKD_DSA, LIBSSH_DSA_TESTKEY); + + return 0; +} +#endif + +static int torture_pkd_setup_ecdsa_256(void **state) { + setup_ecdsa_keys(); + *state = (void *) torture_pkd_setup(PKD_ECDSA, LIBSSH_ECDSA_256_TESTKEY); + + return 0; +} + +static int torture_pkd_setup_ecdsa_384(void **state) { + setup_ecdsa_keys(); + *state = (void *) torture_pkd_setup(PKD_ECDSA, LIBSSH_ECDSA_384_TESTKEY); + + return 0; +} + +static int torture_pkd_setup_ecdsa_521(void **state) { + setup_ecdsa_keys(); + *state = (void *) torture_pkd_setup(PKD_ECDSA, LIBSSH_ECDSA_521_TESTKEY); + + return 0; +} + +/* + * Test matrices: f(clientname, testname, ssh-command, setup-function, teardown-function). + */ + +#define PKDTESTS_DEFAULT_FIPS(f, client, cmd) \ + f(client, rsa_default, cmd, setup_rsa, teardown) \ + f(client, ecdsa_256_default, cmd, setup_ecdsa_256, teardown) \ + f(client, ecdsa_384_default, cmd, setup_ecdsa_384, teardown) \ + f(client, ecdsa_521_default, cmd, setup_ecdsa_521, teardown) + +#ifdef HAVE_DSA +#define PKDTESTS_DEFAULT(f, client, cmd) \ + /* Default passes by server key type. */ \ + PKDTESTS_DEFAULT_FIPS(f, client, cmd) \ + f(client, dsa_default, cmd, setup_dsa, teardown) +#else +#define PKDTESTS_DEFAULT(f, client, cmd) \ + /* Default passes by server key type. */ \ + PKDTESTS_DEFAULT_FIPS(f, client, cmd) +#endif + +#define PKDTESTS_DEFAULT_OPENSSHONLY(f, client, cmd) \ + /* Default passes by server key type. */ \ + f(client, ed25519_default, cmd, setup_ed25519, teardown) + +#define GEX_SHA256 "diffie-hellman-group-exchange-sha256" +#define GEX_SHA1 "diffie-hellman-group-exchange-sha1" + +#if defined(WITH_GEX) +#define PKDTESTS_KEX_FIPS(f, client, kexcmd) \ + f(client, rsa_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_rsa, teardown) \ + f(client, rsa_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_rsa, teardown) \ + f(client, rsa_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_rsa, teardown) \ + f(client, rsa_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_rsa, teardown) \ + f(client, rsa_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_rsa, teardown) \ + f(client, ecdsa_256_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_384_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_521_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_521, teardown) \ + f(client, rsa_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_rsa, teardown) \ + f(client, ecdsa_256_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ecdsa_256, teardown) \ + f(client, ecdsa_384_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ecdsa_384, teardown) \ + f(client, ecdsa_521_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ecdsa_521, teardown) +#else /* !defined(WITH_GEX) */ +#define PKDTESTS_KEX_FIPS(f, client, kexcmd) \ + f(client, rsa_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_rsa, teardown) \ + f(client, rsa_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_rsa, teardown) \ + f(client, rsa_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_rsa, teardown) \ + f(client, rsa_diffie_hellman_group14_sha256, kexcmd("diffie-hellman-group14-sha256"), setup_rsa, teardown) \ + f(client, rsa_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_rsa, teardown) \ + f(client, rsa_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_rsa, teardown) \ + f(client, ecdsa_256_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_diffie_hellman_group14_sha256,kexcmd("diffie-hellman-group14-sha256"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_384_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_diffie_hellman_group14_sha256,kexcmd("diffie-hellman-group14-sha256"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_521_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_diffie_hellman_group14_sha256,kexcmd("diffie-hellman-group14-sha256"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_521, teardown) +#endif + +#define PKDTESTS_KEX_COMMON(f, client, kexcmd) \ + PKDTESTS_KEX_FIPS(f, client, kexcmd) \ + f(client, rsa_curve25519_sha256, kexcmd("curve25519-sha256"), setup_rsa, teardown) \ + f(client, rsa_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_rsa, teardown) \ + f(client, rsa_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_rsa, teardown) \ + f(client, rsa_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_rsa, teardown) \ + f(client, ecdsa_256_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_384_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_521_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ecdsa_521, teardown) + +#if defined(HAVE_DSA) && defined(WITH_GEX) + /* GEX_SHA256 with RSA and ECDSA is included in PKDTESTS_KEX_FIPS if available */ +#define PKDTESTS_KEX(f, client, kexcmd) \ + /* Kex algorithms. */ \ + PKDTESTS_KEX_COMMON(f, client, kexcmd) \ + f(client, rsa_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_rsa, teardown) \ + f(client, dsa_curve25519_sha256, kexcmd("curve25519-sha256"), setup_dsa, teardown) \ + f(client, dsa_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_dsa, teardown) \ + f(client, dsa_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256 "), setup_dsa, teardown) \ + f(client, dsa_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384 "), setup_dsa, teardown) \ + f(client, dsa_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521 "), setup_dsa, teardown) \ + f(client, dsa_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_dsa, teardown) \ + f(client, dsa_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_dsa, teardown) \ + f(client, dsa_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_dsa, teardown) \ + f(client, dsa_diffie_hellman_group14_sha256, kexcmd("diffie-hellman-group14-sha256"), setup_dsa, teardown) \ + f(client, dsa_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_dsa, teardown) \ + f(client, dsa_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_dsa, teardown) \ + f(client, dsa_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_dsa, teardown) \ + f(client, ecdsa_256_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_256, teardown) \ + f(client, ecdsa_384_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_384, teardown) \ + f(client, ecdsa_521_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_521, teardown) + +#elif defined(HAVE_DSA) /* && !defined(WITH_GEX) */ +#define PKDTESTS_KEX(f, client, kexcmd) \ + /* Kex algorithms. */ \ + PKDTESTS_KEX_COMMON(f, client, kexcmd) \ + f(client, dsa_curve25519_sha256, kexcmd("curve25519-sha256"), setup_dsa, teardown) \ + f(client, dsa_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_dsa, teardown) \ + f(client, dsa_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256 "), setup_dsa, teardown) \ + f(client, dsa_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384 "), setup_dsa, teardown) \ + f(client, dsa_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521 "), setup_dsa, teardown) \ + f(client, dsa_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_dsa, teardown) \ + f(client, dsa_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_dsa, teardown) \ + f(client, dsa_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_dsa, teardown) \ + f(client, dsa_diffie_hellman_group14_sha256, kexcmd("diffie-hellman-group14-sha256"), setup_dsa, teardown) \ + f(client, dsa_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_dsa, teardown) + +#elif defined(WITH_GEX) /* && !defined(HAVE_DSA) */ + /* GEX_SHA256 is included in PKDTESTS_KEX_FIPS if available */ +#define PKDTESTS_KEX(f, client, kexcmd) \ + /* Kex algorithms. */ \ + PKDTESTS_KEX_COMMON(f, client, kexcmd) \ + f(client, rsa_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_rsa, teardown) \ + f(client, ecdsa_256_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_256, teardown) \ + f(client, ecdsa_384_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_384, teardown) \ + f(client, ecdsa_521_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_521, teardown) +#else +#define PKDTESTS_KEX(f, client, kexcmd) \ + /* Kex algorithms. */ \ + PKDTESTS_KEX_COMMON(f, client, kexcmd) +#endif + +#ifdef HAVE_DSA +#define PKDTESTS_KEX_OPENSSHONLY(f, client, kexcmd) \ + /* Kex algorithms. */ \ + f(client, ed25519_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ed25519, teardown) \ + f(client, ed25519_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ed25519, teardown) \ + f(client, ed25519_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ed25519, teardown) \ + f(client, ed25519_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ed25519, teardown) \ + f(client, ed25519_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ed25519, teardown) \ + f(client, ed25519_diffie_hellman_group14_sha256, kexcmd("diffie-hellman-group14-sha256"), setup_ed25519, teardown) \ + f(client, ed25519_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_ed25519, teardown) \ + f(client, ed25519_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_ed25519, teardown) \ + f(client, ed25519_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_ed25519, teardown) \ + f(client, ed25519_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ed25519, teardown) \ + f(client, ed25519_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ed25519, teardown) \ + f(client, ed25519_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ed25519, teardown) +#else +#define PKDTESTS_KEX_OPENSSHONLY(f, client, kexcmd) \ + /* Kex algorithms. */ \ + f(client, ed25519_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ed25519, teardown) \ + f(client, ed25519_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ed25519, teardown) \ + f(client, ed25519_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ed25519, teardown) \ + f(client, ed25519_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ed25519, teardown) \ + f(client, ed25519_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ed25519, teardown) \ + f(client, ed25519_diffie_hellman_group14_sha256, kexcmd("diffie-hellman-group14-sha256"), setup_ed25519, teardown) \ + f(client, ed25519_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_ed25519, teardown) \ + f(client, ed25519_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_ed25519, teardown) \ + f(client, ed25519_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ed25519, teardown) \ + f(client, ed25519_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ed25519, teardown) \ + f(client, ed25519_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ed25519, teardown) +#endif + + +#define PKDTESTS_CIPHER_FIPS(f, client, ciphercmd) \ + f(client, rsa_aes128_cbc, ciphercmd("aes128-cbc"), setup_rsa, teardown) \ + f(client, rsa_aes128_ctr, ciphercmd("aes128-ctr"), setup_rsa, teardown) \ + f(client, rsa_aes256_cbc, ciphercmd("aes256-cbc"), setup_rsa, teardown) \ + f(client, rsa_aes256_ctr, ciphercmd("aes256-ctr"), setup_rsa, teardown) \ + f(client, ecdsa_256_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_aes128_ctr, ciphercmd("aes128-ctr"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_aes256_ctr, ciphercmd("aes256-ctr"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_384_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_aes128_ctr, ciphercmd("aes128-ctr"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_aes256_ctr, ciphercmd("aes256-ctr"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_521_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_aes128_ctr, ciphercmd("aes128-ctr"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_aes256_ctr, ciphercmd("aes256-ctr"), setup_ecdsa_521, teardown) + +#ifdef HAVE_DSA +#define PKDTESTS_CIPHER(f, client, ciphercmd) \ + /* Ciphers. */ \ + PKDTESTS_CIPHER_FIPS(f, client, ciphercmd) \ + f(client, rsa_3des_cbc, ciphercmd("3des-cbc"), setup_rsa, teardown) \ + f(client, dsa_3des_cbc, ciphercmd("3des-cbc"), setup_dsa, teardown) \ + f(client, dsa_aes128_cbc, ciphercmd("aes128-cbc"), setup_dsa, teardown) \ + f(client, dsa_aes128_ctr, ciphercmd("aes128-ctr"), setup_dsa, teardown) \ + f(client, dsa_aes256_cbc, ciphercmd("aes256-cbc"), setup_dsa, teardown) \ + f(client, dsa_aes256_ctr, ciphercmd("aes256-ctr"), setup_dsa, teardown) \ + f(client, ecdsa_256_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_384_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_521_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_521, teardown) +#else +#define PKDTESTS_CIPHER(f, client, ciphercmd) \ + /* Ciphers. */ \ + PKDTESTS_CIPHER_FIPS(f, client, ciphercmd) \ + f(client, rsa_3des_cbc, ciphercmd("3des-cbc"), setup_rsa, teardown) \ + f(client, ecdsa_256_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_384_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_521_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_521, teardown) +#endif + +#define CHACHA20 "chacha20-poly1305@openssh.com" +#define AES128_GCM "aes128-gcm@openssh.com" +#define AES256_GCM "aes256-gcm@openssh.com" + +#define PKDTESTS_CIPHER_OPENSSHONLY_FIPS(f, client, ciphercmd) \ + f(client, rsa_aes128_gcm, ciphercmd(AES128_GCM), setup_rsa, teardown) \ + f(client, rsa_aes256_gcm, ciphercmd(AES256_GCM), setup_rsa, teardown) \ + f(client, ecdsa_256_aes128_gcm, ciphercmd(AES128_GCM), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_aes256_gcm, ciphercmd(AES256_GCM), setup_ecdsa_256, teardown) \ + f(client, ecdsa_384_aes128_gcm, ciphercmd(AES128_GCM), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_aes256_gcm, ciphercmd(AES256_GCM), setup_ecdsa_384, teardown) \ + f(client, ecdsa_521_aes128_gcm, ciphercmd(AES128_GCM), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_aes256_gcm, ciphercmd(AES256_GCM), setup_ecdsa_521, teardown) + +#ifdef HAVE_DSA +#define PKDTESTS_CIPHER_OPENSSHONLY(f, client, ciphercmd) \ + /* Ciphers. */ \ + PKDTESTS_CIPHER_OPENSSHONLY_FIPS(f, client, ciphercmd) \ + f(client, rsa_aes192_cbc, ciphercmd("aes192-cbc"), setup_rsa, teardown) \ + f(client, rsa_aes192_ctr, ciphercmd("aes192-ctr"), setup_rsa, teardown) \ + f(client, rsa_chacha20, ciphercmd(CHACHA20), setup_rsa, teardown) \ + f(client, dsa_aes192_cbc, ciphercmd("aes192-cbc"), setup_dsa, teardown) \ + f(client, dsa_aes192_ctr, ciphercmd("aes192-ctr"), setup_dsa, teardown) \ + f(client, dsa_chacha20, ciphercmd(CHACHA20), setup_dsa, teardown) \ + f(client, dsa_aes128_gcm, ciphercmd(AES128_GCM), setup_dsa, teardown) \ + f(client, dsa_aes256_gcm, ciphercmd(AES256_GCM), setup_dsa, teardown) \ + f(client, ed25519_3des_cbc, ciphercmd("3des-cbc"), setup_ed25519, teardown) \ + f(client, ed25519_aes128_cbc, ciphercmd("aes128-cbc"), setup_ed25519, teardown) \ + f(client, ed25519_aes128_ctr, ciphercmd("aes128-ctr"), setup_ed25519, teardown) \ + f(client, ed25519_aes256_cbc, ciphercmd("aes256-cbc"), setup_ed25519, teardown) \ + f(client, ed25519_aes256_ctr, ciphercmd("aes256-ctr"), setup_ed25519, teardown) \ + f(client, ed25519_aes192_cbc, ciphercmd("aes192-cbc"), setup_ed25519, teardown) \ + f(client, ed25519_aes192_ctr, ciphercmd("aes192-ctr"), setup_ed25519, teardown) \ + f(client, ed25519_chacha20, ciphercmd(CHACHA20), setup_ed25519, teardown) \ + f(client, ed25519_aes128_gcm, ciphercmd(AES128_GCM), setup_ed25519, teardown) \ + f(client, ed25519_aes256_gcm, ciphercmd(AES256_GCM), setup_ed25519, teardown) \ + f(client, ecdsa_256_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_aes192_ctr, ciphercmd("aes192-ctr"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_chacha20, ciphercmd(CHACHA20), setup_ecdsa_256, teardown) \ + f(client, ecdsa_384_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_aes192_ctr, ciphercmd("aes192-ctr"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_chacha20, ciphercmd(CHACHA20), setup_ecdsa_384, teardown) \ + f(client, ecdsa_521_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_aes192_ctr, ciphercmd("aes192-ctr"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_chacha20, ciphercmd(CHACHA20), setup_ecdsa_521, teardown) +#else +#define PKDTESTS_CIPHER_OPENSSHONLY(f, client, ciphercmd) \ + /* Ciphers. */ \ + PKDTESTS_CIPHER_OPENSSHONLY_FIPS(f, client, ciphercmd) \ + f(client, rsa_aes192_cbc, ciphercmd("aes192-cbc"), setup_rsa, teardown) \ + f(client, rsa_aes192_ctr, ciphercmd("aes192-ctr"), setup_rsa, teardown) \ + f(client, rsa_chacha20, ciphercmd(CHACHA20), setup_rsa, teardown) \ + f(client, ed25519_3des_cbc, ciphercmd("3des-cbc"), setup_ed25519, teardown) \ + f(client, ed25519_aes128_cbc, ciphercmd("aes128-cbc"), setup_ed25519, teardown) \ + f(client, ed25519_aes128_ctr, ciphercmd("aes128-ctr"), setup_ed25519, teardown) \ + f(client, ed25519_aes256_cbc, ciphercmd("aes256-cbc"), setup_ed25519, teardown) \ + f(client, ed25519_aes256_ctr, ciphercmd("aes256-ctr"), setup_ed25519, teardown) \ + f(client, ed25519_aes192_cbc, ciphercmd("aes192-cbc"), setup_ed25519, teardown) \ + f(client, ed25519_aes192_ctr, ciphercmd("aes192-ctr"), setup_ed25519, teardown) \ + f(client, ed25519_chacha20, ciphercmd(CHACHA20), setup_ed25519, teardown) \ + f(client, ecdsa_256_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_aes192_ctr, ciphercmd("aes192-ctr"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_chacha20, ciphercmd(CHACHA20), setup_ecdsa_256, teardown) \ + f(client, ecdsa_384_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_aes192_ctr, ciphercmd("aes192-ctr"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_chacha20, ciphercmd(CHACHA20), setup_ecdsa_384, teardown) \ + f(client, ecdsa_521_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_aes192_ctr, ciphercmd("aes192-ctr"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_chacha20, ciphercmd(CHACHA20), setup_ecdsa_521, teardown) +#endif + + +#define PKDTESTS_MAC_FIPS(f, client, maccmd) \ + f(client, ecdsa_256_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_384_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_521_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_521, teardown) \ + f(client, rsa_hmac_sha1, maccmd("hmac-sha1"), setup_rsa, teardown) \ + f(client, rsa_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_rsa, teardown) + +#define PKDTESTS_MAC_OPENSSHONLY_FIPS(f, client, maccmd) \ + f(client, ecdsa_256_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_256_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_384_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_384_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_521_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ecdsa_521, teardown) \ + f(client, ecdsa_521_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ecdsa_521, teardown) \ + f(client, rsa_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_rsa, teardown) \ + f(client, rsa_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_rsa, teardown) \ + f(client, rsa_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_rsa, teardown) \ + f(client, rsa_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_rsa, teardown) + +#ifdef HAVE_DSA +#define PKDTESTS_MAC(f, client, maccmd) \ + /* MACs. */ \ + PKDTESTS_MAC_FIPS(f, client, maccmd) \ + f(client, dsa_hmac_sha1, maccmd("hmac-sha1"), setup_dsa, teardown) \ + f(client, dsa_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_dsa, teardown) +#define PKDTESTS_MAC_OPENSSHONLY(f, client, maccmd) \ + PKDTESTS_MAC_OPENSSHONLY_FIPS(f, client, maccmd) \ + f(client, dsa_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_dsa, teardown) \ + f(client, dsa_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_dsa, teardown) \ + f(client, dsa_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_dsa, teardown) \ + f(client, dsa_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_dsa, teardown) \ + f(client, ed25519_hmac_sha1, maccmd("hmac-sha1"), setup_ed25519, teardown) \ + f(client, ed25519_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ed25519, teardown) \ + f(client, ed25519_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ed25519, teardown) \ + f(client, ed25519_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ed25519, teardown) \ + f(client, ed25519_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ed25519, teardown) \ + f(client, ed25519_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ed25519, teardown) +#else +#define PKDTESTS_MAC(f, client, maccmd) \ + /* MACs. */ \ + PKDTESTS_MAC_FIPS(f, client, maccmd) +#define PKDTESTS_MAC_OPENSSHONLY(f, client, maccmd) \ + PKDTESTS_MAC_OPENSSHONLY_FIPS(f, client, maccmd) \ + f(client, ed25519_hmac_sha1, maccmd("hmac-sha1"), setup_ed25519, teardown) \ + f(client, ed25519_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ed25519, teardown) \ + f(client, ed25519_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ed25519, teardown) \ + f(client, ed25519_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ed25519, teardown) \ + f(client, ed25519_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ed25519, teardown) \ + f(client, ed25519_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ed25519, teardown) +#endif + + +#define PKDTESTS_HOSTKEY_OPENSSHONLY_FIPS(f, client, hkcmd) \ + f(client, rsa_sha2_256, hkcmd("rsa-sha2-256"), setup_rsa, teardown) \ + f(client, rsa_sha2_512, hkcmd("rsa-sha2-512"), setup_rsa, teardown) \ + f(client, rsa_sha2_256_512, hkcmd("rsa-sha2-256,rsa-sha2-512"), setup_rsa, teardown) \ + f(client, rsa_sha2_512_256, hkcmd("rsa-sha2-512,rsa-sha2-256"), setup_rsa, teardown) + +#define PKDTESTS_HOSTKEY_OPENSSHONLY(f, client, hkcmd) \ + PKDTESTS_HOSTKEY_OPENSSHONLY_FIPS(f, client, hkcmd) + +static void torture_pkd_client_noop(void **state) { + struct pkd_state *pstate = (struct pkd_state *) (*state); + (void) pstate; + return; +} + +static void torture_pkd_runtest(const char *testname, + const char *testcmd) +{ + int i, rc; + char logfile[1024] = { 0 }; + int iterations = + (pkd_dargs.opts.iterations != 0) ? pkd_dargs.opts.iterations + : DEFAULT_ITERATIONS; + + for (i = 0; i < iterations; i++) { + rc = system_checked(testcmd); + assert_int_equal(rc, 0); + } + + /* Asserts did not trip: cleanup logs. */ + snprintf(&logfile[0], sizeof(logfile), "%s.out", testname); + unlink(logfile); + snprintf(&logfile[0], sizeof(logfile), "%s.err", testname); + unlink(logfile); +} + +/* + * Though each keytest function body is the same, separate functions are + * defined here to result in distinct output when running the tests. + */ + +#define emit_keytest(client, testname, sshcmd, setup, teardown) \ + static void torture_pkd_## client ## _ ## testname(void **state) { \ + const char *tname = "torture_pkd_" #client "_" #testname; \ + char testcmd[1024] = { 0 }; \ + (void) state; \ + snprintf(&testcmd[0], sizeof(testcmd), sshcmd, tname, tname); \ + torture_pkd_runtest(tname, testcmd); \ + } + +/* + * Actual test functions are emitted here. + */ + +#ifdef HAVE_DSA +#define CLIENT_ID_FILE OPENSSH_DSA_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, openssh_dsa, OPENSSH_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_cert_dsa, OPENSSH_CERT_CMD) +PKDTESTS_DEFAULT_OPENSSHONLY(emit_keytest, openssh_dsa, OPENSSH_CMD) +PKDTESTS_KEX(emit_keytest, openssh_dsa, OPENSSH_KEX_CMD) +PKDTESTS_KEX_OPENSSHONLY(emit_keytest, openssh_dsa, OPENSSH_KEX_CMD) +PKDTESTS_CIPHER(emit_keytest, openssh_dsa, OPENSSH_CIPHER_CMD) +PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_dsa, OPENSSH_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, openssh_dsa, OPENSSH_MAC_CMD) +PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_dsa, OPENSSH_MAC_CMD) +#undef CLIENT_ID_FILE +#endif + +#define CLIENT_ID_FILE OPENSSH_RSA_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, openssh_rsa, OPENSSH_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_cert_rsa, OPENSSH_CERT_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_sha256_cert_rsa, OPENSSH_SHA256_CERT_CMD) +PKDTESTS_DEFAULT_OPENSSHONLY(emit_keytest, openssh_rsa, OPENSSH_CMD) +PKDTESTS_KEX(emit_keytest, openssh_rsa, OPENSSH_KEX_CMD) +PKDTESTS_KEX_OPENSSHONLY(emit_keytest, openssh_rsa, OPENSSH_KEX_CMD) +PKDTESTS_CIPHER(emit_keytest, openssh_rsa, OPENSSH_CIPHER_CMD) +PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_rsa, OPENSSH_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, openssh_rsa, OPENSSH_MAC_CMD) +PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_rsa, OPENSSH_MAC_CMD) +PKDTESTS_HOSTKEY_OPENSSHONLY(emit_keytest, openssh_rsa, OPENSSH_HOSTKEY_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE OPENSSH_ECDSA256_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, openssh_e256, OPENSSH_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_cert_e256, OPENSSH_CERT_CMD) +PKDTESTS_DEFAULT_OPENSSHONLY(emit_keytest, openssh_e256, OPENSSH_CMD) +PKDTESTS_KEX(emit_keytest, openssh_e256, OPENSSH_KEX_CMD) +PKDTESTS_KEX_OPENSSHONLY(emit_keytest, openssh_e256, OPENSSH_KEX_CMD) +PKDTESTS_CIPHER(emit_keytest, openssh_e256, OPENSSH_CIPHER_CMD) +PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_e256, OPENSSH_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, openssh_e256, OPENSSH_MAC_CMD) +PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_e256, OPENSSH_MAC_CMD) +#undef CLIENT_ID_FILE + +/* Could add these passes, too: */ +//#define CLIENT_ID_FILE OPENSSH_ECDSA384_TESTKEY +//#define CLIENT_ID_FILE OPENSSH_ECDSA521_TESTKEY + +#define CLIENT_ID_FILE OPENSSH_ED25519_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, openssh_ed, OPENSSH_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_cert_ed, OPENSSH_CERT_CMD) +PKDTESTS_DEFAULT_OPENSSHONLY(emit_keytest, openssh_ed, OPENSSH_CMD) +PKDTESTS_KEX(emit_keytest, openssh_ed, OPENSSH_KEX_CMD) +PKDTESTS_KEX_OPENSSHONLY(emit_keytest, openssh_ed, OPENSSH_KEX_CMD) +PKDTESTS_CIPHER(emit_keytest, openssh_ed, OPENSSH_CIPHER_CMD) +PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_ed, OPENSSH_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, openssh_ed, OPENSSH_MAC_CMD) +PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_ed, OPENSSH_MAC_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE DROPBEAR_RSA_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, dropbear, DROPBEAR_CMD) +PKDTESTS_CIPHER(emit_keytest, dropbear, DROPBEAR_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, dropbear, DROPBEAR_MAC_CMD) +#undef CLIENT_ID_FILE + +/* + * Define an array of testname strings mapped to their associated + * test function. Enables running tests individually by name from + * the command line. + */ + +#define emit_testmap(client, testname, sshcmd, setup, teardown) \ + { "torture_pkd_" #client "_" #testname, \ + emit_unit_test(client, testname, sshcmd, setup, teardown) }, + +#define emit_unit_test(client, testname, sshcmd, setup, teardown) \ + cmocka_unit_test_setup_teardown(torture_pkd_ ## client ## _ ## testname, \ + torture_pkd_ ## setup, \ + torture_pkd_ ## teardown) + +#define emit_unit_test_comma(client, testname, sshcmd, setup, teardown) \ + emit_unit_test(client, testname, sshcmd, setup, teardown), + +struct { + const char *testname; + const struct CMUnitTest test; +} testmap[] = { + /* OpenSSH */ +#ifdef HAVE_DSA + PKDTESTS_DEFAULT(emit_testmap, openssh_dsa, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_testmap, openssh_cert_dsa, OPENSSH_CERT_CMD) + PKDTESTS_DEFAULT_OPENSSHONLY(emit_testmap, openssh_dsa, OPENSSH_CMD) + PKDTESTS_KEX(emit_testmap, openssh_dsa, OPENSSH_KEX_CMD) + PKDTESTS_KEX_OPENSSHONLY(emit_testmap, openssh_dsa, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_testmap, openssh_dsa, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_testmap, openssh_dsa, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, openssh_dsa, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_testmap, openssh_dsa, OPENSSH_MAC_CMD) +#endif + + PKDTESTS_DEFAULT(emit_testmap, openssh_rsa, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_testmap, openssh_cert_rsa, OPENSSH_CERT_CMD) + PKDTESTS_DEFAULT(emit_testmap, openssh_sha256_cert_rsa, OPENSSH_SHA256_CERT_CMD) + PKDTESTS_DEFAULT_OPENSSHONLY(emit_testmap, openssh_rsa, OPENSSH_CMD) + PKDTESTS_KEX(emit_testmap, openssh_rsa, OPENSSH_KEX_CMD) + PKDTESTS_KEX_OPENSSHONLY(emit_testmap, openssh_rsa, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_testmap, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_testmap, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, openssh_rsa, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_testmap, openssh_rsa, OPENSSH_MAC_CMD) + PKDTESTS_HOSTKEY_OPENSSHONLY(emit_testmap, openssh_rsa, OPENSSH_HOSTKEY_CMD) + + PKDTESTS_DEFAULT(emit_testmap, openssh_e256, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_testmap, openssh_cert_e256, OPENSSH_CERT_CMD) + PKDTESTS_DEFAULT_OPENSSHONLY(emit_testmap, openssh_e256, OPENSSH_CMD) + PKDTESTS_KEX(emit_testmap, openssh_e256, OPENSSH_KEX_CMD) + PKDTESTS_KEX_OPENSSHONLY(emit_testmap, openssh_e256, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_testmap, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_testmap, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, openssh_e256, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_testmap, openssh_e256, OPENSSH_MAC_CMD) + + PKDTESTS_DEFAULT(emit_testmap, openssh_ed, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_testmap, openssh_cert_ed, OPENSSH_CERT_CMD) + PKDTESTS_DEFAULT_OPENSSHONLY(emit_testmap, openssh_ed, OPENSSH_CMD) + PKDTESTS_KEX(emit_testmap, openssh_ed, OPENSSH_KEX_CMD) + PKDTESTS_KEX_OPENSSHONLY(emit_testmap, openssh_ed, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_testmap, openssh_ed, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_testmap, openssh_ed, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, openssh_ed, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_testmap, openssh_ed, OPENSSH_MAC_CMD) + + /* Dropbear */ + PKDTESTS_DEFAULT(emit_testmap, dropbear, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_testmap, dropbear, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, dropbear, DROPBEAR_MAC_CMD) + + /* Noop */ + emit_testmap(client, noop, "", setup_noop, teardown) + + /* NULL tail entry */ + { .testname = NULL, + .test = { .name = NULL, + .test_func = NULL, + .setup_func = NULL, + .teardown_func = NULL } } +}; + +static int pkd_run_tests(void) { + int rc = -1; + int tindex = 0; + + const struct CMUnitTest openssh_tests[] = { +#ifdef HAVE_DSA + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_dsa, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_dsa, OPENSSH_CERT_CMD) + PKDTESTS_DEFAULT_OPENSSHONLY(emit_unit_test_comma, openssh_dsa, OPENSSH_CMD) + PKDTESTS_KEX(emit_unit_test_comma, openssh_dsa, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, openssh_dsa, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_dsa, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, openssh_dsa, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_dsa, OPENSSH_MAC_CMD) +#endif + + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_rsa, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_rsa, OPENSSH_CERT_CMD) + PKDTESTS_DEFAULT_FIPS(emit_unit_test_comma, openssh_sha256_cert_rsa, + OPENSSH_SHA256_CERT_CMD) + PKDTESTS_DEFAULT_OPENSSHONLY(emit_unit_test_comma, openssh_rsa, OPENSSH_CMD) + PKDTESTS_KEX(emit_unit_test_comma, openssh_rsa, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, openssh_rsa, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_rsa, OPENSSH_MAC_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_e256, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_e256, OPENSSH_CERT_CMD) + PKDTESTS_DEFAULT_OPENSSHONLY(emit_unit_test_comma, openssh_e256, OPENSSH_CMD) + PKDTESTS_KEX(emit_unit_test_comma, openssh_e256, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, openssh_e256, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_e256, OPENSSH_MAC_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_ed, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_ed, OPENSSH_CERT_CMD) + PKDTESTS_DEFAULT_OPENSSHONLY(emit_unit_test_comma, openssh_ed, OPENSSH_CMD) + PKDTESTS_KEX(emit_unit_test_comma, openssh_ed, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, openssh_ed, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_ed, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, openssh_ed, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_ed, OPENSSH_MAC_CMD) + }; + + const struct CMUnitTest dropbear_tests[] = { + PKDTESTS_DEFAULT(emit_unit_test_comma, dropbear, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, dropbear, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, dropbear, DROPBEAR_MAC_CMD) + }; + + const struct CMUnitTest openssh_fips_tests[] = { + PKDTESTS_DEFAULT_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_CMD) + PKDTESTS_DEFAULT_FIPS(emit_unit_test_comma, openssh_sha256_cert_rsa, + OPENSSH_SHA256_CERT_CMD) + PKDTESTS_KEX_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_MAC_CMD) + + PKDTESTS_DEFAULT_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_CMD) + PKDTESTS_DEFAULT_FIPS(emit_unit_test_comma, openssh_cert_e256, OPENSSH_CERT_CMD) + PKDTESTS_KEX_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_MAC_CMD) + }; + + const struct CMUnitTest noop_tests[] = { + emit_unit_test(client, noop, "", setup_noop, teardown) + }; + + /* Test list is populated depending on which clients are enabled. */ + struct CMUnitTest all_tests[(sizeof(openssh_tests) / sizeof(openssh_tests[0])) + + (sizeof(dropbear_tests) / sizeof(dropbear_tests[0])) + + (sizeof(noop_tests) / sizeof(noop_tests[0]))]; + memset(&all_tests[0], 0x0, sizeof(all_tests)); + + /* Generate client keys and populate test list for each enabled client. */ + if (is_openssh_client_enabled()) { + setup_openssh_client_keys(); + if (ssh_fips_mode()) { + memcpy(&all_tests[tindex], &openssh_fips_tests[0], sizeof(openssh_fips_tests)); + tindex += (sizeof(openssh_fips_tests) / sizeof(openssh_fips_tests[0])); + } else { + memcpy(&all_tests[tindex], &openssh_tests[0], sizeof(openssh_tests)); + tindex += (sizeof(openssh_tests) / sizeof(openssh_tests[0])); + } + } + + if (is_dropbear_client_enabled()) { + setup_dropbear_client_rsa_key(); + if (!ssh_fips_mode()) { + memcpy(&all_tests[tindex], &dropbear_tests[0], sizeof(dropbear_tests)); + tindex += (sizeof(dropbear_tests) / sizeof(dropbear_tests[0])); + } + } + + memcpy(&all_tests[tindex], &noop_tests[0], sizeof(noop_tests)); + tindex += (sizeof(noop_tests) / sizeof(noop_tests[0])); + + if ((pkd_dargs.opts.testname == NULL) && + (pkd_dargs.opts.testmatch == NULL)) { + rc = _cmocka_run_group_tests("all tests", all_tests, tindex, NULL, NULL); + } else { + size_t i = 0; + size_t num_found = 0; + const char *testname = pkd_dargs.opts.testname; + const char *testmatch = pkd_dargs.opts.testmatch; + + struct CMUnitTest matching_tests[sizeof(all_tests)]; + memset(&matching_tests[0], 0x0, sizeof(matching_tests)); + + while (testmap[i].testname != NULL) { + if ((testname != NULL) && + (strcmp(testmap[i].testname, testname) == 0)) { + memcpy(&matching_tests[0], + &testmap[i].test, + sizeof(struct CMUnitTest)); + num_found += 1; + break; + } + + if ((testmatch != NULL) && + (strstr(testmap[i].testname, testmatch) != NULL)) { + memcpy(&matching_tests[num_found], + &testmap[i].test, + sizeof(struct CMUnitTest)); + num_found += 1; + } + + i += 1; + } + + if (num_found > 0) { + rc = _cmocka_run_group_tests("found", matching_tests, num_found, NULL, NULL); + } else { + fprintf(stderr, "Did not find test '%s'\n", testname); + } + } + + /* Clean up client keys for each enabled client. */ + if (is_dropbear_client_enabled()) { + cleanup_dropbear_client_rsa_key(); + } + + if (is_openssh_client_enabled()) { + cleanup_openssh_client_keys(); + } + + /* Clean up any server keys that were generated. */ + cleanup_rsa_key(); + cleanup_ecdsa_keys(); + if (!ssh_fips_mode()) { + cleanup_ed25519_key(); +#ifdef HAVE_DSA + cleanup_dsa_key(); +#endif + } + + return rc; +} + +static int pkd_init_socket_wrapper(void) { + int rc = 0; + char *mkdtemp_str = NULL; + + if (pkd_dargs.opts.socket_wrapper.mkdtemp_str == NULL) { + goto out; + } + + mkdtemp_str = strdup(pkd_dargs.opts.socket_wrapper.mkdtemp_str); + if (mkdtemp_str == NULL) { + fprintf(stderr, "pkd_init_socket_wrapper strdup failed\n"); + goto errstrdup; + } + pkd_dargs.opts.socket_wrapper.mkdtemp_str = mkdtemp_str; + + if (mkdtemp(mkdtemp_str) == NULL) { + fprintf(stderr, "pkd_init_socket_wrapper mkdtemp '%s' failed\n", mkdtemp_str); + goto errmkdtemp; + } + + if (setenv("SOCKET_WRAPPER_DIR", mkdtemp_str, 1) != 0) { + fprintf(stderr, "pkd_init_socket_wrapper setenv failed\n"); + goto errsetenv; + } + + goto out; +errsetenv: +errmkdtemp: + free(mkdtemp_str); +errstrdup: + rc = -1; +out: + return rc; +} + +static int pkd_rmfiles(const char *path) { + char bin[1024] = { 0 }; + snprintf(&bin[0], sizeof(bin), "rm -f %s/*", path); + return system_checked(bin); +} + +static int pkd_cleanup_socket_wrapper(void) { + int rc = 0; + + if (pkd_dargs.opts.socket_wrapper.mkdtemp_str == NULL) { + goto out; + } + + /* clean up socket-wrapper unix domain sockets */ + if (pkd_rmfiles(pkd_dargs.opts.socket_wrapper.mkdtemp_str) != 0) { + fprintf(stderr, "pkd_cleanup_socket_wrapper pkd_rmfiles '%s' failed\n", + pkd_dargs.opts.socket_wrapper.mkdtemp_str); + goto errrmfiles; + } + + if (rmdir(pkd_dargs.opts.socket_wrapper.mkdtemp_str) != 0) { + fprintf(stderr, "pkd_cleanup_socket_wrapper rmdir '%s' failed\n", + pkd_dargs.opts.socket_wrapper.mkdtemp_str); + goto errrmdir; + } + + free(pkd_dargs.opts.socket_wrapper.mkdtemp_str); + + goto out; +errrmdir: +errrmfiles: + rc = -1; +out: + return rc; +} + +int main(int argc, char **argv) { + int i = 0; + int rc = 0; + int exit_code = -1; + + unsetenv("SSH_AUTH_SOCK"); + + pkd_dargs.payload.buf = default_payload_buf; + pkd_dargs.payload.len = default_payload_len; + + rc = ssh_init(); + if (rc != 0) { + goto out; + } + +#ifdef HAVE_ARGP_H + argp_parse(&parser, argc, argv, 0, 0, NULL); +#else /* HAVE_ARGP_H */ + (void) argc; (void) argv; +#endif /* HAVE_ARGP_H */ + + rc = pkd_init_socket_wrapper(); + if (rc != 0) { + fprintf(stderr, "pkd_init_socket_wrapper failed: %d\n", rc); + goto out_finalize; + } + + if (pkd_dargs.opts.list != 0) { + while (testmap[i].testname != NULL) { + printf("%s\n", testmap[i++].testname); + } + } else { + exit_code = pkd_run_tests(); + if (exit_code != 0) { + fprintf(stderr, "pkd_run_tests failed: %d\n", exit_code); + } + } + + rc = pkd_cleanup_socket_wrapper(); + if (rc != 0) { + fprintf(stderr, "pkd_cleanup_socket_wrapper failed: %d\n", rc); + } + +out_finalize: + rc = ssh_finalize(); + if (rc != 0) { + fprintf(stderr, "ssh_finalize: %d\n", rc); + } +out: + return exit_code; +} diff --git a/tests/pkd/pkd_keyutil.c b/tests/pkd/pkd_keyutil.c new file mode 100644 index 0000000..3991bcb --- /dev/null +++ b/tests/pkd/pkd_keyutil.c @@ -0,0 +1,213 @@ +/* + * pkd_keyutil.c -- pkd test key utilities + * + * (c) 2014 Jon Simons + */ + +#include "config.h" + +#include // for cmocka +#include // for cmocka +#include // for cmocka +#include + +#include +#include +#include +#include + +#include "torture.h" // for ssh_fips_mode() + +#include "pkd_client.h" +#include "pkd_keyutil.h" +#include "pkd_util.h" + +void setup_rsa_key() { + int rc = 0; + if (access(LIBSSH_RSA_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t rsa -q -N \"\" -f " + LIBSSH_RSA_TESTKEY); + } + assert_int_equal(rc, 0); +} + +void setup_ed25519_key() { + int rc = 0; + if (access(LIBSSH_ED25519_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ed25519 -q -N \"\" -f " + LIBSSH_ED25519_TESTKEY); + } + assert_int_equal(rc, 0); +} + +#ifdef HAVE_DSA +void setup_dsa_key() { + int rc = 0; + if (access(LIBSSH_DSA_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t dsa -q -N \"\" -f " + LIBSSH_DSA_TESTKEY); + } + assert_int_equal(rc, 0); +} +#endif + +void setup_ecdsa_keys() { + int rc = 0; + + if (access(LIBSSH_ECDSA_256_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 256 -q -N \"\" -f " + LIBSSH_ECDSA_256_TESTKEY); + assert_int_equal(rc, 0); + } + if (access(LIBSSH_ECDSA_384_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 384 -q -N \"\" -f " + LIBSSH_ECDSA_384_TESTKEY); + assert_int_equal(rc, 0); + } + if (access(LIBSSH_ECDSA_521_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 521 -q -N \"\" -f " + LIBSSH_ECDSA_521_TESTKEY); + assert_int_equal(rc, 0); + } +} + +void cleanup_rsa_key() { + cleanup_key(LIBSSH_RSA_TESTKEY); +} + +void cleanup_ed25519_key() { + cleanup_key(LIBSSH_ED25519_TESTKEY); +} + +#ifdef HAVE_DSA +void cleanup_dsa_key() { + cleanup_key(LIBSSH_DSA_TESTKEY); +} +#endif + +void cleanup_ecdsa_keys() { + cleanup_key(LIBSSH_ECDSA_256_TESTKEY); + cleanup_key(LIBSSH_ECDSA_384_TESTKEY); + cleanup_key(LIBSSH_ECDSA_521_TESTKEY); +} + +void setup_openssh_client_keys() { + int rc = 0; + + if (access(OPENSSH_CA_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t rsa -q -N \"\" -f " + OPENSSH_CA_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_RSA_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t rsa -q -N \"\" -f " + OPENSSH_RSA_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_RSA_TESTKEY "-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -s " OPENSSH_CA_TESTKEY " " + OPENSSH_RSA_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_RSA_TESTKEY "-sha256-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -t rsa-sha2-256 " + "-s " OPENSSH_CA_TESTKEY " " + OPENSSH_RSA_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA256_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 256 -q -N \"\" -f " + OPENSSH_ECDSA256_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA256_TESTKEY "-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -s " OPENSSH_CA_TESTKEY " " + OPENSSH_ECDSA256_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA384_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 384 -q -N \"\" -f " + OPENSSH_ECDSA384_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA384_TESTKEY "-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -s " OPENSSH_CA_TESTKEY " " + OPENSSH_ECDSA384_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA521_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 521 -q -N \"\" -f " + OPENSSH_ECDSA521_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA521_TESTKEY "-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -s " OPENSSH_CA_TESTKEY " " + OPENSSH_ECDSA521_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + + if (!ssh_fips_mode()) { +#ifdef HAVE_DSA + if (access(OPENSSH_DSA_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t dsa -q -N \"\" -f " + OPENSSH_DSA_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_DSA_TESTKEY "-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -s " OPENSSH_CA_TESTKEY + " " OPENSSH_DSA_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); +#endif + + if (access(OPENSSH_ED25519_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ed25519 -q -N \"\" -f " + OPENSSH_ED25519_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ED25519_TESTKEY "-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -s " OPENSSH_CA_TESTKEY " " + OPENSSH_ED25519_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + } +} + +void cleanup_openssh_client_keys() { + cleanup_key(OPENSSH_CA_TESTKEY); + cleanup_key(OPENSSH_RSA_TESTKEY); + cleanup_file(OPENSSH_RSA_TESTKEY "-sha256-cert.pub"); + cleanup_key(OPENSSH_ECDSA256_TESTKEY); + cleanup_key(OPENSSH_ECDSA384_TESTKEY); + cleanup_key(OPENSSH_ECDSA521_TESTKEY); + if (!ssh_fips_mode()) { + cleanup_key(OPENSSH_ED25519_TESTKEY); +#ifdef HAVE_DSA + cleanup_key(OPENSSH_DSA_TESTKEY); +#endif + } +} + +void setup_dropbear_client_rsa_key() { + int rc = 0; + if (access(DROPBEAR_RSA_TESTKEY, F_OK) != 0) { + rc = system_checked(DROPBEAR_KEYGEN " -t rsa -f " + DROPBEAR_RSA_TESTKEY " 1>/dev/null 2>/dev/null"); + } + assert_int_equal(rc, 0); +} + +void cleanup_dropbear_client_rsa_key() { + unlink(DROPBEAR_RSA_TESTKEY); +} diff --git a/tests/pkd/pkd_keyutil.h b/tests/pkd/pkd_keyutil.h new file mode 100644 index 0000000..7b18904 --- /dev/null +++ b/tests/pkd/pkd_keyutil.h @@ -0,0 +1,65 @@ +/* + * pkd_keyutil.h -- + * + * (c) 2014 Jon Simons + */ + +#ifndef __PKD_KEYUTIL_H__ +#define __PKD_KEYUTIL_H__ + +#include "config.h" + +/* Server keys. */ +#ifdef HAVE_DSA +#define LIBSSH_DSA_TESTKEY "libssh_testkey.id_dsa" +#endif +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_ED25519_TESTKEY "libssh_testkey.id_ed25519" +#define LIBSSH_ECDSA_256_TESTKEY "libssh_testkey.id_ecdsa256" +#define LIBSSH_ECDSA_384_TESTKEY "libssh_testkey.id_ecdsa384" +#define LIBSSH_ECDSA_521_TESTKEY "libssh_testkey.id_ecdsa521" + +#ifdef HAVE_DSA +void setup_dsa_key(void); +#endif +void setup_rsa_key(void); +void setup_ed25519_key(void); +void setup_ecdsa_keys(void); +#ifdef HAVE_DSA +void cleanup_dsa_key(void); +#endif +void cleanup_rsa_key(void); +void cleanup_ed25519_key(void); +void cleanup_ecdsa_keys(void); + +/* Client keys. */ +#ifdef HAVE_DSA +#define OPENSSH_DSA_TESTKEY "openssh_testkey.id_dsa" +#endif +#define OPENSSH_RSA_TESTKEY "openssh_testkey.id_rsa" +#define OPENSSH_ECDSA256_TESTKEY "openssh_testkey.id_ecdsa256" +#define OPENSSH_ECDSA384_TESTKEY "openssh_testkey.id_ecdsa384" +#define OPENSSH_ECDSA521_TESTKEY "openssh_testkey.id_ecdsa521" +#define OPENSSH_ED25519_TESTKEY "openssh_testkey.id_ed25519" +#define OPENSSH_CA_TESTKEY "libssh_testkey.ca" + +#define DROPBEAR_RSA_TESTKEY "dropbear_testkey.id_rsa" + +void setup_openssh_client_keys(void); +void cleanup_openssh_client_keys(void); + +void setup_dropbear_client_rsa_key(void); +void cleanup_dropbear_client_rsa_key(void); + +#define cleanup_file(name) do {\ + if (access((name), F_OK) != -1) {\ + unlink((name));\ + }} while (0) + +#define cleanup_key(name) do {\ + cleanup_file((name));\ + cleanup_file((name ".pub"));\ + cleanup_file((name "-cert.pub"));\ + } while (0) + +#endif /* __PKD_KEYUTIL_H__ */ diff --git a/tests/pkd/pkd_util.c b/tests/pkd/pkd_util.c new file mode 100644 index 0000000..0e3b19b --- /dev/null +++ b/tests/pkd/pkd_util.c @@ -0,0 +1,113 @@ +/* + * pkd_util.c -- pkd utilities + * + * (c) 2014, 2018 Jon Simons + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "pkd_client.h" +#include "pkd_util.h" + +/** + * @brief runs system(3); exits if that is interrupted with SIGINT/QUIT + * @returns 0 upon success, non-zero otherwise + */ +int system_checked(const char *cmd) { + int rc = system(cmd); + + if (WIFSIGNALED(rc) && + ((WTERMSIG(rc) == SIGINT) || (WTERMSIG(rc) == SIGQUIT))) { + exit(1); + } + + if (rc == -1) { + return -1; + } + + return WEXITSTATUS(rc); +} + +static int bin_exists(const char *binary) { + char bin[1024] = { 0 }; + snprintf(&bin[0], sizeof(bin), "type %s 1>/dev/null 2>/dev/null", binary); + return (system_checked(bin) == 0); +} + +static int is_openssh_client_new_enough(void) { + int rc = -1; + FILE *fp = NULL; + char version_buff[1024] = { 0 }; + char *version; + + static int version_ok = 0; + unsigned long int major = 0; + char *tmp = NULL; + + if (version_ok) { + return version_ok; + } + + fp = popen("ssh -V 2>&1", "r"); + if (fp == NULL) { + fprintf(stderr, "failed to get OpenSSH client version\n"); + goto done; + } + + do { + if (fgets(&version_buff[0], sizeof(version_buff), fp) == NULL) { + fprintf(stderr, "failed to get OpenSSH client version string\n"); + goto errfgets; + } + version = strstr(version_buff, "OpenSSH"); + } while(version == NULL); + + /* "OpenSSH_...." */ + if (strlen(version) < 11) { + goto errversion; + } + + /* Extract major. */ + major = strtoul(version + 8, &tmp, 10); + if ((tmp == (version + 8)) || + ((errno == ERANGE) && (major == ULONG_MAX)) || + ((errno != 0) && (major == 0)) || + ((major < 1) || (major > 100))) { + fprintf(stderr, "failed to parse OpenSSH client version, " + "errno %d\n", errno); + goto errversion; + } + + if (major < 7) { + fprintf(stderr, "error: minimum OpenSSH client version " + "required is 7, found: %ld\n", major); + goto errversion; + } + + version_ok = 1; + +errversion: +errfgets: + rc = pclose(fp); + if (rc != 0) { + fprintf(stderr, "failed to get OpenSSH client version: %d\n", rc); + } +done: + return version_ok; +} + +int is_openssh_client_enabled(void) { + return (bin_exists(OPENSSH_BINARY) && + bin_exists(OPENSSH_KEYGEN) && + is_openssh_client_new_enough()); +} + +int is_dropbear_client_enabled(void) { + return (bin_exists(DROPBEAR_BINARY) && bin_exists(DROPBEAR_KEYGEN)); +} diff --git a/tests/pkd/pkd_util.h b/tests/pkd/pkd_util.h new file mode 100644 index 0000000..aedbbe9 --- /dev/null +++ b/tests/pkd/pkd_util.h @@ -0,0 +1,16 @@ +/* + * pkd_keyutil.h -- + * + * (c) 2014 Jon Simons + */ + +#ifndef __PKD_UTIL_H__ +#define __PKD_UTIL_H__ + +int system_checked(const char *cmd); + +/* Is client 'X' enabled? */ +int is_openssh_client_enabled(void); +int is_dropbear_client_enabled(void); + +#endif /* __PKD_UTIL_H__ */ diff --git a/tests/server/CMakeLists.txt b/tests/server/CMakeLists.txt new file mode 100644 index 0000000..9476ea9 --- /dev/null +++ b/tests/server/CMakeLists.txt @@ -0,0 +1,45 @@ +project(servertests C) + +if (WITH_SERVER AND UNIX AND NOT WIN32) + +find_package(socket_wrapper) + +add_subdirectory(test_server) + +set(LIBSSH_SERVER_TESTS + torture_server + torture_server_auth_kbdint + torture_server_config +) + +include_directories(${libssh_SOURCE_DIR}/include + ${libssh_BINARY_DIR} + test_server) + +if (ARGP_INCLUDE_DIR) + include_directories(${ARGP_INCLUDE_DIR}) +endif () + +foreach(_SRV_TEST ${LIBSSH_SERVER_TESTS}) + add_cmocka_test(${_SRV_TEST} + SOURCES ${_SRV_TEST}.c + COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} + LINK_LIBRARIES ${TORTURE_LIBRARY} testserver util + ) + + if (OSX) + set_property( + TEST + ${_SRV_TEST} + PROPERTY + ENVIRONMENT DYLD_FORCE_FLAT_NAMESPACE=1;DYLD_INSERT_LIBRARIES=${SOCKET_WRAPPER_LIBRARY}) + else () + set_property( + TEST + ${_SRV_TEST} + PROPERTY + ENVIRONMENT ${TORTURE_ENVIRONMENT}) + endif() +endforeach() + +endif (WITH_SERVER AND UNIX AND NOT WIN32) diff --git a/tests/server/test_server/CMakeLists.txt b/tests/server/test_server/CMakeLists.txt new file mode 100644 index 0000000..10bf4b7 --- /dev/null +++ b/tests/server/test_server/CMakeLists.txt @@ -0,0 +1,36 @@ +project(test_server C) + +if (WITH_SERVER AND UNIX AND NOT WIN32) + +find_package(socket_wrapper) + +set(server_SRCS + main.c +) + +add_library(testserver STATIC + test_server.c + default_cb.c) + +set(LIBSSH_SERVER_TESTS +# torture_server_kbdint +) + +include_directories(${libssh_SOURCE_DIR}/include + ${libssh_BINARY_DIR}) + +if (ARGP_INCLUDE_DIR) + include_directories(${ARGP_INCLUDE_DIR}) +endif () + +if (UNIX AND NOT WIN32) + add_executable(test_server ${server_SRCS}) + target_compile_options(test_server PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(test_server + testserver + ssh::ssh + ${ARGP_LIBRARY} + util) +endif () + +endif (WITH_SERVER AND UNIX AND NOT WIN32) diff --git a/tests/server/test_server/default_cb.c b/tests/server/test_server/default_cb.c new file mode 100644 index 0000000..03c3419 --- /dev/null +++ b/tests/server/test_server/default_cb.c @@ -0,0 +1,995 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * 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 "test_server.h" +#include "default_cb.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBUTIL_H +#include +#endif +#ifdef HAVE_PTY_H +#include +#endif +#ifdef HAVE_UTMP_H +#include +#endif +#ifdef HAVE_UTIL_H +#include +#endif + +int auth_pubkey_cb(UNUSED_PARAM(ssh_session session), + const char *user, + UNUSED_PARAM(struct ssh_key_struct *pubkey), + char signature_state, + void *userdata) +{ + struct session_data_st *sdata; + + sdata = (struct session_data_st *)userdata; + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + goto null_userdata; + } + + printf("Public key authentication of user %s\n", user); + + switch(signature_state) { + case SSH_PUBLICKEY_STATE_NONE: + case SSH_PUBLICKEY_STATE_VALID: + break; + default: + goto denied; + } + + /* TODO */ + /* Check wheter the user and public key are in authorized keys list */ + + /* Authenticated */ + printf("Authenticated\n"); + sdata->authenticated = 1; + sdata->auth_attempts = 0; + return SSH_AUTH_SUCCESS; + +denied: + sdata->auth_attempts++; +null_userdata: + return SSH_AUTH_DENIED; +} + +/* TODO implement proper pam authentication cb */ +int auth_password_cb(UNUSED_PARAM(ssh_session session), + const char *user, + const char *password, + void *userdata) +{ + bool known_user = false; + bool valid_password = false; + + struct session_data_st *sdata; + + sdata = (struct session_data_st *)userdata; + + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + goto null_userdata; + } + + if (sdata->username == NULL) { + fprintf(stderr, "Error: expected username not set\n"); + goto denied; + } + + if (sdata->password == NULL) { + fprintf(stderr, "Error: expected password not set\n"); + goto denied; + } + + printf("Password authentication of user %s\n", user); + + known_user = !(strcmp(user, sdata->username)); + valid_password = !(strcmp(password, sdata->password)); + + if (known_user && valid_password) { + sdata->authenticated = 1; + sdata->auth_attempts = 0; + printf("Authenticated\n"); + return SSH_AUTH_SUCCESS; + } + +denied: + sdata->auth_attempts++; +null_userdata: + return SSH_AUTH_DENIED; +} + +#if WITH_GSSAPI +int auth_gssapi_mic_cb(ssh_session session, + UNUSED_PARAM(const char *user), + UNUSED_PARAM(const char *principal), + void *userdata) +{ + ssh_gssapi_creds creds; + struct session_data_st *sdata; + + sdata = (struct session_data_st *)userdata; + + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + goto null_userdata; + } + + printf("GSSAPI authentication\n"); + + creds = ssh_gssapi_get_creds(session); + if (creds != NULL) { + printf("Received some gssapi credentials\n"); + } else { + printf("Not received any forwardable creds\n"); + goto denied; + } + + printf("Authenticated\n"); + + sdata->authenticated = 1; + sdata->auth_attempts = 0; + + return SSH_AUTH_SUCCESS; + +denied: + sdata->auth_attempts++; +null_userdata: + return SSH_AUTH_DENIED; +} +#endif + +int channel_data_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *data, + uint32_t len, + UNUSED_PARAM(int is_stderr), + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + if (len == 0 || cdata->pid < 1 || kill(cdata->pid, 0) < 0) { + rc = SSH_OK; + goto end; + } + + rc = write(cdata->child_stdin, (char *) data, len); + +end: + return rc; +} + +void channel_eof_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +void channel_close_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +void channel_signal_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *signal), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +void channel_exit_status_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(int exit_status), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +void channel_exit_signal_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *signal), + UNUSED_PARAM(int core), + UNUSED_PARAM(const char *errmsg), + UNUSED_PARAM(const char *lang), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +int channel_pty_request_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *term), + int cols, + int rows, + int py, + int px, + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + cdata->winsize->ws_row = rows; + cdata->winsize->ws_col = cols; + cdata->winsize->ws_xpixel = px; + cdata->winsize->ws_ypixel = py; + + rc = openpty(&cdata->pty_master, + &cdata->pty_slave, + NULL, + NULL, + cdata->winsize); + if (rc != 0) { + fprintf(stderr, "Failed to open pty\n"); + rc = SSH_ERROR; + goto end; + } + + rc = SSH_OK; + +end: + return rc; +} + +int channel_pty_resize_cb(ssh_session session, + ssh_channel channel, + int cols, + int rows, + int py, + int px, + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + (void) session; + (void) channel; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + cdata->winsize->ws_row = rows; + cdata->winsize->ws_col = cols; + cdata->winsize->ws_xpixel = px; + cdata->winsize->ws_ypixel = py; + + if (cdata->pty_master != -1) { + rc = ioctl(cdata->pty_master, TIOCSWINSZ, cdata->winsize); + goto end; + } + + rc = SSH_ERROR; + +end: + return rc; +} + +void channel_auth_agent_req_callback(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(void *userdata)) +{ + /* TODO */ +} + +void channel_x11_req_callback(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(int single_connection), + UNUSED_PARAM(const char *auth_protocol), + UNUSED_PARAM(const char *auth_cookie), + UNUSED_PARAM(uint32_t screen_number), + UNUSED_PARAM(void *userdata)) +{ + /* TODO */ +} + +static int exec_pty(const char *mode, + const char *command, + struct channel_data_st *cdata) +{ + int rc; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + cdata->pid = fork(); + switch(cdata->pid) { + case -1: + close(cdata->pty_master); + close(cdata->pty_slave); + fprintf(stderr, "Failed to fork\n"); + rc = SSH_ERROR; + goto end; + case 0: + close(cdata->pty_master); + if (login_tty(cdata->pty_slave) != 0) { + exit(1); + } + execl("/bin/sh", "sh", mode, command, NULL); + exit(0); + default: + close(cdata->pty_slave); + /* pty fd is bi-directional */ + cdata->child_stdout = cdata->child_stdin = cdata->pty_master; + } + + rc = SSH_OK; + +end: + return rc; +} + +static int exec_nopty(const char *command, struct channel_data_st *cdata) +{ + int in[2], out[2], err[2]; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto stdin_failed; + } + + /* Do the plumbing to be able to talk with the child process. */ + if (pipe(in) != 0) { + goto stdin_failed; + } + if (pipe(out) != 0) { + goto stdout_failed; + } + if (pipe(err) != 0) { + goto stderr_failed; + } + + switch(cdata->pid = fork()) { + case -1: + goto fork_failed; + case 0: + /* Finish the plumbing in the child process. */ + close(in[1]); + close(out[0]); + close(err[0]); + dup2(in[0], STDIN_FILENO); + dup2(out[1], STDOUT_FILENO); + dup2(err[1], STDERR_FILENO); + close(in[0]); + close(out[1]); + close(err[1]); + /* exec the requested command. */ + execl("/bin/sh", "sh", "-c", command, NULL); + exit(0); + } + + close(in[0]); + close(out[1]); + close(err[1]); + + cdata->child_stdin = in[1]; + cdata->child_stdout = out[0]; + cdata->child_stderr = err[0]; + + return SSH_OK; + +fork_failed: + close(err[0]); + close(err[1]); +stderr_failed: + close(out[0]); + close(out[1]); +stdout_failed: + close(in[0]); + close(in[1]); +stdin_failed: + return SSH_ERROR; +} + +int channel_shell_request_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + if(cdata->pid > 0) { + rc = SSH_ERROR; + goto end; + } + + if (cdata->pty_master != -1 && cdata->pty_slave != -1) { + rc = exec_pty("-l", NULL, cdata); + goto end; + } + + /* Client requested a shell without a pty, let's pretend we allow that */ + rc = SSH_OK; + +end: + return rc; +} + +int channel_exec_request_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + const char *command, + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + if(cdata->pid > 0) { + rc = SSH_ERROR; + goto end; + } + + if (cdata->pty_master != -1 && cdata->pty_slave != -1) { + rc = exec_pty("-c", command, cdata); + goto end; + } + + rc = exec_nopty(command, cdata); + +end: + return rc; +} + +int channel_env_request_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *env_name), + UNUSED_PARAM(const char *env_value), + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + rc = SSH_OK; + +end: + return rc; +} + +int channel_subsystem_request_cb(ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + rc = strcmp(subsystem, "sftp"); + if (rc == 0) { + rc = channel_exec_request_cb(session, + channel, + SFTP_SERVER_PATH, + userdata); + goto end; + } + + /* TODO add other subsystems */ + + rc = SSH_ERROR; + +end: + return rc; +} + +int channel_write_wontblock_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(size_t bytes), + UNUSED_PARAM(void *userdata)) +{ + /* TODO */ + + return 0; +} + +ssh_channel channel_new_session_cb(ssh_session session, void *userdata) +{ + struct session_data_st *sdata = NULL; + ssh_channel chan = NULL; + + sdata = (struct session_data_st *)userdata; + + if (sdata == NULL) { + fprintf(stderr, "NULL userdata"); + goto end; + } + + chan = ssh_channel_new(session); + if (chan == NULL) { + fprintf(stderr, "Error creating channel: %s\n", + ssh_get_error(session)); + goto end; + } + + sdata->channel = chan; + +end: + return chan; +} + +#ifdef WITH_PCAP +static void set_pcap(struct session_data_st *sdata, + ssh_session session, + char *pcap_file) +{ + int rc = 0; + + if (sdata == NULL) { + return; + } + + if (pcap_file == NULL) { + return; + } + + sdata->pcap = ssh_pcap_file_new(); + if (sdata->pcap == NULL) { + return; + } + + rc = ssh_pcap_file_open(sdata->pcap, pcap_file); + if (rc == SSH_ERROR) { + fprintf(stderr, "Error opening pcap file\n"); + ssh_pcap_file_free(sdata->pcap); + sdata->pcap = NULL; + return; + } + ssh_set_pcap_file(session, sdata->pcap); +} + +static void cleanup_pcap(struct session_data_st *sdata) +{ + if (sdata == NULL) { + return; + } + + if (sdata->pcap == NULL) { + return; + } + + ssh_pcap_file_free(sdata->pcap); + sdata->pcap = NULL; +} +#endif + +static int process_stdout(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write(channel, buf, n); + } + } + + return n; +} + +static int process_stderr(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write_stderr(channel, buf, n); + } + } + + return n; +} + +/* The caller is responsible to set the userdata to be provided to the callback + * The caller is responsible to free the allocated structure + * */ +struct ssh_server_callbacks_struct *get_default_server_cb(void) +{ + + struct ssh_server_callbacks_struct *cb; + + cb = (struct ssh_server_callbacks_struct *)calloc(1, + sizeof(struct ssh_server_callbacks_struct)); + + if (cb == NULL) { + fprintf(stderr, "Out of memory\n"); + goto end; + } + + cb->auth_password_function = auth_password_cb; + cb->auth_pubkey_function = auth_pubkey_cb; + cb->channel_open_request_session_function = channel_new_session_cb; +#if WITH_GSSAPI + cb->auth_gssapi_mic_function = auth_gssapi_mic_cb; +#endif + +end: + return cb; +} + +/* The caller is responsible to set the userdata to be provided to the callback + * The caller is responsible to free the allocated structure + * */ +struct ssh_channel_callbacks_struct *get_default_channel_cb(void) +{ + struct ssh_channel_callbacks_struct *cb; + + cb = (struct ssh_channel_callbacks_struct *)calloc(1, + sizeof(struct ssh_channel_callbacks_struct)); + if (cb == NULL) { + fprintf(stderr, "Out of memory\n"); + goto end; + } + + cb->channel_pty_request_function = channel_pty_request_cb; + cb->channel_pty_window_change_function = channel_pty_resize_cb; + cb->channel_shell_request_function = channel_shell_request_cb; + cb->channel_env_request_function = channel_env_request_cb; + cb->channel_subsystem_request_function = channel_subsystem_request_cb; + cb->channel_exec_request_function = channel_exec_request_cb; + cb->channel_data_function = channel_data_cb; + +end: + return cb; +}; + +void default_handle_session_cb(ssh_event event, + ssh_session session, + struct server_state_st *state) +{ + int n; + int rc = 0; + + /* Structure for storing the pty size. */ + struct winsize wsize = { + .ws_row = 0, + .ws_col = 0, + .ws_xpixel = 0, + .ws_ypixel = 0 + }; + + /* Our struct holding information about the channel. */ + struct channel_data_st cdata = { + .pid = 0, + .pty_master = -1, + .pty_slave = -1, + .child_stdin = -1, + .child_stdout = -1, + .child_stderr = -1, + .event = NULL, + .winsize = &wsize + }; + + /* Our struct holding information about the session. */ + struct session_data_st sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + .username = SSHD_DEFAULT_USER, + .password = SSHD_DEFAULT_PASSWORD + }; + + struct ssh_channel_callbacks_struct *channel_cb = NULL; + struct ssh_server_callbacks_struct *server_cb = NULL; + + if (state == NULL) { + fprintf(stderr, "NULL server state provided\n"); + goto end; + } + + /* If callbacks were provided use them. Otherwise, use default callbacks */ + if (state->server_cb != NULL) { + /* This is a macro, it does not return a value */ + ssh_callbacks_init(state->server_cb); + + rc = ssh_set_server_callbacks(session, state->server_cb); + if (rc) { + goto end; + } + } else { + server_cb = get_default_server_cb(); + if (server_cb == NULL) { + goto end; + } + + server_cb->userdata = &sdata; + + /* This is a macro, it does not return a value */ + ssh_callbacks_init(server_cb); + + rc = ssh_set_server_callbacks(session, server_cb); + if (rc) { + goto end; + } + } + + sdata.server_state = (void *)state; + cdata.server_state = (void *)state; + +#ifdef WITH_PCAP + set_pcap(&sdata, session, state->pcap_file); +#endif + + if (state->expected_username != NULL) { + sdata.username = state->expected_username; + } + + if (state->expected_password != NULL) { + sdata.password = state->expected_password; + } + + if (ssh_handle_key_exchange(session) != SSH_OK) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + return; + } + + /* Set the supported authentication methods */ + if (state->auth_methods) { + ssh_set_auth_methods(session, state->auth_methods); + } else { + ssh_set_auth_methods(session, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_PUBLICKEY); + } + + ssh_event_add_session(event, session); + + n = 0; + while (sdata.authenticated == 0 || sdata.channel == NULL) { + /* If the user has used up all attempts, or if he hasn't been able to + * authenticate in 10 seconds (n * 100ms), disconnect. */ + if (sdata.auth_attempts >= state->max_tries || n >= 100) { + return; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + fprintf(stderr, "do_poll error: %s\n", ssh_get_error(session)); + return; + } + n++; + } + + /* TODO check return values */ + if (state->channel_cb != NULL) { + ssh_callbacks_init(state->channel_cb); + + rc = ssh_set_channel_callbacks(sdata.channel, state->channel_cb); + if (rc) { + goto end; + } + } else { + channel_cb = get_default_channel_cb(); + if (channel_cb == NULL) { + goto end; + } + + channel_cb->userdata = &cdata; + + ssh_callbacks_init(channel_cb); + rc = ssh_set_channel_callbacks(sdata.channel, channel_cb); + if (rc) { + goto end; + } + } + + do { + /* Poll the main event which takes care of the session, the channel and + * even our child process's stdout/stderr (once it's started). */ + if (ssh_event_dopoll(event, -1) == SSH_ERROR) { + ssh_channel_close(sdata.channel); + } + + /* If child process's stdout/stderr has been registered with the event, + * or the child process hasn't started yet, continue. */ + if (cdata.event != NULL || cdata.pid == 0) { + continue; + } + /* Executed only once, once the child process starts. */ + cdata.event = event; + /* If stdout valid, add stdout to be monitored by the poll event. */ + if (cdata.child_stdout != -1) { + if (ssh_event_add_fd(event, cdata.child_stdout, POLLIN, process_stdout, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stdout to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + + /* If stderr valid, add stderr to be monitored by the poll event. */ + if (cdata.child_stderr != -1){ + if (ssh_event_add_fd(event, cdata.child_stderr, POLLIN, process_stderr, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stderr to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + } while(ssh_channel_is_open(sdata.channel) && + (cdata.pid == 0 || waitpid(cdata.pid, &rc, WNOHANG) == 0)); + + close(cdata.pty_master); + close(cdata.child_stdin); + close(cdata.child_stdout); + close(cdata.child_stderr); + + /* Remove the descriptors from the polling context, since they are now + * closed, they will always trigger during the poll calls. */ + ssh_event_remove_fd(event, cdata.child_stdout); + ssh_event_remove_fd(event, cdata.child_stderr); + + /* If the child process exited. */ + if (kill(cdata.pid, 0) < 0 && WIFEXITED(rc)) { + rc = WEXITSTATUS(rc); + ssh_channel_request_send_exit_status(sdata.channel, rc); + /* If client terminated the channel or the process did not exit nicely, + * but only if something has been forked. */ + } else if (cdata.pid > 0) { + kill(cdata.pid, SIGKILL); + } + + ssh_channel_send_eof(sdata.channel); + ssh_channel_close(sdata.channel); + + /* Wait up to 5 seconds for the client to terminate the session. */ + for (n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) { + ssh_event_dopoll(event, 100); + } + +end: +#ifdef WITH_PCAP + cleanup_pcap(&sdata); +#endif + if (channel_cb != NULL) { + free(channel_cb); + } + if (server_cb != NULL) { + free(server_cb); + } + return; +} diff --git a/tests/server/test_server/default_cb.h b/tests/server/test_server/default_cb.h new file mode 100644 index 0000000..487794c --- /dev/null +++ b/tests/server/test_server/default_cb.h @@ -0,0 +1,176 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * 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 +#include + +#define SSHD_DEFAULT_USER "libssh" +#define SSHD_DEFAULT_PASSWORD "libssh" +#define SSHD_DEFAULT_PORT 2222 +#define SSHD_DEFAULT_ADDRESS "127.0.0.1" +#define SSHD_DEFAULT_PCAP_FILE "debug.server.pcap" + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#define BUF_SIZE 1048576 +#define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) +#define SFTP_SERVER_PATH "/usr/lib/sftp-server" + +#ifdef HAVE_PTY_H +#include +#endif + +/* A userdata struct for channel. */ +struct channel_data_st { + /* pid of the child process the channel will spawn. */ + pid_t pid; + /* For PTY allocation */ + socket_t pty_master; + socket_t pty_slave; + /* For communication with the child process. */ + socket_t child_stdin; + socket_t child_stdout; + /* Only used for subsystem and exec requests. */ + socket_t child_stderr; + /* Event which is used to poll the above descriptors. */ + ssh_event event; + /* Terminal size struct. */ + struct winsize *winsize; + /* This pointer will hold the server state for default callbacks */ + void *server_state; + /* This pointer is useful to set data for custom callbacks */ + void *extra_data; +}; + +/* A userdata struct for session. */ +struct session_data_st { + /* Pointer to the channel the session will allocate. */ + ssh_channel channel; + int auth_attempts; + int authenticated; + const char *username; + const char *password; +#ifdef WITH_PCAP + ssh_pcap_file pcap; +#endif + /* This pointer will hold the server state for default callbacks */ + void *server_state; + /* This pointer is useful to set data for custom callbacks */ + void *extra_data; +}; + +int auth_password_cb(ssh_session session, const char *user, + const char *password, void *userdata); + +#if WITH_GSSAPI +int auth_gssapi_mic_cb(ssh_session session, const char *user, + const char *principal, void *userdata); +#endif + +int channel_data_cb(ssh_session session, ssh_channel channel, + void *data, uint32_t len, int is_stderr, void *userdata); + +void channel_eof_cb(ssh_session session, ssh_channel channel, + void *userdata); + +void channel_close_cb(ssh_session session, ssh_channel channel, + void *userdata); + +void channel_signal_cb (ssh_session session, + ssh_channel channel, + const char *signal, + void *userdata); + +void channel_exit_status_cb (ssh_session session, + ssh_channel channel, + int exit_status, + void *userdata); + +void channel_exit_signal_cb(ssh_session session, + ssh_channel channel, + const char *signal, + int core, + const char *errmsg, + const char *lang, + void *userdata); + +int channel_pty_request_cb(ssh_session session, ssh_channel channel, + const char *term, int cols, int rows, int py, int px, void *userdata); + +int channel_pty_resize_cb(ssh_session session, ssh_channel channel, + int cols, int rows, int py, int px, void *userdata); + +int channel_shell_request_cb(ssh_session session, ssh_channel channel, + void *userdata); + +void channel_auth_agent_req_callback(ssh_session session, + ssh_channel channel, void *userdata); + +void channel_x11_req_callback(ssh_session session, + ssh_channel channel, + int single_connection, + const char *auth_protocol, + const char *auth_cookie, + uint32_t screen_number, + void *userdata); + +int channel_exec_request_cb(ssh_session session, + ssh_channel channel, + const char *command, + void *userdata); + +int channel_env_request_cb(ssh_session session, + ssh_channel channel, const char *env_name, const char *env_value, + void *userdata); + +int channel_subsystem_request_cb(ssh_session session, + ssh_channel channel, const char *subsystem, + void *userdata); + +int channel_write_wontblock_cb(ssh_session session, + ssh_channel channel, + size_t bytes, + void *userdata); + +ssh_channel channel_new_session_cb(ssh_session session, void *userdata); + +/* The caller is responsible to set the userdata to be provided to the callback + * The caller is responsible to free the allocated structure + * */ +struct ssh_server_callbacks_struct *get_default_server_cb(void); + +/* The caller is responsible to set the userdata to be provided to the callback + * The caller is responsible to free the allocated structure + * */ +struct ssh_channel_callbacks_struct *get_default_channel_cb(void); + +void default_handle_session_cb(ssh_event event, ssh_session session, + struct server_state_st *state); diff --git a/tests/server/test_server/main.c b/tests/server/test_server/main.c new file mode 100644 index 0000000..87ff6c8 --- /dev/null +++ b/tests/server/test_server/main.c @@ -0,0 +1,634 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * 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 "test_server.h" +#include "default_cb.h" + +#include + +#include +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif + +#include +#include +#include +#include + +struct arguments_st { + char *address; + char *port; + + char *ecdsa_key; + char *dsa_key; + char *ed25519_key; + char *rsa_key; + char *host_key; + + char *verbosity; + char *auth_methods; + bool with_pcap; + + char *pcap_file; + + char *username; + char *password; + + char *config_file; + bool with_global_config; +}; + +static void free_arguments(struct arguments_st *arguments) +{ + if (arguments == NULL) { + goto end; + } + + SAFE_FREE(arguments->address); + SAFE_FREE(arguments->port); + + SAFE_FREE(arguments->ecdsa_key); + SAFE_FREE(arguments->dsa_key); + SAFE_FREE(arguments->ed25519_key); + SAFE_FREE(arguments->rsa_key); + SAFE_FREE(arguments->host_key); + + SAFE_FREE(arguments->verbosity); + SAFE_FREE(arguments->auth_methods); + SAFE_FREE(arguments->pcap_file); + + SAFE_FREE(arguments->username); + SAFE_FREE(arguments->password); + SAFE_FREE(arguments->config_file); + +end: + return; +} + +#ifdef HAVE_ARGP_H + +static void print_auth_methods(int auth_methods) +{ + printf("auth_methods = \n"); + if (auth_methods & SSH_AUTH_METHOD_NONE) { + printf("\tSSH_AUTH_METHOD_NONE\n"); + } + if (auth_methods & SSH_AUTH_METHOD_PASSWORD) { + printf("\tSSH_AUTH_METHOD_PASSWORD\n"); + } + if (auth_methods & SSH_AUTH_METHOD_PUBLICKEY) { + printf("\tSSH_AUTH_METHOD_PUBLICKEY\n"); + } + if (auth_methods & SSH_AUTH_METHOD_HOSTBASED) { + printf("\tSSH_AUTH_METHOD_HOSTBASED\n"); + } + if (auth_methods & SSH_AUTH_METHOD_INTERACTIVE) { + printf("\tSSH_AUTH_METHOD_INTERACTIVE\n"); + } + if (auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) { + printf("\tSSH_AUTH_METHOD_GSSAPI_MIC\n"); + } +} + +static void print_verbosity(int verbosity) +{ + printf("verbosity = "); + switch(verbosity) { + case SSH_LOG_NOLOG: + printf("NO LOG\n"); + break; + case SSH_LOG_WARNING: + printf("WARNING\n"); + break; + case SSH_LOG_PROTOCOL: + printf("PROTOCOL\n"); + break; + case SSH_LOG_PACKET: + printf("PACKET\n"); + break; + case SSH_LOG_FUNCTIONS: + printf("FUNCTIONS\n"); + break; + default: + printf("UNKNOWN\n");; + break; + } +} + +static void print_server_state(struct server_state_st *state) +{ + if (state) { + printf("===================| STATE |=====================\n"); + printf("address = %s\n", + state->address? state->address: "NULL"); + printf("port = %d\n", + state->port? state->port: 0); + printf("=================================================\n"); + printf("ecdsa_key = %s\n", + state->ecdsa_key? state->ecdsa_key: "NULL"); + printf("dsa_key = %s\n", + state->dsa_key? state->dsa_key: "NULL"); + printf("ed25519_key = %s\n", + state->ed25519_key? state->ed25519_key: "NULL"); + printf("rsa_key = %s\n", + state->rsa_key? state->rsa_key: "NULL"); + printf("host_key = %s\n", + state->host_key? state->host_key: "NULL"); + printf("=================================================\n"); + print_auth_methods(state->auth_methods); + print_verbosity(state->verbosity); + printf("with_pcap = %s\n", + state->with_pcap? "TRUE": "FALSE"); + printf("pcap_file = %s\n", + state->pcap_file? state->pcap_file: "NULL"); + printf("=================================================\n"); + printf("username = %s\n", + state->expected_username? state->expected_username: "NULL"); + printf("password = %s\n", + state->expected_password? state->expected_password: "NULL"); + printf("=================================================\n"); + printf("with_global_config = %s\n", + state->parse_global_config? "TRUE": "FALSE"); + printf("config_file = %s\n", + state->config_file? state->config_file: "NULL"); + printf("=================================================\n"); + } +} + +static int init_server_state(struct server_state_st *state, + struct arguments_st *arguments) +{ + int rc = 0; + + if (state == NULL) { + rc = SSH_ERROR; + goto end; + } + + /* Initialize server state. The "arguments structure" */ + if (arguments->address) { + state->address = arguments->address; + arguments->address = NULL; + } else { + state->address = strdup(SSHD_DEFAULT_ADDRESS); + if (state->address == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto end; + } + } + + if (arguments->port) { + state->port = atoi(arguments->port); + } else { + state->port = SSHD_DEFAULT_PORT; + } + + if (arguments->ecdsa_key) { + state->ecdsa_key = arguments->ecdsa_key; + arguments->ecdsa_key = NULL; + } else { + state->ecdsa_key = NULL; + } + + if (arguments->dsa_key) { + state->dsa_key = arguments->dsa_key; + arguments->dsa_key = NULL; + } else { + state->dsa_key = NULL; + } + + if (arguments->ed25519_key) { + state->ed25519_key = arguments->ed25519_key; + arguments->ed25519_key = NULL; + } else { + state->ed25519_key = NULL; + } + + if (arguments->rsa_key) { + state->rsa_key = arguments->rsa_key; + arguments->rsa_key = NULL; + } else { + state->rsa_key = NULL; + } + + if (arguments->host_key) { + state->host_key = arguments->host_key; + arguments->host_key = NULL; + } else { + state->host_key = NULL; + } + + if (arguments->username) { + state->expected_username = arguments->username; + arguments->username = NULL; + } else { + state->expected_username = strdup(SSHD_DEFAULT_USER); + if (state->expected_username == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto end; + } + } + + if (arguments->password) { + state->expected_password = arguments->password; + arguments->password = NULL; + } else { + state->expected_password = strdup(SSHD_DEFAULT_PASSWORD); + if (state->expected_password == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto end; + } + } + + if (arguments->verbosity) { + state->verbosity = atoi(arguments->verbosity); + } else { + state->verbosity = 0; + } + + if (arguments->auth_methods) { + state->auth_methods = atoi(arguments->auth_methods); + } else { + state->auth_methods = SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_PUBLICKEY; + } + + state->with_pcap = arguments->with_pcap; + + if (arguments->pcap_file) { + state->pcap_file = arguments->pcap_file; + arguments->pcap_file = NULL; + } else { + if (arguments->with_pcap) { + state->pcap_file = strdup(SSHD_DEFAULT_PCAP_FILE); + if (state->pcap_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto end; + } + } else { + state->pcap_file = NULL; + } + } + + state->parse_global_config = arguments->with_global_config; + + if (arguments->config_file) { + state->config_file = arguments->config_file; + arguments->config_file = NULL; + } + + /* TODO make configurable */ + state->max_tries = 3; + state->error = 0; + + + if (state) { + print_server_state(state); + } + + /* TODO make callbacks configurable through command line ? */ + /* Set callbacks to be used */ + state->handle_session = default_handle_session_cb; + + /* Check required parameters */ + if (state->address == NULL) { + rc = SSH_ERROR; + goto end; + } + +end: + if (rc != 0) { + free_server_state(state); + } + + return rc; +} + +const char *argp_program_version = "libssh test server " +SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "ecdsakey", + .key = 'c', + .arg = "FILE", + .flags = 0, + .doc = "Set the ECDSA key.", + .group = 0 + }, + { + .name = "dsakey", + .key = 'd', + .arg = "FILE", + .flags = 0, + .doc = "Set the DSA key.", + .group = 0 + }, + { + .name = "ed25519key", + .key = 'e', + .arg = "FILE", + .flags = 0, + .doc = "Set the ed25519 key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the RSA key.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "pcapfile", + .key = 'f', + .arg = "FILE", + .flags = 0, + .doc = "Set the pcap output file.", + .group = 0 + }, + { + .name = "auth-methods", + .key = 'a', + .arg = "METHODS", + .flags = 0, + .doc = "Set supported authentication methods.", + .group = 0 + }, + { + .name = "user", + .key = 'u', + .arg = "USERNAME", + .flags = 0, + .doc = "Set expected username.", + .group = 0 + }, + { + .name = "verbosity", + .key = 'v', + .arg = "VERBOSITY", + .flags = 0, + .doc = "Set output verbosity [0-4].", + .group = 0 + }, + { + .name = "with-pcap", + .key = 'w', + .arg = NULL, + .flags = 0, + .doc = "Use PCAP.", + .group = 0 + }, + { + .name = "without-global-config", + .key = 'g', + .arg = NULL, + .flags = 0, + .doc = "Do not use system-wide configuration file.", + .group = 0 + }, + { + .name = "config", + .key = 'C', + .arg = "CONFIG_FILE", + .flags = 0, + .doc = "Use this server configuration file.", + .group = 0 + }, + { .name = NULL } +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) +{ + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + struct arguments_st *arguments = state->input; + error_t rc = 0; + + if (arguments == NULL) { + fprintf(stderr, "NULL pointer to arguments structure provided\n"); + rc = EINVAL; + goto end; + } + + switch (key) { + case 'c': + arguments->ecdsa_key = strdup(arg); + if (arguments->ecdsa_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'd': + arguments->dsa_key = strdup(arg); + if (arguments->dsa_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'e': + arguments->ed25519_key = strdup(arg); + if (arguments->ed25519_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'f': + arguments->pcap_file = strdup(arg); + if (arguments->pcap_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'k': + arguments->host_key = strdup(arg); + if (arguments->host_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'a': + arguments->auth_methods = strdup(arg); + if (arguments->auth_methods == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'p': + arguments->port = strdup(arg); + if (arguments->port == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'r': + arguments->rsa_key = strdup(arg); + if (arguments->rsa_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'u': + arguments->username = strdup(arg); + if (arguments->username == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'v': + arguments->verbosity = strdup(arg); + if (arguments->verbosity == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'w': + arguments->with_pcap = true; + break; + case 'g': + arguments->with_global_config = false; + break; + case 'C': + arguments->config_file = strdup(arg); + if (arguments->config_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + printf("Too many arguments\n"); + argp_usage(state); + } + arguments->address = strdup(arg); + if (arguments->address == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + printf("Too few arguments\n"); + /* Not enough arguments. */ + argp_usage(state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + +end: + return rc; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; + +#endif /* HAVE_ARGP_H */ + +int main(UNUSED_PARAM(int argc), UNUSED_PARAM(char **argv)) +{ + int rc; + + struct arguments_st arguments = { + .address = NULL, + .with_global_config = true, + }; + struct server_state_st state = { + .address = NULL, + }; + +#ifdef HAVE_ARGP_H + argp_parse (&argp, argc, argv, 0, 0, &arguments); +#endif + + /* Initialize the state using default or given parameters */ + rc = init_server_state(&state, &arguments); + if (rc != 0) { + goto free_arguments; + } + + /* Free the arguments used to initialize the state before fork */ + free_arguments(&arguments); + + /* Run the server */ + rc = run_server(&state); + if (rc != 0) { + goto free_state; + } + +free_state: + free_server_state(&state); +free_arguments: + free_arguments(&arguments); + return rc; +} diff --git a/tests/server/test_server/test_server.c b/tests/server/test_server/test_server.c new file mode 100644 index 0000000..42d2149 --- /dev/null +++ b/tests/server/test_server/test_server.c @@ -0,0 +1,329 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * 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 "test_server.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +void free_server_state(struct server_state_st *state) +{ + if (state == NULL) { + goto end; + } + + SAFE_FREE(state->address); + + SAFE_FREE(state->ecdsa_key); + SAFE_FREE(state->dsa_key); + SAFE_FREE(state->ed25519_key); + SAFE_FREE(state->rsa_key); + SAFE_FREE(state->host_key); + + SAFE_FREE(state->pcap_file); + + SAFE_FREE(state->expected_username); + SAFE_FREE(state->expected_password); + SAFE_FREE(state->config_file); + +end: + return; +} + +/* SIGCHLD handler for cleaning up dead children. */ +static void sigchld_handler(int signo) { + (void) signo; + while (waitpid(-1, NULL, WNOHANG) > 0); +} + +int run_server(struct server_state_st *state) +{ + ssh_session session = NULL; + ssh_bind sshbind = NULL; + ssh_event event = NULL; + + struct sigaction sa = { + .sa_flags = 0 + }; + + int rc; + + /* Check provided state */ + if (state == NULL) { + fprintf(stderr, "Invalid state\n"); + return SSH_ERROR; + } + + /* Set up SIGCHLD handler. */ + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + + if (sigaction(SIGCHLD, &sa, NULL) != 0) { + fprintf(stderr, "Failed to register SIGCHLD handler\n"); + return SSH_ERROR; + } + + if (state->address == NULL) { + fprintf(stderr, "Missing bind address\n"); + return SSH_ERROR; + } + + if (state->address == NULL) { + fprintf(stderr, "Missing bind address\n"); + return SSH_ERROR; + } + + sshbind = ssh_bind_new(); + if (sshbind == NULL) { + fprintf(stderr, "Out of memory\n"); + return SSH_ERROR; + } + + if (state->verbosity) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_LOG_VERBOSITY, + &state->verbosity); + if (rc != 0) { + fprintf(stderr, + "Error setting verbosity level: %s\n", + ssh_get_error(sshbind)); + goto free_sshbind; + } + } + + if (!state->parse_global_config) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_PROCESS_CONFIG, + &(state->parse_global_config)); + if (rc != 0) { + goto free_sshbind; + } + } + + if (state->config_file) { + rc = ssh_bind_options_parse_config(sshbind, state->config_file); + if (rc != 0) { + goto free_sshbind; + } + } + + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_BINDADDR, + state->address); + if (rc != 0) { + fprintf(stderr, + "Error setting bind address: %s\n", + ssh_get_error(sshbind)); + goto free_sshbind; + } + + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_BINDPORT, + &(state->port)); + if (rc != 0) { + fprintf(stderr, + "Error setting bind port: %s\n", + ssh_get_error(sshbind)); + goto free_sshbind; + } + + if (state->dsa_key != NULL) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_DSAKEY, + state->dsa_key); + if (rc != 0) { + fprintf(stderr, + "Error setting DSA key: %s\n", + ssh_get_error(sshbind)); + goto free_sshbind; + } + } + + if (state->rsa_key != NULL) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_RSAKEY, + state->rsa_key); + if (rc != 0) { + fprintf(stderr, + "Error setting RSA key: %s\n", + ssh_get_error(sshbind)); + goto free_sshbind; + } + } + + if (state->ecdsa_key != NULL) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_ECDSAKEY, + state->ecdsa_key); + if (rc != 0) { + fprintf(stderr, + "Error setting ECDSA key: %s\n", + ssh_get_error(sshbind)); + goto free_sshbind; + } + } + + if (state->host_key) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_HOSTKEY, + state->host_key); + if (rc) { + fprintf(stderr, + "Error setting hostkey: %s\n", + ssh_get_error(sshbind)); + goto free_sshbind; + } + } + + rc = ssh_bind_listen(sshbind); + if (rc != 0) { + fprintf(stderr, + "Error listening to socket: %s\n", + ssh_get_error(sshbind)); + goto free_sshbind; + } + + printf("Started libssh test server on port %d\n", state->port); + + for (;;) { + session = ssh_new(); + if (session == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto free_sshbind; + } + + /* Blocks until there is a new incoming connection. */ + rc = ssh_bind_accept(sshbind, session); + if (rc != SSH_ERROR) { + pid_t pid = fork(); + + switch(pid) { + case 0: + /* Remove the SIGCHLD handler inherited from parent. */ + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + /* Remove socket binding, which allows us to restart the + * parent process, without terminating existing sessions. */ + ssh_bind_free(sshbind); + + event = ssh_event_new(); + if (event != NULL) { + /* Blocks until the SSH session ends by either + * child process exiting, or client disconnecting. */ + state->handle_session(event, session, state); + ssh_event_free(event); + } else { + fprintf(stderr, "Could not create polling context\n"); + } + ssh_disconnect(session); + ssh_free(session); + + free_server_state(state); + + exit(0); + case -1: + fprintf(stderr, "Failed to fork\n"); + } + } else { + fprintf(stderr, + "Error accepting a connection: %s\n", + ssh_get_error(sshbind)); + } + + /* Since the session has been passed to a child fork, do some cleaning + * up at the parent process. */ + ssh_disconnect(session); + ssh_free(session); + } + + rc = 0; + +free_sshbind: + ssh_bind_free(sshbind); + return rc; +} + +pid_t fork_run_server(struct server_state_st *state) +{ + pid_t pid; + int rc; + + char err_str[1024] = {0}; + + struct sigaction sa; + + /* Check provided state */ + if (state == NULL) { + fprintf(stderr, "Invalid state\n"); + return -1; + } + + /* Set up SIGCHLD handler. */ + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + + if (sigaction(SIGCHLD, &sa, NULL) != 0) { + strerror_r(errno, err_str, 1024); + fprintf(stderr, "Failed to register SIGCHLD handler: %s\n", + err_str); + return -1; + } + + pid = fork(); + switch(pid) { + case 0: + /* Remove the SIGCHLD handler inherited from parent. */ + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + + /* The child process starts a server which will listen for connections */ + rc = run_server(state); + if (rc != 0) { + exit(rc); + } + + exit(0); + case -1: + strerror_r(errno, err_str, 1024); + fprintf(stderr, "Failed to fork: %s\n", + err_str); + return -1; + default: + /* Return the child pid */ + return pid; + } +} diff --git a/tests/server/test_server/test_server.h b/tests/server/test_server/test_server.h new file mode 100644 index 0000000..b53894b --- /dev/null +++ b/tests/server/test_server/test_server.h @@ -0,0 +1,76 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * 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 +#include + +#include +#include +#include + +struct server_state_st { + /* Arguments */ + char *address; + int port; + + char *ecdsa_key; + char *dsa_key; + char *ed25519_key; + char *rsa_key; + char *host_key; + + int verbosity; + int auth_methods; + bool with_pcap; + + char *pcap_file; + + char *expected_username; + char *expected_password; + + char *config_file; + bool parse_global_config; + + /* State */ + int max_tries; + int error; + + struct ssh_server_callbacks_struct *server_cb; + struct ssh_channel_callbacks_struct *channel_cb; + + /* Callback to handle the session, should block until disconnected */ + void (*handle_session)(ssh_event event, + ssh_session session, + struct server_state_st *state); +}; + +/*TODO: Add documentation */ +void free_server_state(struct server_state_st *state); + +/*TODO: Add documentation */ +int run_server(struct server_state_st *state); + +/*TODO: Add documentation */ +pid_t fork_run_server(struct server_state_st *state); diff --git a/tests/server/torture_server.c b/tests/server/torture_server.c new file mode 100644 index 0000000..518fc97 --- /dev/null +++ b/tests/server/torture_server.c @@ -0,0 +1,550 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include "test_server.h" +#include "default_cb.h" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +const char template[] = "temp_dir_XXXXXX"; + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; + char *temp_dir; +}; + +static int setup_default_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; +#ifdef HAVE_DSA + char dsa_hostkey[1024]; +#endif /* HAVE_DSA */ + + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + //char trusted_ca_pubkey[1024]; + + char sshd_path[1024]; + struct stat sb; + + const char *sftp_server_locations[] = { + "/usr/lib/ssh/sftp-server", + "/usr/libexec/sftp-server", + "/usr/libexec/openssh/sftp-server", + "/usr/lib/openssh/sftp-server", /* Debian */ + }; + + size_t sftp_sl_size = ARRAY_SIZE(sftp_server_locations); + const char *sftp_server; + + size_t i; + int rc; + + char pid_str[1024]; + + pid_t pid; + + assert_non_null(state); + + tss = (struct test_server_st*)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, + sizeof(sshd_path), + "%s/sshd", + s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + +#ifdef HAVE_DSA + snprintf(dsa_hostkey, + sizeof(dsa_hostkey), + "%s/sshd/ssh_host_dsa_key", + s->socket_dir); + torture_write_file(dsa_hostkey, torture_get_testkey(SSH_KEYTYPE_DSS, 0)); +#endif /* HAVE_DSA */ + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + sftp_server = getenv("TORTURE_SFTP_SERVER"); + if (sftp_server == NULL) { + for (i = 0; i < sftp_sl_size; i++) { + sftp_server = sftp_server_locations[i]; + rc = lstat(sftp_server, &sb); + if (rc == 0) { + break; + } + } + } + assert_non_null(sftp_server); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->ecdsa_key = strdup(ecdsa_hostkey); + assert_non_null(ss->ecdsa_key); + +#ifdef HAVE_DSA + ss->dsa_key = strdup(dsa_hostkey); + assert_non_null(ss->dsa_key); +#endif /* HAVE_DSA */ + + ss->ed25519_key = strdup(ed25519_hostkey); + assert_non_null(ed25519_hostkey); + + ss->rsa_key = strdup(rsa_hostkey); + assert_non_null(ss->rsa_key); + + ss->host_key = NULL; + + /* Use default username and password (set in default_handle_session_cb) */ + ss->expected_username = NULL; + ss->expected_password = NULL; + + ss->verbosity = torture_libssh_verbosity(); + + ss->auth_methods = SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + /* Start the server using the default values */ + pid = fork_run_server(ss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + //rc = torture_wait_for_daemon(5); + //assert_int_equal(rc, 0); + + /* TODO properly wait for the server (use ping approach) */ + /* Wait 200ms */ + usleep(200 * 1000); + + tss->state = s; + tss->ss = ss; + + *state = tss; + + return 0; +} + +static int teardown_default_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + char *cwd = NULL; + char *tmp_dir = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + tss->cwd = cwd; + tss->temp_dir = tmp_dir; + + s = tss->state; + assert_non_null(s); + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(tss->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->temp_dir); + SAFE_FREE(tss->cwd); + + return 0; +} + +static void torture_server_auth_none(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } +} + +static void torture_server_auth_password(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* TODO: implement proper pam authentication in callback */ + /* Using the default user for the server */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + /* TODO: implement proper pam authentication in callback */ + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_server_auth_pubkey(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Authenticate as alice with bob's pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_server_hostkey_mismatch(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + char known_hosts_file[1024] = {0}; + FILE *file = NULL; + enum ssh_known_hosts_e found; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Store the testkey in the knownhosts file */ + snprintf(known_hosts_file, + sizeof(known_hosts_file), + "%s/%s", + s->socket_dir, + TORTURE_KNOWN_HOSTS_FILE); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + /* Using the default user for the server */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_ssh_return_code(session, rc); + + /* Configure the client to offer only rsa-sha2-256 hostkey algorithm */ + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Make sure we can verify the signature */ + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); +} + +static void torture_server_unknown_global_request(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + ssh_channel channel; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Request asking for reply */ + rc = ssh_global_request(session, "unknown-request-00@test.com", NULL, 1); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + /* Request and don't ask for reply */ + rc = ssh_global_request(session, "another-bad-req-00@test.com", NULL, 0); + assert_ssh_return_code(session, rc); + + /* Open channel to make sure the session is still working */ + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + ssh_channel_close(channel); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_server_auth_none, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_auth_password, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_auth_pubkey, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_hostkey_mismatch, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_unknown_global_request, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + + ssh_finalize(); + + return rc; +} diff --git a/tests/server/torture_server_auth_kbdint.c b/tests/server/torture_server_auth_kbdint.c new file mode 100644 index 0000000..5bf8159 --- /dev/null +++ b/tests/server/torture_server_auth_kbdint.c @@ -0,0 +1,767 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +#include "test_server.h" +#include "default_cb.h" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +enum { + SUCCESS, + MORE, + FAILED +}; + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; +}; + +#ifdef WITH_PCAP +static void set_pcap(struct session_data_st *sdata, + ssh_session session, + char *pcap_file) +{ + int rc = 0; + + if (sdata == NULL) { + return; + } + + if (pcap_file == NULL) { + return; + } + + sdata->pcap = ssh_pcap_file_new(); + if (sdata->pcap == NULL) { + return; + } + + rc = ssh_pcap_file_open(sdata->pcap, pcap_file); + if (rc == SSH_ERROR) { + fprintf(stderr, "Error opening pcap file\n"); + ssh_pcap_file_free(sdata->pcap); + sdata->pcap = NULL; + return; + } + ssh_set_pcap_file(session, sdata->pcap); +} + +static void cleanup_pcap(struct session_data_st *sdata) +{ + if (sdata == NULL) { + return; + } + + if (sdata->pcap == NULL) { + return; + } + + /* Do not free the pcap data context here since its ownership was + * transfered to the session object, which will take care of its cleanup. + * Morover it is still in use so we can very simply crash by freeing + * it here. + */ + sdata->pcap = NULL; +} +#endif + +static int process_stdout(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write(channel, buf, n); + } + } + + return n; +} + +static int process_stderr(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write_stderr(channel, buf, n); + } + } + + return n; +} + +static int authenticate_kbdint(ssh_session session, + ssh_message message, + void *userdata) +{ + int rc = 0; + int count; + int *step = NULL; + size_t expected_len; + + const char instruction[] = "Type the requested data"; + const char name[] = "Keyboard-Interactive Authentication\n"; + char initial_echo[] = {1, 0}; + char retype_echo[] = {0}; + const char *initial_prompt[2]; + const char *retype_prompt[1]; + int cmp; + + const char *answer; + + struct session_data_st *sdata = (struct session_data_st *)userdata; + + initial_prompt[0] = "username: "; + initial_prompt[1] = "password: "; + + /* Prompt for aditional prompts */ + retype_prompt[0] = "retype password: "; + + if ((session == NULL) || (message == NULL) || (sdata == NULL)) { + fprintf(stderr, "Null argument provided\n"); + goto failed; + } + + if (sdata->extra_data == NULL) { + goto failed; + } + + step = (int *)sdata->extra_data; + + switch (*step) { + case 0: + ssh_message_auth_interactive_request(message, name, instruction, 2, + initial_prompt, initial_echo); + rc = MORE; + goto end; + case 1: + count = ssh_userauth_kbdint_getnanswers(session); + if (count != 2) { + goto failed; + } + + if ((sdata->username == NULL) || (sdata->password == NULL)) { + goto failed; + } + + /* Get and compare username */ + expected_len = strlen(sdata->username); + if (expected_len <= 0) { + goto failed; + } + + answer = ssh_userauth_kbdint_getanswer(session, 0); + if (answer == NULL) { + goto failed; + } + + cmp = strncmp(answer, sdata->username, expected_len); + if (cmp != 0) { + goto failed; + } + + /* Get and compare password */ + expected_len = strlen(sdata->password); + if (expected_len <= 0) { + goto failed; + } + + answer = ssh_userauth_kbdint_getanswer(session, 1); + if (answer == NULL) { + goto failed; + } + + cmp = strncmp(answer, sdata->password, expected_len); + if (cmp != 0) { + goto failed; + } + + /* Username and password matched. Ask for a retype. */ + ssh_message_auth_interactive_request(message, + name, + instruction, + 1, + retype_prompt, + retype_echo); + + rc = MORE; + goto end; + case 2: + /* Get and compare password */ + expected_len = strlen(sdata->password); + if (expected_len <= 0) { + goto failed; + } + + answer = ssh_userauth_kbdint_getanswer(session, 0); + if (answer == NULL) { + goto failed; + } + + cmp = strncmp(answer, sdata->password, expected_len); + if (cmp != 0) { + goto failed; + } + + /* Password was correct, authenticated */ + rc = SUCCESS; + goto end; + default: + goto failed; + } + +failed: + if (step != NULL) { + *step = 0; + } + return FAILED; + +end: + if (step != NULL) { + (*step)++; + } + return rc; +} + +static int authenticate_callback(ssh_session session, + ssh_message message, + void *userdata) +{ + struct session_data_st *sdata = (struct session_data_st *)userdata; + int rc; + + if (sdata == NULL) { + fprintf(stderr, "Null userdata\n"); + goto denied; + } + + if (sdata->extra_data == NULL) { + sdata->extra_data = (void *)calloc(1, sizeof(int)); + } + + switch (ssh_message_type(message)) { + case SSH_REQUEST_AUTH: + switch (ssh_message_subtype(message)) { + case SSH_AUTH_METHOD_INTERACTIVE: + rc = authenticate_kbdint(session, message, (void *)sdata); + if (rc == SUCCESS) { + goto accept; + } + else if (rc == MORE) { + goto more; + } + ssh_message_auth_set_methods(message, SSH_AUTH_METHOD_INTERACTIVE); + goto denied; + default: + ssh_message_auth_set_methods(message, SSH_AUTH_METHOD_INTERACTIVE); + goto denied; + } + default: + ssh_message_auth_set_methods(message, SSH_AUTH_METHOD_INTERACTIVE); + goto denied; + } + + ssh_message_free(message); + +accept: + if (sdata) { + if (sdata->extra_data) { + free(sdata->extra_data); + sdata->extra_data = NULL; + } + } + ssh_message_auth_reply_success (message, 0); +more: + return 0; +denied: + if (sdata) { + if (sdata->extra_data) { + free(sdata->extra_data); + sdata->extra_data = NULL; + } + } + return 1; +} + +static void handle_kbdint_session_cb(ssh_event event, + ssh_session session, + struct server_state_st *state) +{ + int n; + int rc = 0; + + /* Structure for storing the pty size. */ + struct winsize wsize = { + .ws_row = 0, + .ws_col = 0, + .ws_xpixel = 0, + .ws_ypixel = 0 + }; + + /* Our struct holding information about the channel. */ + struct channel_data_st cdata = { + .pid = 0, + .pty_master = -1, + .pty_slave = -1, + .child_stdin = -1, + .child_stdout = -1, + .child_stderr = -1, + .event = NULL, + .winsize = &wsize + }; + + /* Our struct holding information about the session. */ + struct session_data_st sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + .username = TORTURE_SSH_USER_BOB, + .password = TORTURE_SSH_USER_BOB_PASSWORD + }; + + struct ssh_channel_callbacks_struct *channel_cb = NULL; + struct ssh_server_callbacks_struct *server_cb = NULL; + + if (state == NULL) { + fprintf(stderr, "NULL server state provided\n"); + goto end; + } + + server_cb = get_default_server_cb(); + if (server_cb == NULL) { + goto end; + } + + server_cb->userdata = &sdata; + + /* This is a macro, it does not return a value */ + ssh_callbacks_init(server_cb); + + rc = ssh_set_server_callbacks(session, server_cb); + if (rc) { + goto end; + } + +#ifdef WITH_PCAP + set_pcap(&sdata, session, state->pcap_file); +#endif + + rc = ssh_handle_key_exchange(session); + if (rc != SSH_OK) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + goto end; + } + + /* Set the supported authentication methods */ + ssh_set_auth_methods(session, SSH_AUTH_METHOD_INTERACTIVE); + + ssh_set_message_callback(session, authenticate_callback, &sdata); + + rc = ssh_event_add_session(event, session); + if (rc != 0) { + fprintf(stderr, "Error adding session to event\n"); + goto end; + } + + n = 0; + while (sdata.authenticated == 0 || sdata.channel == NULL) { + /* If the user has used up all attempts, or if he hasn't been able to + * authenticate in 10 seconds (n * 100ms), disconnect. */ + if (sdata.auth_attempts >= state->max_tries || n >= 100) { + goto end; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + fprintf(stderr, "do_poll error: %s\n", ssh_get_error(session)); + goto end; + } + n++; + } + + channel_cb = get_default_channel_cb(); + if (channel_cb == NULL) { + goto end; + } + + channel_cb->userdata = &cdata; + + ssh_callbacks_init(channel_cb); + rc = ssh_set_channel_callbacks(sdata.channel, channel_cb); + if (rc != 0) { + goto end; + } + + do { + /* Poll the main event which takes care of the session, the channel and + * even our child process's stdout/stderr (once it's started). */ + rc = ssh_event_dopoll(event, -1); + if (rc == SSH_ERROR) { + ssh_channel_close(sdata.channel); + } + + /* If child process's stdout/stderr has been registered with the event, + * or the child process hasn't started yet, continue. */ + if (cdata.event != NULL || cdata.pid == 0) { + continue; + } + /* Executed only once, once the child process starts. */ + cdata.event = event; + /* If stdout valid, add stdout to be monitored by the poll event. */ + if (cdata.child_stdout != -1) { + if (ssh_event_add_fd(event, cdata.child_stdout, POLLIN, process_stdout, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stdout to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + + /* If stderr valid, add stderr to be monitored by the poll event. */ + if (cdata.child_stderr != -1){ + if (ssh_event_add_fd(event, cdata.child_stderr, POLLIN, process_stderr, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stderr to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + } while(ssh_channel_is_open(sdata.channel) && + (cdata.pid == 0 || waitpid(cdata.pid, &rc, WNOHANG) == 0)); + + close(cdata.pty_master); + close(cdata.child_stdin); + close(cdata.child_stdout); + close(cdata.child_stderr); + + /* Remove the descriptors from the polling context, since they are now + * closed, they will always trigger during the poll calls. */ + ssh_event_remove_fd(event, cdata.child_stdout); + ssh_event_remove_fd(event, cdata.child_stderr); + + /* If the child process exited. */ + if (kill(cdata.pid, 0) < 0 && WIFEXITED(rc)) { + rc = WEXITSTATUS(rc); + ssh_channel_request_send_exit_status(sdata.channel, rc); + /* If client terminated the channel or the process did not exit nicely, + * but only if something has been forked. */ + } else if (cdata.pid > 0) { + kill(cdata.pid, SIGKILL); + } + + ssh_channel_send_eof(sdata.channel); + ssh_channel_close(sdata.channel); + + /* Wait up to 5 seconds for the client to terminate the session. */ + for (n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) { + ssh_event_dopoll(event, 100); + } + +end: +#ifdef WITH_PCAP + cleanup_pcap(&sdata); +#endif + if (channel_cb != NULL) { + free(channel_cb); + } + if (server_cb != NULL) { + free(server_cb); + } + return; +} + +static int setup_kbdint_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + char rsa_hostkey[1024] = {0}; + + char sshd_path[1024]; + + int rc; + + char pid_str[1024]; + + pid_t pid; + + assert_non_null(state); + + tss = (struct test_server_st*)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, + sizeof(sshd_path), + "%s/sshd", + s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 0)); + + /* Create the server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->host_key = strdup(rsa_hostkey); + assert_non_null(rsa_hostkey); + + ss->verbosity = torture_libssh_verbosity(); + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + ss->max_tries = 3; + ss->error = 0; + + /* Set the session handling function */ + ss->handle_session = handle_kbdint_session_cb; + assert_non_null(ss->handle_session); + + /* Start the server */ + pid = fork_run_server(ss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait 200ms */ + usleep(200 * 1000); + + tss->state = s; + tss->ss = ss; + + *state = tss; + + return 0; +} + +static int teardown_kbdint_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool b = false; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_server_auth_kbdint(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_INTERACTIVE); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 2); + + /* Reply the first 2 prompts using the username and password */ + rc = ssh_userauth_kbdint_setanswer(session, 0, + TORTURE_SSH_USER_BOB); + assert_false(rc < 0); + + rc = ssh_userauth_kbdint_setanswer(session, 1, + TORTURE_SSH_USER_BOB_PASSWORD); + assert_false(rc < 0); + + /* Resend the password */ + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 1); + + rc = ssh_userauth_kbdint_setanswer(session, 0, + TORTURE_SSH_USER_BOB_PASSWORD); + assert_false(rc < 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + + /* Sometimes, SSH server send an empty query at the end of exchange */ + if(rc == SSH_AUTH_INFO) { + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 0); + rc = ssh_userauth_kbdint(session, NULL, NULL); + } + + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_server_auth_kbdint, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_kbdint_server, + teardown_kbdint_server); + + ssh_finalize(); + + return rc; +} diff --git a/tests/server/torture_server_config.c b/tests/server/torture_server_config.c new file mode 100644 index 0000000..d751dd7 --- /dev/null +++ b/tests/server/torture_server_config.c @@ -0,0 +1,801 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/token.h" + +#include "test_server.h" +#include "default_cb.h" + +const char template[] = "temp_dir_XXXXXX"; + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; + char *temp_dir; + char ed25519_hostkey[1024]; + char rsa_hostkey[1024]; + char ecdsa_521_hostkey[1024]; + char ecdsa_384_hostkey[1024]; + char ecdsa_256_hostkey[1024]; +#ifdef HAVE_DSA + char dsa_hostkey[1024]; +#endif /* HAVE_DSA */ +}; + +static int setup_files(void **state) +{ + struct test_server_st *tss; + struct torture_state *s; + char sshd_path[1024]; + + int rc; + + tss = (struct test_server_st*)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, + sizeof(sshd_path), + "%s/sshd", + s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(tss->rsa_hostkey, + sizeof(tss->rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(tss->rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(tss->ecdsa_521_hostkey, + sizeof(tss->ecdsa_521_hostkey), + "%s/sshd/ssh_host_ecdsa_521_key", + s->socket_dir); + torture_write_file(tss->ecdsa_521_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + snprintf(tss->ecdsa_384_hostkey, + sizeof(tss->ecdsa_384_hostkey), + "%s/sshd/ssh_host_ecdsa_384_key", + s->socket_dir); + torture_write_file(tss->ecdsa_384_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P384, 0)); + + snprintf(tss->ecdsa_256_hostkey, + sizeof(tss->ecdsa_256_hostkey), + "%s/sshd/ssh_host_ecdsa_256_key", + s->socket_dir); + torture_write_file(tss->ecdsa_256_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P256, 0)); + + if (!ssh_fips_mode()) { + snprintf(tss->ed25519_hostkey, + sizeof(tss->ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(tss->ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + +#ifdef HAVE_DSA + snprintf(tss->dsa_hostkey, + sizeof(tss->dsa_hostkey), + "%s/sshd/ssh_host_dsa_key", + s->socket_dir); + torture_write_file(tss->dsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_DSS, 0)); +#endif /* HAVE_DSA */ + } + + tss->state = s; + *state = tss; + + return 0; +} + +static int teardown_files(void **state) +{ + struct torture_state *s; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + torture_teardown_socket_dir((void **)&s); + SAFE_FREE(tss); + + return 0; +} + +static int setup_temp_dir(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + char *cwd = NULL; + char *tmp_dir = NULL; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + tss->cwd = cwd; + tss->temp_dir = tmp_dir; + + return 0; +} + +static int teardown_temp_dir(void **state) +{ + struct test_server_st *tss = *state; + int rc; + + assert_non_null(tss); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(tss->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->temp_dir); + SAFE_FREE(tss->cwd); + + return 0; +} + +static struct server_state_st *setup_server_state(char *config_file, + bool parse_global) +{ + struct server_state_st *ss = NULL; + + assert_non_null(config_file); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + ss->host_key = NULL; + + /* Use default username and password (set in default_handle_session_cb) */ + ss->expected_username = NULL; + ss->expected_password = NULL; + + ss->verbosity = torture_libssh_verbosity(); + ss->auth_methods = SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY; + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Set if should parse global configuration before */ + ss->parse_global_config = parse_global; + + /* Set the config file to be used */ + ss->config_file = strdup(config_file); + assert_non_null(ss->config_file); + + return ss; +} + +static int start_server(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct server_state_st *ss; + + char pid_str[1024]; + pid_t pid; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* Start the server using the default values */ + pid = fork_run_server(ss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + /* TODO properly wait for the server (use ping approach) */ + /* Wait 200ms */ + usleep(200 * 1000); + + return 0; +} + +static int stop_server(void **state) +{ + struct torture_state *s; + struct test_server_st *tss; + + int rc; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + rc = torture_terminate_process(s->srv_pidfile); + if (rc != 0) { + fprintf(stderr, "XXXXXX Failed to terminate sshd\n"); + } + + unlink(s->srv_pidfile); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + s = tss->state; + assert_non_null(s); + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static int try_config_content(void **state, const char *config_content, + bool parse_global) +{ + struct test_server_st *tss = *state; + struct server_state_st *ss; + struct torture_state *s; + char config_file[1024]; + int rc; + + ssh_session session; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + /* Prepare the config file to test */ + snprintf(config_file, + sizeof(config_file), + "%s/config_file", + tss->temp_dir); + + if (parse_global) { + fprintf(stderr, "Using system-wide configuration\n"); + } + fprintf(stderr, "Trying content: \n\n%s\n", config_content); + + torture_write_file(config_file, config_content); + + ss = setup_server_state(config_file, parse_global); + assert_non_null(ss); + + tss->ss = ss; + + rc = start_server(state); + assert_int_equal(rc, 0); + + rc = session_setup(state); + assert_int_equal(rc, 0); + + session = s->ssh.session; + assert_non_null(session); + + /* Authenticate as alice with bob */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + rc = session_teardown(state); + assert_int_equal(rc, 0); + + rc = stop_server(state); + assert_int_equal(rc, 0); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + + unlink(config_file); + + return 0; +} + +static char *hostkey_files[6] = {0}; + +static size_t setup_hostkey_files(struct test_server_st *tss) +{ + size_t num_hostkey_files = 1; + + hostkey_files[0] = tss->rsa_hostkey; + +#ifdef TEST_ALL_CRYPTO_COMBINATIONS + hostkey_files[1] = tss->ecdsa_256_hostkey; + hostkey_files[2] = tss->ecdsa_384_hostkey; + hostkey_files[3] = tss->ecdsa_521_hostkey; + + num_hostkey_files = 4; + + if (!ssh_fips_mode()) { + hostkey_files[4] = tss->ed25519_hostkey; + num_hostkey_files++; +#ifdef HAVE_DSA + hostkey_files[5] = tss->dsa_hostkey; + num_hostkey_files++; +#endif + } +#endif /* TEST_ALL_CRYPTO_COMBINATIONS */ + + return num_hostkey_files; +} + +static void torture_server_config_hostkey(void **state) +{ + struct test_server_st *tss = *state; + size_t i, num_hostkey_files; + char config_content[4096]; + + int rc; + + assert_non_null(tss); + + num_hostkey_files = setup_hostkey_files(tss); + + for (i = 0; i < num_hostkey_files; i++) { + snprintf(config_content, + sizeof(config_content), + "HostKey %s\n", + hostkey_files[i]); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + } +} + +static void torture_server_config_ciphers(void **state) +{ + struct test_server_st *tss = *state; + size_t i, j, num_hostkey_files = 1; + char config_content[4096]; + + const char *ciphers; + + struct ssh_tokens_st *tokens; + + int rc; + + assert_non_null(tss); + + num_hostkey_files = setup_hostkey_files(tss); + + if (ssh_fips_mode()) { + ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_S_C); + assert_non_null(ciphers); + } else { + ciphers = ssh_kex_get_default_methods(SSH_CRYPT_S_C); + assert_non_null(ciphers); + } + + tokens = ssh_tokenize(ciphers, ','); + assert_non_null(tokens); + + for (i = 0; i < num_hostkey_files; i++) { + /* Try setting all default algorithms */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nCiphers %s\n", + hostkey_files[i], ciphers); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* Try each algorithm individually */ + j = 0; + while(tokens->tokens[j] != NULL) { + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nCiphers %s\n", + hostkey_files[i], tokens->tokens[j]); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + j++; + } + } + + ssh_tokens_free(tokens); +} + +static void torture_server_config_macs(void **state) +{ + struct test_server_st *tss = *state; + size_t i, j, num_hostkey_files = 1; + char config_content[4096]; + + const char *macs; + + struct ssh_tokens_st *tokens; + + int rc; + + assert_non_null(tss); + + num_hostkey_files = setup_hostkey_files(tss); + + if (ssh_fips_mode()) { + macs = ssh_kex_get_fips_methods(SSH_MAC_S_C); + assert_non_null(macs); + } else { + macs = ssh_kex_get_default_methods(SSH_MAC_S_C); + assert_non_null(macs); + } + + tokens = ssh_tokenize(macs, ','); + assert_non_null(tokens); + + for (i = 0; i < num_hostkey_files; i++) { + /* Try setting all default algorithms */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nMACs %s\n", + hostkey_files[i], macs); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* Try each algorithm individually */ + j = 0; + while(tokens->tokens[j] != NULL) { + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nMACs %s\n", + hostkey_files[i], tokens->tokens[j]); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + j++; + } + } + + ssh_tokens_free(tokens); +} + +static void torture_server_config_kex(void **state) +{ + struct test_server_st *tss = *state; + size_t i, j, num_hostkey_files = 1; + char config_content[4096]; + + const char *kex; + + struct ssh_tokens_st *tokens; + + int rc; + + assert_non_null(tss); + + num_hostkey_files = setup_hostkey_files(tss); + + if (ssh_fips_mode()) { + kex = ssh_kex_get_fips_methods(SSH_KEX); + assert_non_null(kex); + } else { + kex = ssh_kex_get_default_methods(SSH_KEX); + assert_non_null(kex); + } + + tokens = ssh_tokenize(kex, ','); + assert_non_null(tokens); + + for (i = 0; i < num_hostkey_files; i++) { + /* Try setting all default algorithms */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nKexAlgorithms %s\n", + hostkey_files[i], kex); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* Try each algorithm individually */ + j = 0; + while(tokens->tokens[j] != NULL) { + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nKexAlgorithms %s\n", + hostkey_files[i], tokens->tokens[j]); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + j++; + } + } + + ssh_tokens_free(tokens); +} + +static void torture_server_config_hostkey_algorithms(void **state) +{ + struct test_server_st *tss = *state; + size_t i, num_hostkey_files = 5; + char config_content[4096]; + + const char *allowed; + + int rc; + + assert_non_null(tss); + + num_hostkey_files = setup_hostkey_files(tss); + + if (ssh_fips_mode()) { + allowed = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + assert_non_null(allowed); + } else { + allowed = ssh_kex_get_default_methods(SSH_HOSTKEYS); + assert_non_null(allowed); + } + + for (i = 0; i < num_hostkey_files; i++) { + /* Should work with all allowed */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostKeyAlgorithms %s\n", + hostkey_files[i], allowed); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + } + + /* Should work with matching hostkey and allowed algorithm */ + + if (!ssh_fips_mode()) { + /* ed25519 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->ed25519_hostkey, "ssh-ed25519"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* ssh-rsa */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->rsa_hostkey, "ssh-rsa"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + } + + /* rsa-sha2-256 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->rsa_hostkey, "rsa-sha2-256"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* ssh-sha2-512 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->rsa_hostkey, "rsa-sha2-512"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* ecdsa-sha2-nistp256 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->ecdsa_256_hostkey, "ecdsa-sha2-nistp256"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* ecdsa-sha2-nistp384 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->ecdsa_384_hostkey, "ecdsa-sha2-nistp384"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* ecdsa-sha2-nistp521 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->ecdsa_521_hostkey, "ecdsa-sha2-nistp521"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + +#ifdef HAVE_DSA + if (!ssh_fips_mode()) { + /* ssh-dss */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->dsa_hostkey, "ssh-dss"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + } +#endif +} + +static void torture_server_config_unknown(void **state) +{ + struct test_server_st *tss = *state; + char config_content[4096]; + + int rc; + + assert_non_null(tss); + assert_non_null(tss->rsa_hostkey); + + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nUnknownOption unknown-value1,unknown-value2\n", + tss->rsa_hostkey); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_server_config_hostkey, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_ciphers, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_macs, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_kex, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_hostkey_algorithms, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_unknown, + setup_temp_dir, teardown_temp_dir), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_files, + teardown_files); + + ssh_finalize(); + + return rc; +} diff --git a/tests/sftp_stress/main.c b/tests/sftp_stress/main.c new file mode 100644 index 0000000..ac3f8bb --- /dev/null +++ b/tests/sftp_stress/main.c @@ -0,0 +1,174 @@ +/* + * main.c + * + * Created on: 22 juin 2009 + * Author: aris + */ +#include +#include +#include +#include +#include +#include +#include +#include +#define TEST_READ 1 +#define TEST_WRITE 2 +#define NTHREADS 3 +#define FILESIZE 100000 +unsigned char samplefile[FILESIZE]; +volatile int stop=0; + +const char* hosts[]={"localhost","barebone"}; +void signal_stop(){ + stop=1; + printf("Stopping...\n"); +} + +SSH_SESSION *connect_host(const char *hostname); +int sftp_test(SSH_SESSION *session, int test); + +int docycle(const char *host, int test){ + SSH_SESSION *session=connect_host(host); + int ret=SSH_ERROR; + if(!session){ + printf("connect failed\n"); + } else { + printf("Connected\n"); + ret=sftp_test(session,test); + if(ret != SSH_OK){ + printf("Error in sftp\n"); + } + ssh_disconnect(session); + } + return ret; +} + +int thread(){ + while(docycle(hosts[rand()%2],TEST_WRITE) == SSH_OK) + if(stop) + break; + return 0; +} + +int main(int argc, char **argv){ + int i; + pthread_t threads[NTHREADS]; + ssh_init(); + srand(time(NULL)); + for(i=0;i + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *banner = NULL; + ssh_session session = NULL; + int rc = 1; + + if (argc < 1 || argv[1] == NULL) { + fprintf(stderr, "Error: Need an argument (hostname)\n"); + goto out; + } + + session = ssh_new(); + if (session == NULL) { + goto out; + } + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, argv[1]); + if (rc < 0) { + goto out; + } + + /* The automatic username is not available under uid wrapper */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, "ping"); + if (rc < 0) { + goto out; + } + + rc = ssh_connect(session); + if (rc != SSH_OK) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + goto out; + } + + banner = ssh_get_serverbanner(session); + if (banner == NULL) { + fprintf(stderr, "Did not receive SSH banner\n"); + goto out; + } + + printf("OK: %s\n", banner); + rc = 0; + +out: + ssh_free(session); + return rc; +} + diff --git a/tests/test_exec.c b/tests/test_exec.c new file mode 100644 index 0000000..352cc83 --- /dev/null +++ b/tests/test_exec.c @@ -0,0 +1,62 @@ +/* +This file is distributed in public domain. You can do whatever you want +with its content. +*/ +#include +#include +#include +#include "tests.h" + +void do_connect(SSH_SESSION *session) { + char buf[4096] = {0}; + CHANNEL *channel; + + int error = ssh_connect(session); + if (error != SSH_OK) { + fprintf(stderr,"Error at connection: %s\n", ssh_get_error(session)); + return; + } + printf("Connected\n"); + + ssh_session_is_known_server(session); + + error = authenticate(session); + if(error != SSH_AUTH_SUCCESS) { + fprintf(stderr,"Error at authentication: %s\n", ssh_get_error(session)); + return; + } + printf("Authenticated\n"); + channel = ssh_channel_new(session); + ssh_channel_open_session(channel); + printf("Execute 'ls' on the channel\n"); + error = ssh_channel_request_exec(channel, "ls"); + if(error != SSH_OK){ + fprintf(stderr, "Error executing command: %s\n", ssh_get_error(session)); + return; + } + printf("--------------------output----------------------\n"); + while (ssh_channel_read(channel, buf, sizeof(buf), 0)) { + printf("%s", buf); + } + printf("\n"); + printf("---------------------end------------------------\n"); + ssh_channel_send_eof(channel); + fprintf(stderr, "Exit status: %d\n", ssh_channel_get_exit_status(channel)); + + printf("\nChannel test finished\n"); + ssh_channel_close(channel); + ssh_channel_free(channel); +} + +int main(int argc, char **argv){ + SSH_OPTIONS *options=set_opts(argc, argv); + SSH_SESSION *session=ssh_new(); + if(options==NULL){ + return 1; + } + ssh_set_options(session,options); + do_connect(session); + ssh_disconnect(session); + ssh_finalize(); + return 0; +} diff --git a/tests/test_pcap.c b/tests/test_pcap.c new file mode 100644 index 0000000..01aa714 --- /dev/null +++ b/tests/test_pcap.c @@ -0,0 +1,50 @@ +/* + * 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. + */ + +/* Simple test for the pcap functions */ + +#include +#include +#include + +#include +#include +#include + +int main(int argc, char **argv){ + ssh_pcap_file pcap; + ssh_pcap_context ctx; + ssh_buffer buffer=ssh_buffer_new(); + char *str="Hello, this is a test string to test the capabilities of the" + "pcap file writer."; + printf("Simple pcap tester\n"); + pcap=ssh_pcap_file_new(); + if(ssh_pcap_file_open(pcap,"test.cap") != SSH_OK){ + printf("error happened\n"); + return EXIT_FAILURE; + } + buffer_add_data(buffer,str,strlen(str)); + ctx=ssh_pcap_context_new(NULL); + ssh_pcap_context_set_file(ctx,pcap); + ssh_pcap_context_write(ctx,SSH_PCAP_DIR_OUT,str,strlen(str),strlen(str)); + + return EXIT_SUCCESS; +} diff --git a/tests/test_socket.c b/tests/test_socket.c new file mode 100644 index 0000000..84f7b35 --- /dev/null +++ b/tests/test_socket.c @@ -0,0 +1,93 @@ +/* + * 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. + */ + +/* Simple test for the socket callbacks */ + +#include +#include +#include +#include + +#include +#include +#include + +int stop=0; +ssh_socket s; + +static int data_rcv(const void *data, size_t len, void *user){ + printf("Received data: '"); + fwrite(data,1,len,stdout); + printf("'\n"); + ssh_socket_write(s,"Hello you !\n",12); + ssh_socket_nonblocking_flush(s); + return len; +} + +static void controlflow(int code,void *user){ + printf("Control flow: %x\n",code); +} + +static void exception(int code, int errno_code,void *user){ + printf("Exception: %d (%d)\n",code,errno_code); + stop=1; +} + +static void connected(int code, int errno_code,void *user){ + if(code == SSH_SOCKET_CONNECTED_OK) + printf("Connected: %d (%d)\n",code, errno_code); + else { + printf("Error while connecting:(%d, %d:%s)\n",code,errno_code,strerror(errno_code)); + stop=1; + } +} + +struct ssh_socket_callbacks_struct callbacks={ + data_rcv, + controlflow, + exception, + connected, + NULL +}; +int main(int argc, char **argv){ + ssh_session session; + ssh_poll_ctx ctx; + int verbosity=SSH_LOG_FUNCTIONS; + if(argc < 3){ + printf("Usage : %s host port\n", argv[0]); + return EXIT_FAILURE; + } + session=ssh_new(); + ssh_options_set(session,SSH_OPTIONS_LOG_VERBOSITY,&verbosity); + ssh_init(); + s=ssh_socket_new(session); + ctx=ssh_poll_ctx_new(2); + ssh_socket_set_callbacks(s, &callbacks); + ssh_poll_ctx_add_socket(ctx,s); + if(ssh_socket_connect(s,argv[1],atoi(argv[2]),NULL) != SSH_OK){ + printf("ssh_socket_connect: %s\n",ssh_get_error(session)); + return EXIT_FAILURE; + } + while(!stop) + ssh_poll_ctx_dopoll(ctx,-1); + printf("finished\n"); + return EXIT_SUCCESS; +} diff --git a/tests/test_ssh_bind_accept_fd.c b/tests/test_ssh_bind_accept_fd.c new file mode 100644 index 0000000..5aa8211 --- /dev/null +++ b/tests/test_ssh_bind_accept_fd.c @@ -0,0 +1,147 @@ +/* Test the ability to use ssh_bind_accept_fd. + * + * Expected behavior: Prints "SUCCESS!" + * + * Faulty behavior observed before change: Connection timeout + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct options { + const char *server_keyfile; +} options; + +const char HOST[] = "127.0.0.1"; +const int PORT = 3333; + +int get_connection() { + int rc, server_socket, client_conn = -1; + struct sockaddr_in server_socket_addr; + struct sockaddr_storage client_conn_addr; + socklen_t client_conn_addr_size = sizeof(client_conn_addr); + + server_socket = socket(PF_INET, SOCK_STREAM, 0); + if (server_socket < 0) { + goto out; + } + + server_socket_addr.sin_family = AF_INET; + server_socket_addr.sin_port = htons(PORT); + if (inet_pton(AF_INET, HOST, &server_socket_addr.sin_addr) != 1) { + goto out; + } + + rc = bind(server_socket, (struct sockaddr *)&server_socket_addr, + sizeof(server_socket_addr)); + if (rc < 0) { + goto out; + } + + if (listen(server_socket, 0) < 0) { + goto out; + } + + client_conn = accept(server_socket, + (struct sockaddr *)&client_conn_addr, + &client_conn_addr_size); + + out: + return client_conn; +} + +void ssh_server() { + ssh_bind bind; + ssh_session session; + + int client_conn = get_connection(); + if (client_conn < 0) { + err(1, "get_connection"); + } + + bind = ssh_bind_new(); + if (!bind) { + errx(1, "ssh_bind_new"); + } + +#ifdef HAVE_DSA + /*TODO mbedtls this is probably required */ + if (ssh_bind_options_set(bind, SSH_BIND_OPTIONS_DSAKEY, + options.server_keyfile) != SSH_OK) { + errx(1, "ssh_bind_options_set(SSH_BIND_OPTIONS_DSAKEY"); + } +#else + if (ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSAKEY, + options.server_keyfile) != SSH_OK) { + errx(1, "ssh_bind_options_set(SSH_BIND_OPTIONS_RSAKEY"); + } +#endif + + session = ssh_new(); + if (!session) { + errx(1, "ssh_new"); + } + + if (ssh_bind_accept_fd(bind, session, client_conn) != SSH_OK) { + errx(1, "ssh_bind_accept: %s", ssh_get_error(bind)); + } + + if (ssh_handle_key_exchange(session) != SSH_OK) { + errx(1, "ssh_handle_key_exchange: %s", ssh_get_error(session)); + } + + printf("SUCCESS!\n"); +} + +void ssh_client() { + ssh_session session; + + session = ssh_new(); + if (!session) { + errx(1, "ssh_new"); + } + + if (ssh_options_set(session, SSH_OPTIONS_HOST, HOST) < 0) { + errx(1, "ssh_options_set(SSH_OPTIONS_HOST)"); + } + if (ssh_options_set(session, SSH_OPTIONS_PORT, &PORT) < 0) { + errx(1, "ssh_options_set(SSH_OPTIONS_PORT)"); + } + + if (ssh_connect(session) != SSH_OK) { + errx(1, "ssh_connect: %s", ssh_get_error(session)); + } +} + +int main(int argc, const char *argv[]) { + if (argc != 2) { + printf("Usage: %s \n", argv[0]); + exit(1); + } + + options.server_keyfile = argv[1]; + + pid_t pid = fork(); + if (pid < 0) { + errx(1, "fork"); + } + if (pid == 0) { + /* Allow the server to get set up */ + sleep(3); + + ssh_client(); + } else { + ssh_server(); + } + + return 0; +} diff --git a/tests/test_tunnel.c b/tests/test_tunnel.c new file mode 100644 index 0000000..952c73e --- /dev/null +++ b/tests/test_tunnel.c @@ -0,0 +1,76 @@ +/* +This file is distributed in public domain. You can do whatever you want +with its content. +*/ +#include +#include +#include +#include "tests.h" +#define ECHO_PORT 7 +void do_connect(SSH_SESSION *session){ + int error=ssh_connect(session); + if(error != SSH_OK){ + fprintf(stderr,"Error at connection :%s\n",ssh_get_error(session)); + return; + } + printf("Connected\n"); + ssh_session_is_known_server(session); + // we don't care what happens here + error=authenticate(session); + if(error != SSH_AUTH_SUCCESS){ + fprintf(stderr,"Error at authentication :%s\n",ssh_get_error(session)); + return; + } + printf("Authenticated\n"); + CHANNEL *channel=ssh_channel_new(session); + error=ssh_channel_open_forward(channel,"localhost",ECHO_PORT,"localhost",42); + if(error!=SSH_OK){ + fprintf(stderr,"Error when opening forward:%s\n",ssh_get_error(session)); + return; + } + printf("Forward opened\n"); + int i=0; + char string[20]; + char buffer[20]; + for(i=0;i<2000;++i){ + sprintf(string,"%d\n",i); + ssh_channel_write(channel,string,strlen(string)); + do { + error=ssh_channel_poll(channel,0); + //if(error < strlen(string)) + //usleep(10); + } while(error < strlen(string) && error >= 0); + if(error>0){ + error=ssh_channel_read_nonblocking(channel,buffer,strlen(string),0); + if(error>=0){ + if(memcmp(buffer,string,strlen(string))!=0){ + fprintf(stderr,"Problem with answer: wanted %s got %s\n",string,buffer); + } else { + printf("."); + fflush(stdout); + } + } + + } + if(error==-1){ + fprintf(stderr,"Channel reading error : %s\n",ssh_get_error(session)); + break; + } + } + printf("\nChannel test finished\n"); + ssh_channel_close(channel); + ssh_channel_free(channel); +} + +int main(int argc, char **argv){ + SSH_OPTIONS *options=set_opts(argc, argv); + SSH_SESSION *session=ssh_new(); + if(options==NULL){ + return 1; + } + ssh_set_options(session,options); + do_connect(session); + ssh_disconnect(session); + ssh_finalize(); + return 0; +} diff --git a/tests/tests.h b/tests/tests.h new file mode 100644 index 0000000..dd001f1 --- /dev/null +++ b/tests/tests.h @@ -0,0 +1,8 @@ +/* +This file is distributed in public domain. You can do whatever you want +with its content. +*/ +#include +int authenticate (SSH_SESSION *session); +SSH_OPTIONS *set_opts(int argc, char **argv); + diff --git a/tests/torture.c b/tests/torture.c new file mode 100644 index 0000000..907f45b --- /dev/null +++ b/tests/torture.c @@ -0,0 +1,1385 @@ +/* + * torture.c - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 by Andreas Schneider + * + * 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 +#include +#include +#include +#include +#include + +#ifndef _WIN32 +# include +# include +# include +#endif + +#ifdef HAVE_UNISTD_H +#include +#elif (defined _WIN32) || (defined _WIN64) +#include +#include +#define read _read +#define open _open +#define write _write +#define close _close +#define chdir _chdir +#endif + +#include "torture.h" +#include "torture_key.h" +#include "libssh/misc.h" + +#define TORTURE_SSHD_SRV_IPV4 "127.0.0.10" +/* socket wrapper IPv6 prefix fd00::5357:5fxx */ +#define TORTURE_SSHD_SRV_IPV6 "fd00::5357:5f0a" +#define TORTURE_SSHD_SRV_PORT 22 + +#define TORTURE_SOCKET_DIR "/tmp/test_socket_wrapper_XXXXXX" +#define TORTURE_SSHD_PIDFILE "sshd/sshd.pid" +#define TORTURE_SSHD_CONFIG "sshd/sshd_config" +#define TORTURE_PCAP_FILE "socket_trace.pcap" + +static const char torture_rsa_certauth_pub[]= + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCnA2n5vHzZbs/GvRkGloJNV1CXHI" + "S5Xnrm05HusUJSWyPq3I1iCMHdYA7oezHa9GCFYbIenaYPy+G6USQRjYQz8SvAZo06" + "SFNeJSsa1kAIqxzdPT9kBrRrYK39PZQPsYVfRPqZBdmc+jwrfz97IFEJyXMI47FoTG" + "kgEq7eu3z2px/tdIZ34I5Hr5DDBxicZi4jluyRUJHfSPoBxyhF7OkPX4bYkrc691je" + "IQDxubl650WYLHgFfad0xTzBIFE6XUb55Dp5AgRdevSoso1Pe0IKFxxMVpP664LCbY" + "K06Lv6kcotfFlpvUtR1yx8jToGcSoq5sSzTwvXSHCQQ9ZA1hvF " + "torture_certauth_key"; + +static int verbosity = 0; +static const char *pattern = NULL; + +#ifndef _WIN32 + +static int _torture_auth_kbdint(ssh_session session, + const char *password) { + const char *prompt; + char echo; + int err; + + if (session == NULL || password == NULL) { + return SSH_AUTH_ERROR; + } + + err = ssh_userauth_kbdint(session, NULL, NULL); + if (err == SSH_AUTH_ERROR) { + return err; + } + + if (ssh_userauth_kbdint_getnprompts(session) != 1) { + return SSH_AUTH_ERROR; + } + + prompt = ssh_userauth_kbdint_getprompt(session, 0, &echo); + if (prompt == NULL) { + return SSH_AUTH_ERROR; + } + + if (ssh_userauth_kbdint_setanswer(session, 0, password) < 0) { + return SSH_AUTH_ERROR; + } + err = ssh_userauth_kbdint(session, NULL, NULL); + if (err == SSH_AUTH_INFO) { + if (ssh_userauth_kbdint_getnprompts(session) != 0) { + return SSH_AUTH_ERROR; + } + err = ssh_userauth_kbdint(session, NULL, NULL); + } + + return err; +} + +int torture_rmdirs(const char *path) { + DIR *d; + struct dirent *dp; + struct stat sb; + char *fname; + + if ((d = opendir(path)) != NULL) { + while(stat(path, &sb) == 0) { + /* if we can remove the directory we're done */ + if (rmdir(path) == 0) { + break; + } + switch (errno) { + case ENOTEMPTY: + case EEXIST: + case EBADF: + break; /* continue */ + default: + closedir(d); + return 0; + } + + while ((dp = readdir(d)) != NULL) { + size_t len; + /* skip '.' and '..' */ + if (dp->d_name[0] == '.' && + (dp->d_name[1] == '\0' || + (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) { + continue; + } + + len = strlen(path) + strlen(dp->d_name) + 2; + fname = malloc(len); + if (fname == NULL) { + closedir(d); + return -1; + } + snprintf(fname, len, "%s/%s", path, dp->d_name); + + /* stat the file */ + if (lstat(fname, &sb) != -1) { + if (S_ISDIR(sb.st_mode) && !S_ISLNK(sb.st_mode)) { + if (rmdir(fname) < 0) { /* can't be deleted */ + if (errno == EACCES) { + closedir(d); + SAFE_FREE(fname); + return -1; + } + torture_rmdirs(fname); + } + } else { + unlink(fname); + } + } /* lstat */ + SAFE_FREE(fname); + } /* readdir */ + + rewinddir(d); + } + } else { + return -1; + } + + closedir(d); + return 0; +} + +int torture_isdir(const char *path) { + struct stat sb; + + if (lstat (path, &sb) == 0 && S_ISDIR(sb.st_mode)) { + return 1; + } + + return 0; +} + +static pid_t +torture_read_pidfile(const char *pidfile) +{ + char buf[8] = {0}; + long int tmp; + pid_t ret; + ssize_t rc; + int fd; + + fd = open(pidfile, O_RDONLY); + if (fd < 0) { + return -1; + } + + rc = read(fd, buf, sizeof(buf)); + close(fd); + if (rc <= 0) { + return -1; + } + + buf[sizeof(buf) - 1] = '\0'; + + tmp = strtol(buf, NULL, 10); + if (tmp == 0 || errno == ERANGE) { + return -1; + } + ret = (pid_t)tmp; + /* Check if we are out of pid_t range on this system */ + if ((long)ret != tmp) { + return -1; + } + + return ret; +} + +int torture_terminate_process(const char *pidfile) +{ + ssize_t rc; + pid_t pid; + int is_running = 1; + int count; + + /* read the pidfile */ + pid = torture_read_pidfile(pidfile); + assert_int_not_equal(pid, -1); + + for (count = 0; count < 10; count++) { + /* Make sure the daemon goes away! */ + kill(pid, SIGTERM); + + /* 10 ms */ + usleep(10 * 1000); + + rc = kill(pid, 0); + if (rc != 0) { + is_running = 0; + break; + } + } + + if (is_running) { + fprintf(stderr, + "WARNING: The process with pid %u is still running!\n", pid); + } + + return 0; +} + +ssh_session torture_ssh_session(struct torture_state *s, + const char *host, + const unsigned int *port, + const char *user, + const char *password) { + ssh_session session; + int method; + int rc; + + bool process_config = false; + + if (host == NULL) { + return NULL; + } + + session = ssh_new(); + if (session == NULL) { + return NULL; + } + +#ifdef WITH_PCAP + if (s != NULL && s->plain_pcap != NULL) { + ssh_set_pcap_file(session, s->plain_pcap); + } +#endif /* WITH_PCAP */ + + if (ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity) < 0) { + goto failed; + } + + if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) { + goto failed; + } + + if (port != NULL) { + if (ssh_options_set(session, SSH_OPTIONS_PORT, port) < 0) { + goto failed; + } + } + + if (user != NULL) { + if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) { + goto failed; + } + } + + if (ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, + &process_config) < 0) { + goto failed; + } + + if (ssh_connect(session)) { + goto failed; + } + + /* We are in testing mode, so consinder the hostkey as verified ;) */ + + /* This request should return a SSH_REQUEST_DENIED error */ + rc = ssh_userauth_none(session, NULL); + if (rc == SSH_ERROR) { + goto failed; + } + method = ssh_userauth_list(session, NULL); + if (method == 0) { + goto failed; + } + + if (password != NULL) { + if (method & SSH_AUTH_METHOD_PASSWORD) { + rc = ssh_userauth_password(session, NULL, password); + } else if (method & SSH_AUTH_METHOD_INTERACTIVE) { + rc = _torture_auth_kbdint(session, password); + } + } else { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + if (rc == SSH_AUTH_ERROR) { + goto failed; + } + } + if (rc != SSH_AUTH_SUCCESS) { + goto failed; + } + + return session; +failed: + if (ssh_is_connected(session)) { + ssh_disconnect(session); + } + ssh_free(session); + + return NULL; +} + +#ifdef WITH_SERVER + +ssh_bind torture_ssh_bind(const char *addr, + const unsigned int port, + enum ssh_keytypes_e key_type, + const char *private_key_file) { + int rc; + ssh_bind sshbind = NULL; + enum ssh_bind_options_e opts = -1; + + sshbind = ssh_bind_new(); + if (sshbind == NULL) { + goto out; + } + + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, addr); + if (rc != 0) { + goto out_free; + } + + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT, &port); + if (rc != 0) { + goto out_free; + } + + switch (key_type) { +#ifdef HAVE_DSA + case SSH_KEYTYPE_DSS: + opts = SSH_BIND_OPTIONS_DSAKEY; + break; +#endif /* HAVE_DSA */ + case SSH_KEYTYPE_RSA: + opts = SSH_BIND_OPTIONS_RSAKEY; + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + opts = SSH_BIND_OPTIONS_ECDSAKEY; + break; + default: + goto out_free; + } + + rc = ssh_bind_options_set(sshbind, opts, private_key_file); + if (rc != 0) { + goto out_free; + } + + rc = ssh_bind_listen(sshbind); + if (rc != SSH_OK) { + goto out_free; + } + + goto out; + out_free: + ssh_bind_free(sshbind); + sshbind = NULL; + out: + return sshbind; +} + +#endif /* WITH_SERVER */ + +#ifdef WITH_SFTP + +struct torture_sftp *torture_sftp_session(ssh_session session) { + struct torture_sftp *t; + char template[] = "/tmp/ssh_torture_XXXXXX"; + char *p; + int rc; + + if (session == NULL) { + return NULL; + } + + t = malloc(sizeof(struct torture_sftp)); + if (t == NULL) { + return NULL; + } + + t->ssh = session; + t->sftp = sftp_new(session); + if (t->sftp == NULL) { + goto failed; + } + + rc = sftp_init(t->sftp); + if (rc < 0) { + goto failed; + } + + p = mkdtemp(template); + if (p == NULL) { + goto failed; + } + /* useful if TESTUSER is not the local user */ + chmod(template,0777); + t->testdir = strdup(p); + if (t->testdir == NULL) { + goto failed; + } + + return t; +failed: + if (t->sftp != NULL) { + sftp_free(t->sftp); + } + ssh_disconnect(t->ssh); + ssh_free(t->ssh); + free(t); + + return NULL; +} + +void torture_sftp_close(struct torture_sftp *t) { + if (t == NULL) { + return; + } + + if (t->sftp != NULL) { + sftp_free(t->sftp); + } + + free(t->testdir); + free(t); +} +#endif /* WITH_SFTP */ + +int torture_server_port(void) +{ + char *env = getenv("TORTURE_SERVER_PORT"); + + if (env != NULL && env[0] != '\0' && strlen(env) < 6) { + int port = atoi(env); + + if (port > 0 && port < 65536) { + return port; + } + } + + return TORTURE_SSHD_SRV_PORT; +} + +const char *torture_server_address(int family) +{ + switch (family) { + case AF_INET: { + const char *ip4 = getenv("TORTURE_SERVER_ADDRESS_IPV4"); + + if (ip4 != NULL && ip4[0] != '\0') { + return ip4; + } + + return TORTURE_SSHD_SRV_IPV4; + } + case AF_INET6: { + const char *ip6 = getenv("TORTURE_SERVER_ADDRESS_IPV6"); + + if (ip6 != NULL && ip6[0] != '\0') { + return ip6; + } + + return TORTURE_SSHD_SRV_IPV6; + } + default: + return NULL; + } + + return NULL; +} + +void torture_setup_socket_dir(void **state) +{ + struct torture_state *s; + const char *p; + size_t len; + char *env = NULL; + int rc; + + s = calloc(1, sizeof(struct torture_state)); + assert_non_null(s); + +#ifdef WITH_PCAP + env = getenv("TORTURE_PLAIN_PCAP_FILE"); + if (env != NULL && env[0] != '\0') { + s->plain_pcap = ssh_pcap_file_new(); + assert_non_null(s->plain_pcap); + + rc = ssh_pcap_file_open(s->plain_pcap, env); + assert_int_equal(rc, SSH_OK); + } +#endif /* WITH_PCAP */ + + s->socket_dir = torture_make_temp_dir(TORTURE_SOCKET_DIR); + assert_non_null(s->socket_dir); + + p = s->socket_dir; + + /* pcap file */ + len = strlen(p) + 1 + strlen(TORTURE_PCAP_FILE) + 1; + + s->pcap_file = malloc(len); + assert_non_null(s->pcap_file); + + snprintf(s->pcap_file, len, "%s/%s", p, TORTURE_PCAP_FILE); + + /* pid file */ + len = strlen(p) + 1 + strlen(TORTURE_SSHD_PIDFILE) + 1; + + s->srv_pidfile = malloc(len); + assert_non_null(s->srv_pidfile); + + snprintf(s->srv_pidfile, len, "%s/%s", p, TORTURE_SSHD_PIDFILE); + + /* config file */ + len = strlen(p) + 1 + strlen(TORTURE_SSHD_CONFIG) + 1; + + s->srv_config = malloc(len); + assert_non_null(s->srv_config); + + snprintf(s->srv_config, len, "%s/%s", p, TORTURE_SSHD_CONFIG); + + setenv("SOCKET_WRAPPER_DIR", p, 1); + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "170", 1); + env = getenv("TORTURE_GENERATE_PCAP"); + if (env != NULL && env[0] == '1') { + setenv("SOCKET_WRAPPER_PCAP_FILE", s->pcap_file, 1); + } + + *state = s; +} + +static void torture_setup_create_sshd_config(void **state, bool pam) +{ + struct torture_state *s = *state; + char ed25519_hostkey[1024] = {0}; +#ifdef HAVE_DSA + char dsa_hostkey[1024]; +#endif /* HAVE_DSA */ + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + char trusted_ca_pubkey[1024]; + char sshd_config[4096]; + char sshd_path[1024]; + const char *additional_config = NULL; + struct stat sb; + const char *sftp_server_locations[] = { + "/usr/lib/ssh/sftp-server", + "/usr/libexec/sftp-server", + "/usr/libexec/openssh/sftp-server", + "/usr/lib/openssh/sftp-server", /* Debian */ + }; +#ifndef OPENSSH_VERSION_MAJOR +#define OPENSSH_VERSION_MAJOR 7U +#define OPENSSH_VERSION_MINOR 0U +#endif /* OPENSSH_VERSION_MAJOR */ + const char config_string[]= + "Port 22\n" + "ListenAddress 127.0.0.10\n" + "%s %s\n" +#ifdef HAVE_DSA + "%s %s\n" +#endif /* HAVE_DSA */ + "%s %s\n" + "%s %s\n" + "\n" + "TrustedUserCAKeys %s\n" + "\n" + "LogLevel DEBUG3\n" + "Subsystem sftp %s -l DEBUG2\n" + "\n" + "PasswordAuthentication yes\n" + "PubkeyAuthentication yes\n" + "\n" + "StrictModes no\n" + "\n" + "%s" /* Here comes UsePam */ + "\n" +#if (OPENSSH_VERSION_MAJOR == 6 && OPENSSH_VERSION_MINOR >= 7) || (OPENSSH_VERSION_MAJOR >= 7) +# ifdef HAVE_DSA + "HostKeyAlgorithms +ssh-dss\n" +# else /* HAVE_DSA */ + "HostKeyAlgorithms +ssh-rsa\n" +# endif /* HAVE_DSA */ +# if (OPENSSH_VERSION_MAJOR == 7 && OPENSSH_VERSION_MINOR < 6) + "Ciphers +3des-cbc,aes128-cbc,aes192-cbc,aes256-cbc,blowfish-cbc\n" +# else /* OPENSSH_VERSION 7.0 - 7.5 */ + "Ciphers +3des-cbc,aes128-cbc,aes192-cbc,aes256-cbc\n" +# endif /* OPENSSH_VERSION 7.0 - 7.6 */ + "KexAlgorithms +diffie-hellman-group1-sha1," + "diffie-hellman-group-exchange-sha1" +#else /* OPENSSH_VERSION >= 6.7 */ + "Ciphers 3des-cbc,aes128-cbc,aes192-cbc,aes256-cbc,aes128-ctr," + "aes192-ctr,aes256-ctr,aes128-gcm@openssh.com," + "aes256-gcm@openssh.com,arcfour128,arcfour256,arcfour," + "blowfish-cbc,cast128-cbc,chacha20-poly1305@openssh.com\n" + "KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp256," + "ecdh-sha2-nistp384,ecdh-sha2-nistp521," + "diffie-hellman-group-exchange-sha256," + "diffie-hellman-group-exchange-sha1," + "diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha1," + "diffie-hellman-group1-sha1\n" +#endif /* OPENSSH_VERSION >= 6.7 */ + "\n" + "AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES\n" + "AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT\n" + "AcceptEnv LC_IDENTIFICATION LC_ALL LC_LIBSSH\n" + "\n" + "PidFile %s\n" + "%s\n"; /* The space for test-specific options */ + /* FIPS config */ + const char fips_config_string[]= + "Port 22\n" + "ListenAddress 127.0.0.10\n" + "%s %s\n" /* HostKey */ + "%s %s\n" /* HostKey */ + "\n" + "TrustedUserCAKeys %s\n" /* Trusted CA */ + "\n" + "LogLevel DEBUG3\n" + "Subsystem sftp %s -l DEBUG2\n" /* SFTP server */ + "\n" + "PasswordAuthentication yes\n" + "PubkeyAuthentication yes\n" + "\n" + "StrictModes no\n" + "\n" + "%s" /* UsePam */ + "\n" + "Ciphers " + "aes256-gcm@openssh.com,aes256-ctr,aes256-cbc," + "aes128-gcm@openssh.com,aes128-ctr,aes128-cbc" + "\n" + "MACs " + "hmac-sha2-256-etm@openssh.com,hmac-sha1-etm@openssh.com," + "hmac-sha2-512-etm@openssh.com,hmac-sha2-256," + "hmac-sha1,hmac-sha2-512" + "\n" + "GSSAPIKeyExchange no\n" + "KexAlgorithms " + "ecdh-sha2-nistp256,ecdh-sha2-nistp384," + "ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256," + "diffie-hellman-group14-sha256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512" + "\n" + "PubkeyAcceptedKeyTypes " + "rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com," + "ecdsa-sha2-nistp256,ecdsa-sha2-nistp256-cert-v01@openssh.com," + "ecdsa-sha2-nistp384,ecdsa-sha2-nistp384-cert-v01@openssh.com," + "rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com," + "ecdsa-sha2-nistp521,ecdsa-sha2-nistp521-cert-v01@openssh.com" + "\n" + "AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES\n" + "AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT\n" + "AcceptEnv LC_IDENTIFICATION LC_ALL LC_LIBSSH\n" + "\n" + "PidFile %s\n" /* PID file */ + "%s\n"; /* The space for test-specific options */ + const char usepam_yes[] = + "UsePAM yes\n" + "KbdInteractiveAuthentication yes\n"; + const char usepam_no[] = + "UsePAM no\n" + "KbdInteractiveAuthentication no\n"; + size_t sftp_sl_size = ARRAY_SIZE(sftp_server_locations); + const char *sftp_server, *usepam; + size_t i; + bool written = false; + int rc; + + s->srv_pam = pam; + if (pam) { + usepam = usepam_yes; + } else { + usepam = usepam_no; + } + + assert_non_null(s->socket_dir); + + snprintf(sshd_path, + sizeof(sshd_path), + "%s/sshd", + s->socket_dir); + + rc = lstat(sshd_path, &sb); + if (rc == 0 ) { /* The directory is already in place */ + written = true; + } + + if (!written) { + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + } + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + +#ifdef HAVE_DSA + snprintf(dsa_hostkey, + sizeof(dsa_hostkey), + "%s/sshd/ssh_host_dsa_key", + s->socket_dir); +#endif /* HAVE_DSA */ + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + + snprintf(trusted_ca_pubkey, + sizeof(trusted_ca_pubkey), + "%s/sshd/user_ca.pub", + s->socket_dir); + + if (!written) { + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); +#ifdef HAVE_DSA + torture_write_file(dsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_DSS, 0)); +#endif /* HAVE_DSA */ + torture_write_file(rsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + torture_write_file(trusted_ca_pubkey, torture_rsa_certauth_pub); + } + + sftp_server = getenv("TORTURE_SFTP_SERVER"); + if (sftp_server == NULL) { + for (i = 0; i < sftp_sl_size; i++) { + sftp_server = sftp_server_locations[i]; + rc = lstat(sftp_server, &sb); + if (rc == 0) { + break; + } + } + } + assert_non_null(sftp_server); + + additional_config = (s->srv_additional_config != NULL ? + s->srv_additional_config : ""); + + if (ssh_fips_mode()) { + snprintf(sshd_config, sizeof(sshd_config), + fips_config_string, + "HostKey", rsa_hostkey, + "HostKey", ecdsa_hostkey, + trusted_ca_pubkey, + sftp_server, + usepam, + s->srv_pidfile, + additional_config); + } else { + snprintf(sshd_config, sizeof(sshd_config), + config_string, + "HostKey", ed25519_hostkey, +#ifdef HAVE_DSA + "HostKey", dsa_hostkey, +#endif /* HAVE_DSA */ + "HostKey", rsa_hostkey, + "HostKey", ecdsa_hostkey, + trusted_ca_pubkey, + sftp_server, + usepam, + s->srv_pidfile, + additional_config); + } + + torture_write_file(s->srv_config, sshd_config); +} + +static int torture_wait_for_daemon(unsigned int seconds) +{ + struct ssh_timestamp start; + int rc; + + ssh_timestamp_init(&start); + + while (!ssh_timeout_elapsed(&start, seconds * 1000)) { + rc = system(SSH_PING_EXECUTABLE " " TORTURE_SSH_SERVER); + if (rc == 0) { + return 0; + } + /* Wait 200 ms before retrying */ + usleep(200 * 1000); + } + return 1; +} + +void torture_setup_sshd_server(void **state, bool pam) +{ + struct torture_state *s; + char sshd_start_cmd[1024]; + int rc; + + torture_setup_socket_dir(state); + torture_setup_create_sshd_config(state, pam); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + s = *state; + + snprintf(sshd_start_cmd, sizeof(sshd_start_cmd), + "/usr/sbin/sshd -r -f %s -E %s/sshd/daemon.log 2> %s/sshd/cwrap.log", + s->srv_config, s->socket_dir, s->socket_dir); + + rc = system(sshd_start_cmd); + assert_return_code(rc, errno); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); +} + +void torture_teardown_socket_dir(void **state) +{ + struct torture_state *s = *state; + char *env = getenv("TORTURE_SKIP_CLEANUP"); + int rc; + + if (env != NULL && env[0] == '1') { + fprintf(stderr, "[ TORTURE ] >>> Skipping cleanup of %s\n", s->socket_dir); + } else { + rc = torture_rmdirs(s->socket_dir); + if (rc < 0) { + fprintf(stderr, + "torture_rmdirs(%s) failed: %s", + s->socket_dir, + strerror(errno)); + } + } +#ifdef WITH_PCAP + if (s->plain_pcap != NULL) { + ssh_pcap_file_free(s->plain_pcap); + } + s->plain_pcap = NULL; +#endif /* WITH_PCAP */ + + free(s->srv_config); + free(s->socket_dir); + free(s->pcap_file); + free(s->srv_pidfile); + free(s->srv_additional_config); + free(s); +} + +static int +torture_reload_sshd_server(void **state) +{ + struct torture_state *s = *state; + pid_t pid; + int rc; + + /* read the pidfile */ + pid = torture_read_pidfile(s->srv_pidfile); + assert_int_not_equal(pid, -1); + + kill(pid, SIGHUP); + + /* 10 ms */ + usleep(10 * 1000); + + rc = kill(pid, 0); + if (rc != 0) { + fprintf(stderr, + "ERROR: SSHD process %u died during reload!\n", pid); + return SSH_ERROR; + } + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + return SSH_OK; +} + +/* @brief: Updates SSHD server configuration with more options and + * reloads the server to apply them. + * Note, that this still uses the default configuration options specified + * in this file and overwrites options previously specified by this function. + */ +int +torture_update_sshd_config(void **state, const char *config) +{ + struct torture_state *s = *state; + int rc; + + /* Store the configuration in internal structure */ + SAFE_FREE(s->srv_additional_config); + s->srv_additional_config = strdup(config); + assert_non_null(s->srv_additional_config); + + /* Rewrite the configuration file */ + torture_setup_create_sshd_config(state, s->srv_pam); + + /* Reload the server */ + rc = torture_reload_sshd_server(state); + assert_int_equal(rc, SSH_OK); + + return SSH_OK; +} + + +void torture_teardown_sshd_server(void **state) +{ + struct torture_state *s = *state; + int rc; + + rc = torture_terminate_process(s->srv_pidfile); + if (rc != 0) { + fprintf(stderr, "XXXXXX Failed to terminate sshd\n"); + } + + torture_teardown_socket_dir(state); +} + +char *torture_make_temp_dir(const char *template) +{ + char *new_dir = NULL; + char *template_copy = NULL; + + if (template == NULL) { + goto end; + } + + template_copy = strdup(template); + if (template_copy == NULL) { + goto end; + } + + new_dir = mkdtemp(template_copy); + if (new_dir == NULL) { + SAFE_FREE(template_copy); + } + +end: + return template_copy; +} + +char *torture_create_temp_file(const char *template) +{ + char *new_file = NULL; + FILE *fp = NULL; + mode_t mask; + int fd; + + new_file = strdup(template); + if (new_file == NULL) { + goto end; + } + + mask = umask(S_IRWXO | S_IRWXG); + fd = mkstemp(new_file); + umask(mask); + if (fd == -1) { + goto end; + } + + fp = fdopen(fd, "w"); + if (fp == NULL) { + SAFE_FREE(new_file); + close(fd); + goto end; + } + + fclose(fp); + +end: + return new_file; +} + +char *torture_get_current_working_dir(void) +{ + + char *cwd = NULL; + char *result = NULL; + + cwd = (char *)malloc(PATH_MAX + 1); + if (cwd == NULL) { + goto end; + } + + result = getcwd(cwd, PATH_MAX); + + if (result == NULL) { + SAFE_FREE(cwd); + goto end; + } + +end: + return cwd; +} + +#else /* _WIN32 */ + +char *torture_make_temp_dir(const char *template) +{ + DWORD rc = 0; + char tmp_dir_path[MAX_PATH]; + char tmp_file_name[MAX_PATH]; + char *prefix = NULL; + char *path = NULL; + char *prefix_end = NULL; + char *slash = NULL; + + BOOL created; + + if (template == NULL) { + goto end; + } + + prefix = strdup(template); + if (prefix == NULL) { + goto end; + } + + /* Replace slashes with backslashes */ + slash = strchr(prefix, '/'); + for (; slash != NULL; slash = strchr(prefix, '/')) { + *slash = '\\'; + } + + prefix_end = strstr(prefix, "XXXXXX"); + if (prefix_end != NULL) { + *prefix_end = '\0'; + } + + rc = GetTempPathA(MAX_PATH, tmp_dir_path); + if ((rc > MAX_PATH) || (rc == 0)) { + goto free_prefix; + } + + rc = GetTempFileNameA(tmp_dir_path, TEXT(prefix), 0, tmp_file_name); + if (rc == 0) { + goto free_prefix; + } + + path = strdup(tmp_file_name); + if (path == NULL) { + goto free_prefix; + } + + /* GetTempFileNameA() creates a temporary file; we need to remove it */ + rc = DeleteFileA(path); + if (rc == 0) { + rc = -1; + SAFE_FREE(path); + goto free_prefix; + } + + created = CreateDirectoryA(path, NULL); + if (!created) { + SAFE_FREE(path); + } + +free_prefix: + SAFE_FREE(prefix); +end: + return path; +} + +static int recursive_rm_dir_content(const char *path) +{ + WIN32_FIND_DATA file_data; + HANDLE file_handle; + DWORD attributes; + + DWORD last_error = 0; + + char file_path[MAX_PATH]; + + int rc = 0; + BOOL removed; + + strcpy(file_path, path); + strcat(file_path, "\\*"); + + file_handle = FindFirstFile(file_path, &file_data); + + if (file_handle == INVALID_HANDLE_VALUE) { + last_error = GetLastError(); + + /* Empty directory */ + if (last_error == ERROR_FILE_NOT_FOUND) { + rc = 0; + } + else { + /*TODO print error message?*/ + rc = last_error; + } + goto end; + } + else { + do { + rc = strcmp(file_data.cFileName, "."); + if (rc == 0) { + continue; + } + + rc = strcmp(file_data.cFileName, ".."); + if (rc == 0) { + continue; + } + + /* Create full file path */ + strcpy(file_path, path); + strcat(file_path, "\\"); + strcat(file_path, file_data.cFileName); + + attributes = GetFileAttributes(file_path); + if (attributes & FILE_ATTRIBUTE_DIRECTORY) { + rc = recursive_rm_dir_content((const char *)file_path); + if (rc != 0) { + goto end; + } + + removed = RemoveDirectoryA(file_path); + + if (!removed) { + last_error = GetLastError(); + + /*TODO print error message?*/ + + rc = last_error; + goto end; + } + } + else { + rc = remove(file_path); + if (rc) { + goto end; + } + } + + } while(FindNextFile(file_handle, &file_data)); + + FindClose(file_handle); + } + +end: + return rc; +} + +int torture_rmdirs(const char *path) +{ + int rc = 0; + BOOL removed; + + rc = recursive_rm_dir_content(path); + if (rc) { + return rc; + } + + removed = RemoveDirectoryA(path); + if (!removed) { + rc = -1; + } + + return rc; +} + +int torture_isdir(const char *path) +{ + + DWORD attributes = 0; + + attributes = GetFileAttributes(path); + if (attributes & FILE_ATTRIBUTE_DIRECTORY) { + return 1; + } + + return 0; +} + +char *torture_create_temp_file(const char *template) +{ + DWORD rc = 0; + char tmp_dir_path[MAX_PATH]; + char tmp_file_name[MAX_PATH]; + char *prefix = NULL; + char *path = NULL; + char *prefix_end = NULL; + char *slash = NULL; + + if (template == NULL) { + goto end; + } + + prefix = strdup(template); + if (prefix == NULL) { + goto end; + } + + /* Replace slashes with backslashes */ + slash = strchr(prefix, '/'); + for (; slash != NULL; slash = strchr(prefix, '/')) { + *slash = '\\'; + } + + prefix_end = strstr(prefix, "XXXXXX"); + if (prefix_end != NULL) { + *prefix_end = '\0'; + } + + rc = GetTempPathA(MAX_PATH, tmp_dir_path); + if ((rc > MAX_PATH) || (rc == 0)) { + goto free_prefix; + } + + /* Remark: this function creates the file */ + rc = GetTempFileNameA(tmp_dir_path, TEXT(prefix), 0, tmp_file_name); + if (rc == 0) { + goto free_prefix; + } + + path = strdup(tmp_file_name); + +free_prefix: + SAFE_FREE(prefix); +end: + return path; +} + +char *torture_get_current_working_dir(void) +{ + char *cwd = NULL; + char *result = NULL; + + cwd = (char *)malloc(_MAX_PATH + 1); + if (cwd == NULL) { + goto end; + } + + result = _getcwd(cwd, _MAX_PATH); + + if (result == NULL) { + SAFE_FREE(cwd); + goto end; + } + +end: + return cwd; +} + +#endif /* _WIN32 */ + +int torture_change_dir(char *path) +{ + int rc = 0; + + if (path == NULL) { + rc = -1; + goto end; + } + + rc = chdir(path); + +end: + return rc; +} + +int torture_libssh_verbosity(void){ + return verbosity; +} + +void _torture_filter_tests(struct CMUnitTest *tests, size_t ntests) +{ + (void) tests; + (void) ntests; + + return; +} + +void torture_write_file(const char *filename, const char *data){ + int fd; + int rc; + + assert_non_null(filename); + assert_true(filename[0] != '\0'); + assert_non_null(data); + + fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, 0600); + assert_true(fd >= 0); + + rc = write(fd, data, strlen(data)); + assert_int_equal(rc, strlen(data)); + + close(fd); +} + +void torture_reset_config(ssh_session session) +{ + memset(session->opts.options_seen, 0, sizeof(session->opts.options_seen)); +} + +int main(int argc, char **argv) { + struct argument_s arguments; + char *env = getenv("LIBSSH_VERBOSITY"); + + arguments.verbose=0; + arguments.pattern=NULL; + torture_cmdline_parse(argc, argv, &arguments); + verbosity=arguments.verbose; + pattern=arguments.pattern; + + if (verbosity == 0 && env != NULL && env[0] != '\0') { + if (env[0] > '0' && env[0] < '9') { + verbosity = atoi(env); + } + } + +#if defined HAVE_CMOCKA_SET_TEST_FILTER + cmocka_set_test_filter(pattern); +#endif + + return torture_run_tests(); +} diff --git a/tests/torture.h b/tests/torture.h new file mode 100644 index 0000000..1cd52b2 --- /dev/null +++ b/tests/torture.h @@ -0,0 +1,144 @@ +/* + * torture.c - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 by Andreas Schneider + * + * 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. + */ + +#ifndef _TORTURE_H +#define _TORTURE_H + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/server.h" +#include "libssh/sftp.h" + +#include + +#include "torture_cmocka.h" + +#ifndef assert_return_code +/* hack for older versions of cmocka */ +#define assert_return_code(code, errno) \ + assert_true(code >= 0) +#endif /* assert_return_code */ + +#define TORTURE_SSH_SERVER "127.0.0.10" +#define TORTURE_SSH_USER_BOB "bob" +#define TORTURE_SSH_USER_BOB_PASSWORD "secret" + +#define TORTURE_SSH_USER_ALICE "alice" + +/* Used by main to communicate with parse_opt. */ +struct argument_s { + const char *pattern; + int verbose; +}; + +struct torture_sftp { + ssh_session ssh; + sftp_session sftp; + char *testdir; +}; + +struct torture_state { + char *socket_dir; + char *pcap_file; + char *srv_pidfile; + char *srv_config; + bool srv_pam; + char *srv_additional_config; + struct { + ssh_session session; + struct torture_sftp *tsftp; + } ssh; +#ifdef WITH_PCAP + ssh_pcap_file plain_pcap; +#endif +}; + +#ifndef ZERO_STRUCT +#define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x)) +#endif + +void torture_cmdline_parse(int argc, char **argv, struct argument_s *arguments); + +int torture_rmdirs(const char *path); +int torture_isdir(const char *path); + +int torture_terminate_process(const char *pidfile); + +/* + * Returns the verbosity level asked by user + */ +int torture_libssh_verbosity(void); + +ssh_session torture_ssh_session(struct torture_state *s, + const char *host, + const unsigned int *port, + const char *user, + const char *password); + +ssh_bind torture_ssh_bind(const char *addr, + const unsigned int port, + enum ssh_keytypes_e key_type, + const char *private_key_file); + +struct torture_sftp *torture_sftp_session(ssh_session session); +void torture_sftp_close(struct torture_sftp *t); + +void torture_write_file(const char *filename, const char *data); + +#define torture_filter_tests(tests) _torture_filter_tests(tests, sizeof(tests) / sizeof(tests)[0]) +void _torture_filter_tests(struct CMUnitTest *tests, size_t ntests); + +const char *torture_server_address(int domain); +int torture_server_port(void); + +void torture_setup_socket_dir(void **state); +void torture_setup_sshd_server(void **state, bool pam); + +void torture_teardown_socket_dir(void **state); +void torture_teardown_sshd_server(void **state); + +int torture_update_sshd_config(void **state, const char *config); + +void torture_reset_config(ssh_session session); + +/* + * This function must be defined in every unit test file. + */ +int torture_run_tests(void); + +char *torture_make_temp_dir(const char *template); +char *torture_create_temp_file(const char *template); + +char *torture_get_current_working_dir(void); +int torture_change_dir(char *path); + +#endif /* _TORTURE_H */ diff --git a/tests/torture_cmocka.c b/tests/torture_cmocka.c new file mode 100644 index 0000000..9c259f7 --- /dev/null +++ b/tests/torture_cmocka.c @@ -0,0 +1,102 @@ +/* + * torture.c - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" + +void _assert_ssh_return_code(ssh_session session, + int rc, + const char * const file, + const int line) +{ + char ssh_error[1024] = {0}; + + if (session != NULL) { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code - %s", + ssh_get_error(session)); + } else { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code"); + } + + _assert_true(rc == SSH_OK, + ssh_error, + file, + line); +} + +void _assert_ssh_return_code_equal(ssh_session session, + int rc, + int expected_rc, + const char * const file, + const int line) +{ + char ssh_error[1024] = {0}; + + if (session != NULL) { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code - %s", + ssh_get_error(session)); + } else { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code"); + } + + _assert_true((rc == expected_rc), + ssh_error, + file, + line); +} + +void _assert_ssh_return_code_not_equal(ssh_session session, + int rc, + int unexpected_rc, + const char * const file, + const int line) +{ + char ssh_error[1024] = {0}; + + if (session != NULL) { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code - %s", + ssh_get_error(session)); + } else { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code"); + } + + _assert_true((rc != unexpected_rc), + ssh_error, + file, + line); +} diff --git a/tests/torture_cmocka.h b/tests/torture_cmocka.h new file mode 100644 index 0000000..c831743 --- /dev/null +++ b/tests/torture_cmocka.h @@ -0,0 +1,55 @@ +/* + * torture.c - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Andreas Schneider + * + * 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. + */ + +#ifndef _TORTURE_CMOCKA_H +#define _TORTURE_CMOCKA_H + +#include "libssh/session.h" + +void _assert_ssh_return_code(ssh_session session, + int rc, + const char * const file, + const int line); + +#define assert_ssh_return_code(session, rc) \ + _assert_ssh_return_code((session), (rc), __FILE__, __LINE__) + +void _assert_ssh_return_code_equal(ssh_session session, + int rc, + int expected_rc, + const char * const file, + const int line); + +#define assert_ssh_return_code_equal(session, rc, expected_rc) \ + _assert_ssh_return_code_equal((session), (rc), (expected_rc), __FILE__, __LINE__) + +void _assert_ssh_return_code_not_equal(ssh_session session, + int rc, + int expected_rc, + const char * const file, + const int line); + +#define assert_ssh_return_code_not_equal(session, rc, unexpected_rc) \ + _assert_ssh_return_code_not_equal((session), (rc), (unexpected_rc), __FILE__, __LINE__) + +#endif /* _TORTURE_CMOCKA_H */ diff --git a/tests/torture_key.c b/tests/torture_key.c new file mode 100644 index 0000000..5854026 --- /dev/null +++ b/tests/torture_key.c @@ -0,0 +1,809 @@ +/* + * torture_key.c - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 by Andreas Schneider + * + * 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 + +#include "torture.h" +#include "torture_key.h" + +/**************************************************************************** + * DSA KEYS + ****************************************************************************/ +static const char torture_rsa_private_testkey[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEowIBAAKCAQEArAOREUWlBXJAKZ5hABYyxnRayDZP1bJeLbPVK+npxemrhHyZ\n" + "gjdbY3ADot+JRyWjvll2w2GI+3blt0j+x/ZWwjMKu/QYcycYp5HL01goxOxuusZb\n" + "i+KiHRGB6z0EMdXM7U82U7lA/j//HyZppyDjUDniWabXQJge8ksGXGTiFeAJ/687\n" + "uV+JJcjGPxAGFQxzyjitf/FrL9S0WGKZbyqeGDzyeBZ1NLIuaiOORyLGSW4duHLD\n" + "N78EmsJnwqg2gJQmRSaD4BNZMjtbfiFcSL9Uw4XQFTsWugUDEY1AU4c5g11nhzHz\n" + "Bi9qMOt5DzrZQpD4j0gA2LOHpHhoOdg1ZuHrGQIDAQABAoIBAFJTaqy/jllq8vZ4\n" + "TKiD900wBvrns5HtSlHJTe80hqQoT+Sa1cWSxPR0eekL32Hjy9igbMzZ83uWzh7I\n" + "mtgNODy9vRdznfgO8CfTCaBfAzQsjFpr8QikMT6EUI/LpiRL1UaGsNOlSEvnSS0Z\n" + "b1uDzAdrjL+nsEHEDJud+K9jwSkCRifVMy7fLfaum+YKpdeEz7K2Mgm5pJ/Vg+9s\n" + "vI2V1q7HAOI4eUVTgJNHXy5ediRJlajQHf/lNUzHKqn7iH+JRl01gt62X8roG62b\n" + "TbFylbheqMm9awuSF2ucOcx+guuwhkPir8BEMb08j3hiK+TfwPdY0F6QH4OhiKK7\n" + "MTqTVgECgYEA0vmmu5GOBtwRmq6gVNCHhdLDQWaxAZqQRmRbzxVhFpbv0GjbQEF7\n" + "tttq3fjDrzDf6CE9RtZWw2BUSXVq+IXB/bXb1kgWU2xWywm+OFDk9OXQs8ui+MY7\n" + "FiP3yuq3YJob2g5CCsVQWl2CHvWGmTLhE1ODll39t7Y1uwdcDobJN+ECgYEA0LlR\n" + "hfMjydWmwqooU9TDjXNBmwufyYlNFTH351amYgFUDpNf35SMCP4hDosUw/zCTDpc\n" + "+1w04BJJfkH1SNvXSOilpdaYRTYuryDvGmWC66K2KX1nLErhlhs17CwzV997nYgD\n" + "H3OOU4HfqIKmdGbjvWlkmY+mLHyG10bbpOTbujkCgYAc68xHejSWDCT9p2KjPdLW\n" + "LYZGuOUa6y1L+QX85Vlh118Ymsczj8Z90qZbt3Zb1b9b+vKDe255agMj7syzNOLa\n" + "/MseHNOyq+9Z9gP1hGFekQKDIy88GzCOYG/fiT2KKJYY1kuHXnUdbiQgSlghODBS\n" + "jehD/K6DOJ80/FVKSH/dAQKBgQDJ+apTzpZhJ2f5k6L2jDq3VEK2ACedZEm9Kt9T\n" + "c1wKFnL6r83kkuB3i0L9ycRMavixvwBfFDjuY4POs5Dh8ip/mPFCa0hqISZHvbzi\n" + "dDyePJO9zmXaTJPDJ42kfpkofVAnfohXFQEy+cguTk848J+MmMIKfyE0h0QMabr9\n" + "86BUsQKBgEVgoi4RXwmtGovtMew01ORPV9MOX3v+VnsCgD4/56URKOAngiS70xEP\n" + "ONwNbTCWuuv43HGzJoVFiAMGnQP1BAJ7gkHkjSegOGKkiw12EPUWhFcMg+GkgPhc\n" + "pOqNt/VMBPjJ/ysHJqmLfQK9A35JV6Cmdphe+OIl28bcKhAOz8Dw\n" + "-----END RSA PRIVATE KEY-----\n"; + +static const char torture_rsa_private_testkey_passphrase[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,5375534F40903DD66B3851A0DA03F6FA\n" + "\n" + "m5YYTNOMd1xCKfifwCX4R1iLJoAc4cn1aFiL7f2kBbfE2jF1LTQBJV1h1CqYZfAB\n" + "WtM/7FkQPnKXqsMndP+v+1Xc+PYigE3AezJj/0g7xn/zIBwGjkLAp435AdL5i6Fg\n" + "OhOL8LyolRrcGn17jE4S4iGbzw8PVyfzNzdj0Emwql5F6M7pgLbInRNKM/TF4z2h\n" + "b6Pi9Bw43dwaJ7wiiy/vo/v4MyXsJBoeKbc4VCmxiYFvAYCvVFlDkyIw/QnR3MKQ\n" + "g/Zsk7Pw3aOioxk6LJpZ5x0tO23nXDG1aOZHWykI0BpJV+LIpD2oSYOHJyVO83XT\n" + "RQUMSTXc2K2+ejs0XQoLt/GxDDHe+8W8fWQK3C7Lyvl9oKjmb5sTWi3mdSv0C+zR\n" + "n5KSVbUKNXrjix7qPKkv5rWqb84CKVnCMb7tWaPLR19nQqKVYBIs6v0OTTvS6Le7\n" + "lz4lxBkcUy6vi0tWH9MvLuT+ugdHLJZ4UXBthCgV58pM1o+L+WMIl+SZXckiCAO3\n" + "7ercA57695IA6iHskmr3eazJsYFEVFdR/cm+IDy2FPkKmJMjXeIWuh3yASBk7LBR\n" + "EQq3CC7AioO+Vj8m/fEIiNZJSQ6p0NmgnPoO3rTYT/IobmE99/Ht6oNLmFX4Pr7e\n" + "F4CGWKzwxWpCnw2vVolCFByASmZycbJvrIonZBKY1toU28lRm4tCM6eCNISVLMeE\n" + "VtQ+1PH9/2KZspZl+SX/kjV3egggy0TFKRU8EcYPJFC3Vpy+shEai35KBVo44Z18\n" + "apza7exm3igNEqOqe07hLs3Bjhvk1oS+WhMbAG9ARTOKuyBOJh/ZV9tFMNZ6v+q5\n" + "TofgNcIhNYNascymU1io18xTW9c3RRcmRKqIWnj4EH8o7Aojv/l+zvdV7/GVlR4W\n" + "pR9cuJEiyiEjS46axoc6dSOtdnvag+BpFQb+lGY97F9nNGyBdtLD5ASVh5OVG4fu\n" + "Pf0O7Bdj1kIuBhV8axE/slf6UHANiodeqkR9B24+0Cy+miPiHazzUkbdSJ4r03g5\n" + "J1Y5S8qbl9++sqhQMLMUkeK4pDWh1aocA9bDA2RcBNuXGiZeRFUiqxcBS+iO418n\n" + "DFyWz4UfI/m1IRSjoo/PEpgu5GmosUzs3Dl4nAcf/REBEX6M/kKKxHTLjE8DxDsz\n" + "fn/vfsXV3s0tbN7YyJdP8aU+ApZntw1OF2TS2qS8CPWHTcCGGTab5WEGC3xFXKp0\n" + "uyonCxV7vNLOiIiHdQX+1bLu7ps7GBH92xGkPg7FrNNcMc07soP7jjjB578n9Gpl\n" + "cIDBdgovTRFHiWu3yRspVt0zPfMJB/hqn+IAp98wfvjl8OZM1ZZkejnwXnQil5ZU\n" + "wjEBEtx+nX56vdxipzKoHh5yDXmPbNajBYkg3rXJrLFh3Tsf0CzHcLdHNz/qJ9LO\n" + "wH16grjR1Q0CzCW3FAv0Q0euqkXac+TfuIg3HiTPrBPnJQW1uivrx1F5tpO/uboG\n" + "h28LwqJLYh+1T0V//uiy3SMATpYKvzg2byGct9VUib8QVop8LvVF/n42RaxtTCfw\n" + "JSvUyxoaZUjQkT7iF94HsF+FVVJdI55UjgnMiZ0d5vKffWyTHYcYHkFYaSloAMWN\n" + "-----END RSA PRIVATE KEY-----\n"; + +static const char torture_rsa_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI0RSm1ZXOBD8CAggA\n" + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBBS+59quuIVuxN/H9Wltk8TBIIE\n" + "0J7OhRw35ANRyTU2qhlhS8NATcguoD1J4IMXpXpv38iCBWd2bjxvuWnEu4aBX7iU\n" + "desfz9n6AoTVqURaOMLsv6EFV0tycf+mZsmdUmrD2270Wyj6TtQD8LO/7ibifCeL\n" + "XCCKjxciueSggHp5lnfogZwn8wjSEDP7OqNVRTwm8QKNrE7J5m5giFrjXoyqKM7r\n" + "DBa35UIZAXXY8z9CkI+GsyRtaZik3VD+xHShwUriOYg4x4VGZQLj24tjoUnqU4ml\n" + "iRMhGyYpxN7CnfaIwHJr3T0dmbT/BIXOQ2B6sWakioZeUuA6OTBHbFTUN9TUHaF0\n" + "rDMVmjL6BQcEiWwjvtw/3NLdkcKFjMiLTWA2GL71KPGCecpMmAMjo+ijnxeVhqpQ\n" + "dnhowG92DhCSf/XZI0vaaYflrV54U9PgcSPDFWmTOVe5151Mi8eR9qrCanfyHmX1\n" + "MLXs8Mw6xWedNj8AWLV3JGiWEeAEATuTAQfTqmBZbzaFKfSKp5PZjWxa5bZIomzS\n" + "Q0AsONTeYmKK+Pv95RYlgR2kKqhwy3OmcOuepwnzSeAGh1BdBzd2raoipkq1fpY5\n" + "8e75dJnTGvWfqfh0VXz/Wud+hMz/98Mh6Bnp9l+Ddxpp4RioWB2aH0HM8ZGTlbhf\n" + "r5qFmDY7k+RfDDp7K7UYMA+2hHCxY1aFSHVYGRQKdYdKIugLtKx6YKLeGVCR7Gbm\n" + "l/88qiGshF/qhdFbPb4K0Tz2Ug5uklveOQSkKX6RSZ30IW+N3E4nH/wvyOwbCPk7\n" + "u+iHB2zzk2Hws4O52a0Gqj+RbeGzzhl1D9jH35GMHUsfhDSA3/mmrVC7hiN/Aplt\n" + "2OmKFAkobZh/1UJAHBY9feIhLmQUy9dwy0E8G/0LEyyZYEizDC76jsvbh2cPg3jM\n" + "JsI31qUaGggwh3wB034BvsYIf/ZqLCt8hAXF9U5U7T5y3r6FNNBla8zlj25ILog6\n" + "t/bhOwFKYXamAVYMhhvUiA3YIYuBxT7MrgL7gDtKh3N/DleS/pLjmOFfMI3dfCd0\n" + "KSQX46uw7aFbV0Has9uUuGle9Foq52QFvYnDHWJuIyOvJ5st1Hd3Mjjsl9t3JFVM\n" + "I1aDZ17Z4LoThdezNQKGaAe5z7gGFMKKsm55CMT/7FxvConALeQKGAV6jA5xZzl4\n" + "+QB14YlxlZTxYnXd/69KGV56wP8sb6uMVDC/f5Vd3oHsamJKpPgts8WCn11f9wFn\n" + "Mx8YY/vBVVLQMw1aB+82Vk+Ix8YDYIPj5bJk2BkyCCUnMYkKswUOVzsdUq0xssEp\n" + "PASw0YvQ9mY2aQ9exme99JuAj5t4qIXoYTSrX5iv6NXtzDHgTR1pl9gQQVQ0zAUO\n" + "ZHKZXYAv5rLZKRcyeCLw0LkuthY2QtN3PsBlaRtfwZTaqUbBGbvEkcx5fxdEsasS\n" + "yQkZKBBvIi42LUN9ZzywYNGbOanCZ04p/+QscmmnVGuDMZJyaDRaapW6f0nJQ+lQ\n" + "CaVPRzLKGnHV5hWQDjTaPIh2s9rJSZJ3HyE8qshETHW/vQoYIcVB9TX5TnOY02Ak\n" + "IINKfSZGgz/NBeJItjk30UuTcISk65ekoXZIHHgdxD9iHy9D0w6FXcPNLLsWQn7n\n" + "jS4Bvt0VZ9zVAiyyVO4yAaMgP+saitYpjMgI8g67geD3\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_rsa_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDX\n" + "ClCBeHgYyOEqmWpAanz9AAAAEAAAAAEAAAEXAAAAB3NzaC1yc2EAAAADAQABAAAB\n" + "AQDXvXuawzaArEwkLIXTz/EWywLOCtqQL3P9yKkrhz6AplXP2PhOh5pyxa1VfGKe\n" + "453jNeYBJ0ROto3BshXgZXbo86oLXTkbe0gO5xi3r5WjXxjOFvRRTLot5fPLNDOv\n" + "9+TnsPmkNn0iIeyPnfrcPIyjWt5zSWUfkNC8oNHxsiSshjpbJvTXSDipukpUy41d\n" + "7jg4uWGuonMTF7yu7HfuHqq7lhb0WlwSpfbqAbfYARBddcdcARyhix4RMWZZqVY2\n" + "0H3Vsjq8bjKC+NJXFce1PRg+qcOWQdlXEei4dkzAvHvfQRx1TjzkrBZ6B6thmZty\n" + "eb9IsiB0tg2g0JN2VTAGkxqpAAADwG8gm8jZpx+GIKdhV+igcvYvIhzA+fz6UdXf\n" + "d/8wnYzMXtg+Ys7XsKUsxtMD8HGPiuwYsTrd/YGiol7SpkJV0STqtW+UZrcKamJ5\n" + "reFaDoIU8hhWTXCe/ogplTxH/zNNK7Xx5OAGnNWE3zsR1vbZaCv+Vwwa27eUCbpv\n" + "V1+92nBwkah3FCKCbwYDvTVRn1TZHQwnuNxDCRrlwaMjf8eX2ssqLLX7jqrb3j1u\n" + "c28GR3fNJ8ENaWshZ77tqexUQCnCx14/qtT434CMvENXnCP5BP/cRmbOlCFQ6Id7\n" + "nLMW0uDIy/q3xBsAcdMyV0LJW7sJNXIjTnS4lyXd0XescXrqTAKxTkqd1E0VIBpc\n" + "37+7vqv9A9Xxq74jy//L9L4Yrbijc9Vt+oNWFgOuakZGBLIQvm36Oqb0z0oWJcUt\n" + "VdZcvkCNMeixBqCnrQ8egO3x0pnZwo6cwH586Me8FgFacOnzWjzuQT6vYJ4EK5ch\n" + "YNRQpjtz5+T3rZK7eIF1ZUobM4S6di7A6lW9tycQVhjo5XlhalMfCfajhazgcIrY\n" + "Qdaq8+AguP8H+3bvXPZmitL8/mv5uVjqxy1lYh2xLzViTmFnvfdbZ92BWI9C6JBI\n" + "+mRWzXeEY71MjfeEaPStwBm5OYBMFwYrXPL7E3JjAXRxbB+LKUksj/lRk3K7aQp4\n" + "IDKCzAACgkOixfP39BgKQkrLjAoi6mEDqu5Ajc3GoljXsJEkcbu0j+0tVth+41nV\n" + "8yCkP5SVUQTCSKzoduE+0pk6oYO6vrwKLM62cQRPXLl/XNoUqETIe8dklIKojYo6\n" + "3ho1RaHgYr9/NAS0029CFt/rGmONWF9ihKON6wMavJRcofZ25FeylKiP2rrqdDIb\n" + "EiWULZi3MUJfKBwSeZMwaYYmSpaOZF1U/MgvEfeRkE1UmDp3FmBLSNHBYhAxNazH\n" + "R393BTr1zk7h+8s7QK986ZtcKkyUNXEK1NkLLuKlqMwFnjiOdeAIGwz9NEn+Tj60\n" + "jE5IcCE06B6ze/MOZcsPp1SoZv4kKmgWY5Gdqv/9O9SyFQ0Yh4MvBSD8l4x0epId\n" + "8Xm54ISVWP1SZ1x3Oe8yvtwOGqDkZeOVjnP7EQ7R0+1PZzW5P/x47skACqadGChN\n" + "ahbngIl+EhPOqhx+wIfDbtzTmGABgNhcI/d02b8py5MXFnA+uzeSucDREYRdm2TO\n" + "TQQ2CtxB6lcatIYG4AhyouQbujLd/AwpZJ05S1i/Qt6NenTgK3YyTWdXLQnjZSMx\n" + "FBRkf+Jj9eVXieT4PJKtWuvxNNrJVA==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_rsa_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdz\n" + "c2gtcnNhAAAAAwEAAQAAAQEA1717msM2gKxMJCyF08/xFssCzgrakC9z/cipK4c+\n" + "gKZVz9j4ToeacsWtVXxinuOd4zXmASdETraNwbIV4GV26POqC105G3tIDucYt6+V\n" + "o18Yzhb0UUy6LeXzyzQzr/fk57D5pDZ9IiHsj5363DyMo1rec0llH5DQvKDR8bIk\n" + "rIY6Wyb010g4qbpKVMuNXe44OLlhrqJzExe8rux37h6qu5YW9FpcEqX26gG32AEQ\n" + "XXXHXAEcoYseETFmWalWNtB91bI6vG4ygvjSVxXHtT0YPqnDlkHZVxHouHZMwLx7\n" + "30EcdU485KwWegerYZmbcnm/SLIgdLYNoNCTdlUwBpMaqQAAA7iQHqVWkB6lVgAA\n" + "AAdzc2gtcnNhAAABAQDXvXuawzaArEwkLIXTz/EWywLOCtqQL3P9yKkrhz6AplXP\n" + "2PhOh5pyxa1VfGKe453jNeYBJ0ROto3BshXgZXbo86oLXTkbe0gO5xi3r5WjXxjO\n" + "FvRRTLot5fPLNDOv9+TnsPmkNn0iIeyPnfrcPIyjWt5zSWUfkNC8oNHxsiSshjpb\n" + "JvTXSDipukpUy41d7jg4uWGuonMTF7yu7HfuHqq7lhb0WlwSpfbqAbfYARBddcdc\n" + "ARyhix4RMWZZqVY20H3Vsjq8bjKC+NJXFce1PRg+qcOWQdlXEei4dkzAvHvfQRx1\n" + "TjzkrBZ6B6thmZtyeb9IsiB0tg2g0JN2VTAGkxqpAAAAAwEAAQAAAQAdjR3uQAkq\n" + "LO+tENAwCE680YgL0x7HG0jnHWJWzQq5so8UjmLM1vRH/l3U1Nnpa8JHyi08QTWx\n" + "Fn5qZstqVluoYyAKuHVHF2bya6NOHeYAX9lU+X3z2O+zs8jmL7tYwjr/pZU8ch5H\n" + "25+8uGYRXtXg1mScJBSO81Y0UE8RrVYqr2Os583yB657kYiVYYYSZlRGd9wmfXnJ\n" + "w0t8LaYcTn+i/lOvrJGa0Q0iV6+4rYmjwYd/D/vyNzF31hUEFrn3vDSgTnJdShgH\n" + "VqW0OwNuEDe/4p8KkKR1EVVj6xv4zicwouY7aQI+zT3MwAzvNdvYwytsIj6bhT9x\n" + "oyeAAIW0vaKVAAAAgQD6pPfu6tb7DiTlaH3/IPdGh3PTIf0zXHZ/ygxORXBZdoLY\n" + "Fq2h/YnBd2Hs8vARAjGJYs78gTPP0FVXPV8ut38xct4DQ2hbPMrjWv5gdhDazq8Q\n" + "qaFEa0+DeYONej8ItKwpsV2Rskkv5Pfm7M6EffVty1uzOpIcT8RYDAYUlc5D/wAA\n" + "AIEA+44ykLho3BDWnUzshVEm6iNoqlZqcDVcNSpCuYDnCy5UrTDk0zj+OUG9M0Zx\n" + "4c7kAmu/poXSimgAgMh9GNCzy3+a70WvH+fBqvG5tXLaSOQCswSdQjltANAnlt5L\n" + "YDHzGGJBsS4pYxoz22MKhFbpYUCQJvotXnZJpTQU6hdFRX8AAACBANuNSlFq/vG8\n" + "Vf9c2YsPiITmOrYxpUDMiMLvUGQOdyIIc45EAggOFHNF3AdPZEhinpD92EK+LiJc\n" + "WYJ26muVcicZoddgmpcHRt2gByC+ckWOM4sLpih6EyQLFZfqTx2X+KOI0ZTt7zEi\n" + "zfm1MJUNDFOr3DM0VBIf34Bn1hU/isPXAAAAAAEC\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + + +static const char torture_rsa_public_testkey[] = + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsA5ERRaUFckApnmEAFjLGdFrIN" + "k/Vsl4ts9Ur6enF6auEfJmCN1tjcAOi34lHJaO+WXbDYYj7duW3SP7H9lbCMwq79B" + "hzJxinkcvTWCjE7G66xluL4qIdEYHrPQQx1cztTzZTuUD+P/8fJmmnIONQOeJZptd" + "AmB7ySwZcZOIV4An/rzu5X4klyMY/EAYVDHPKOK1/8Wsv1LRYYplvKp4YPPJ4FnU0" + "si5qI45HIsZJbh24csM3vwSawmfCqDaAlCZFJoPgE1kyO1t+IVxIv1TDhdAVOxa6B" + "QMRjUBThzmDXWeHMfMGL2ow63kPOtlCkPiPSADYs4ekeGg52DVm4esZ " + "aris@aris-air\n"; + +static const char torture_rsa_testkey_cert[] = + "ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNz" + "aC5jb20AAAAgL77S/SgY969FbEtNBsbLvvtGFgnEHaPb+V7ajwuf+R0AAAADAQABA" + "AABAQCsA5ERRaUFckApnmEAFjLGdFrINk/Vsl4ts9Ur6enF6auEfJmCN1tjcAOi34" + "lHJaO+WXbDYYj7duW3SP7H9lbCMwq79BhzJxinkcvTWCjE7G66xluL4qIdEYHrPQQ" + "x1cztTzZTuUD+P/8fJmmnIONQOeJZptdAmB7ySwZcZOIV4An/rzu5X4klyMY/EAYV" + "DHPKOK1/8Wsv1LRYYplvKp4YPPJ4FnU0si5qI45HIsZJbh24csM3vwSawmfCqDaAl" + "CZFJoPgE1kyO1t+IVxIv1TDhdAVOxa6BQMRjUBThzmDXWeHMfMGL2ow63kPOtlCkP" + "iPSADYs4ekeGg52DVm4esZAAAAAAAAAAAAAAABAAAADmxpYnNzaF90b3J0dXJlAAA" + "AAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRp" + "bmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtc" + "G9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdX" + "Nlci1yYwAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAoowcv2Gn8tO" + "eDyw/lgdMpoBsLtHTTdVVOOo5HwMFvj/lFkbZlb6J2n9GIE64HNPE45vSnIdJZwz4" + "UYfTvtnNKNHp1MgMrjK1Z6EjyZsGqDZ+BhmvcKA6IckkhBJnDV7U9dMrovAWha61Z" + "9GpDqB1naRfbwqJQwSRHF1p71Cnf0fZKxOhAVx0ophmYGz3x3qq4PeOZv3Yl0AHTV" + "dRmqmeELDUxeuXN2bgSyb881zEgdaKHH5oWySykP4uwjn6T7ETuL2MsDdG3HZHDhn" + "LzLmfzOZ/cNadMCrgauMluQKc5dYF2TSeDaUxwun/NPMQBVZdETHLAMBgkGmhRUku" + "flVDIQAAAQ8AAAAHc3NoLXJzYQAAAQADSp4b/Zta8zs6v47iwmxV2Gbucvt1kDrvT" + "vKAKSbGN0+zoMyXiNfMHM/OvZObDS/WWGs4GMRqbJavwO3ja/dQY17oJss23lZ+Rc" + "Lw4Rqsi3/ZEPCnX6ficiRS/yRN/LAkoXvx9vBx9QHfxlzF6JXq07wTt21zxW0tntd" + "8dL+JI9ZZ9YylnxF3gHqfRFe2ahJpiywmxm0yOZgDmimOhep59i6BH5zHiPALvpge" + "Mbk075oA5K9XKsHTflCcsQRQH+pXqaNQGL37z2CFz9oezxQYvIqqKF0w/eeRIARoA" + "neB6OdgTpKFsmgPZVtqrvhjw+b5T8a4W4iWSl+6wg6gowAm " + "rsa_privkey.pub\n"; + +/**************************************************************************** + * DSA KEYS + ****************************************************************************/ + +static const char torture_dsa_private_testkey[] = + "-----BEGIN DSA PRIVATE KEY-----\n" + "MIIBuwIBAAKBgQCUyvVPEkn3UnZDjzCzSzSHpTltzr0Ec+1mz/JACjHMBJ9C/W/P\n" + "wvH3yjkfoFhhREvoY7IPnwAu5bcxw8TkISq7YROQ409PqwwPvy0N3GUp/+kKS268\n" + "BIJ+VKN513XRf7eL1e4aHUJ+al9x1JxTmc6T0GBq1lyu+CTUUyh25aNDFwIVAK84\n" + "j20GmU+zewjQwsIXuVb6C/PHAoGAXhuIVsJxUQJ5nWQRLf7o3XEGQ+EcVmHOzMB1\n" + "xCsHjYnpEhhco+r/HDZSD31kzDeAZUycz31WqGL8yXr+OZRLqEsGC7dwEAzPiXDu\n" + "l0zHcl0yiKPrRrLgNJHeKcT6JflBngK7jQRIVUg3F3104fbVa2rwaniLl4GSBZPX\n" + "MpUdng8CgYB4roDQBfgf8AoSAJAb7y8OVvxt5cT7iqaRMQX2XgtW09Nu9RbUIVS7\n" + "n2mw3iqZG0xnG3iv1oL9gwNXMLlf+gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM7b2u\n" + "ADmhirI6dRZUVO+/iMGUvDxa66OI4hDV055pbwQhtxupUatThyDzIgIVAI1Hd8/i\n" + "Pzsg7bTzoNvjQL+Noyiy\n" + "-----END DSA PRIVATE KEY-----\n"; + +static const char torture_dsa_private_testkey_passphrase[] = + "-----BEGIN DSA PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,266023B64B1B814BCD0D0E477257F06D\n" + "\n" + "QJQErZrvYsfeMNMnU+6yVHH5Zze/zUFdPip7Bon4T1wCGlVasn4x/GQcMm1+mgmb\n" + "PCK/qJ5qw9nCepLYJq2xh8gohbwF/XKxeaNGcRA2+ancTooDUjeRTlk1WRtS1+bq\n" + "LBkwhxLXW26lIuQUHzfi93rRqQI2LC4McngY7L7WVJer7sH7hk5//4Gf6zHtPEl+\n" + "Tr2ub1zNrVbh6e1Bitw7DaGZNX6XEWpyTTsAd42sQWh6o23MC6GyfS1YFsPGHzGe\n" + "WYQbWn2AZ1mK32z2mLZfVg41qu9RKG20iCyaczZ2YmuYyOkoLHijOAHC8vZbHwYC\n" + "+lN9Yc8/BoMuMMwDTMDaJD0TsBX02hi9YI7Gu88PMCJO+SRe5400MonUMXTwCa91\n" + "Tt3RhYpBzx2XGOq5199+oLdTJAaXHJcuB6viKNdSLBuhx6RAEJXZnVexchaHs4Q6\n" + "HweIv6Et8MjVoqwkaQDmcIGA73qZ0lbUJFZAu2YDJ6TpHc1lHZes763HoMYfuvkX\n" + "HTSuHZ7edjoWqwnl/vkc3+nG//IEj8LqAacx0i4krDcQpGuQ6BnPfwPFco2NQQpw\n" + "wHBOL6HrOnD+gGs6DUFwzA==\n" + "-----END DSA PRIVATE KEY-----\n"; + +static const char torture_dsa_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIIBrTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI8001emUNAOECAggA\n" + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBDgXXvQsVxY6zaAQVwzUwvDBIIB\n" + "UOBQqqJs4rYK6R0rXFitkdUodOK3CdFAKodyCkSC5cgoW2+ht2ndRCepxuKB2X14\n" + "Lvt1CIxPvu1k7bGnd25kePmNF85cJxG9wf0/+6vpptO3fTUdsUKyLcRKDqvxxOMB\n" + "OSqQK1MLgvUxB5uBSGCsKqFkVUPYs46uihfozjqHH2IghHSQr+VczhFDoWtzgcgp\n" + "nRNZiyXN5Thob5WOrL849TSlcaMyI3ssErEVP1G2t3ax5bLQ4AqDddumoRBed/XY\n" + "lad5QGAS2XlwMFj8tR/Spi1fEWfamIsvh23ba5ksb35TT3SUJd2gf2NC7QEz3dUK\n" + "YDSSeRSF24c4nXBsJ94TkVuUujo4X3QSaWQ2anYYBBwfQtrddVNVu95QS2sQGLov\n" + "UWIhq1xXbnL/SGC6E5T1VGnAx3qwfDEZX5tTNzkwqeTZfkrb6vRk+O+Lxt67iP+n\n" + "nw==\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_dsa_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBC\n" + "UZK61oXs3uKMs4l7G0cpAAAAEAAAAAEAAAGxAAAAB3NzaC1kc3MAAACBAJTK9U8S\n" + "SfdSdkOPMLNLNIelOW3OvQRz7WbP8kAKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+f\n" + "AC7ltzHDxOQhKrthE5DjT0+rDA+/LQ3cZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hod\n" + "Qn5qX3HUnFOZzpPQYGrWXK74JNRTKHblo0MXAAAAFQCvOI9tBplPs3sI0MLCF7lW\n" + "+gvzxwAAAIBeG4hWwnFRAnmdZBEt/ujdcQZD4RxWYc7MwHXEKweNiekSGFyj6v8c\n" + "NlIPfWTMN4BlTJzPfVaoYvzJev45lEuoSwYLt3AQDM+JcO6XTMdyXTKIo+tGsuA0\n" + "kd4pxPol+UGeAruNBEhVSDcXfXTh9tVravBqeIuXgZIFk9cylR2eDwAAAIB4roDQ\n" + "Bfgf8AoSAJAb7y8OVvxt5cT7iqaRMQX2XgtW09Nu9RbUIVS7n2mw3iqZG0xnG3iv\n" + "1oL9gwNXMLlf+gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM7b2uADmhirI6dRZUVO+/\n" + "iMGUvDxa66OI4hDV055pbwQhtxupUatThyDzIgAAAeAtGFEW6JZTeSumizZJI4T2\n" + "Kha05Ze3juTeW+BMjqTcf77yAL2jvsljogCtu4+5CWWO4g+cr80vyVytji6IYTNM\n" + "MPn1qe6dHXnfmgtiegHXxrjr5v5/i1cvD32Bxffy+yjR9kbV9GJYF+K5pfYVpQBa\n" + "XVmq6AJUPd/yxKw6jRGZJi8GTcrKbCZAL+VYSPwc0veCrmGPjeeMCgYcEXPvhSui\n" + "P0JnG1Ap12FeK+61rIbZBAr7qbTGJi5Z5HlDlgon2tmMZOkIuL1Oytgut4MpmYjP\n" + "ph+qrzgwfSwOsjVIuHlb1L0phWRlgbT8lmysEE7McGKWiCOabxgl3NF9lClhDBb9\n" + "nzupkK1cg/4p17USYMOdeNhTmJ0DkQT+8UenfBOmzV7kamLlEYXJdDZBN//dZ8UR\n" + "KEzAzpaAVIyJQ+wvCUIh/VO8sJP+3q4XQUkv0QcIRlc0+r9qbW2Tqv3vajFcFtK6\n" + "nrTmIJVL0pG+z/93Ncpy5susD+JvhJ4yfl7Jet3jy4fWwm3qkLl0WsobJ7Om+GyH\n" + "DzHH9RgDk3XuUHS/fz+kTwmtyIH/Rq1jIt+s+T8iA9CzKSX6sBu2yfMo1w2/LbCx\n" + "Xy1rHS42TePw28m1cQuUfjqdOC3IBgQ1m3x2f1on7hk=\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_dsa_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsQAAAAdz\n" + "c2gtZHNzAAAAgQCUyvVPEkn3UnZDjzCzSzSHpTltzr0Ec+1mz/JACjHMBJ9C/W/P\n" + "wvH3yjkfoFhhREvoY7IPnwAu5bcxw8TkISq7YROQ409PqwwPvy0N3GUp/+kKS268\n" + "BIJ+VKN513XRf7eL1e4aHUJ+al9x1JxTmc6T0GBq1lyu+CTUUyh25aNDFwAAABUA\n" + "rziPbQaZT7N7CNDCwhe5VvoL88cAAACAXhuIVsJxUQJ5nWQRLf7o3XEGQ+EcVmHO\n" + "zMB1xCsHjYnpEhhco+r/HDZSD31kzDeAZUycz31WqGL8yXr+OZRLqEsGC7dwEAzP\n" + "iXDul0zHcl0yiKPrRrLgNJHeKcT6JflBngK7jQRIVUg3F3104fbVa2rwaniLl4GS\n" + "BZPXMpUdng8AAACAeK6A0AX4H/AKEgCQG+8vDlb8beXE+4qmkTEF9l4LVtPTbvUW\n" + "1CFUu59psN4qmRtMZxt4r9aC/YMDVzC5X/oC5rKlN+/PI2hGfSIWfFXYBwKB5ulF\n" + "jO29rgA5oYqyOnUWVFTvv4jBlLw8WuujiOIQ1dOeaW8EIbcbqVGrU4cg8yIAAAHY\n" + "tbI937WyPd8AAAAHc3NoLWRzcwAAAIEAlMr1TxJJ91J2Q48ws0s0h6U5bc69BHPt\n" + "Zs/yQAoxzASfQv1vz8Lx98o5H6BYYURL6GOyD58ALuW3McPE5CEqu2ETkONPT6sM\n" + "D78tDdxlKf/pCktuvASCflSjedd10X+3i9XuGh1CfmpfcdScU5nOk9BgatZcrvgk\n" + "1FModuWjQxcAAAAVAK84j20GmU+zewjQwsIXuVb6C/PHAAAAgF4biFbCcVECeZ1k\n" + "ES3+6N1xBkPhHFZhzszAdcQrB42J6RIYXKPq/xw2Ug99ZMw3gGVMnM99Vqhi/Ml6\n" + "/jmUS6hLBgu3cBAMz4lw7pdMx3JdMoij60ay4DSR3inE+iX5QZ4Cu40ESFVINxd9\n" + "dOH21Wtq8Gp4i5eBkgWT1zKVHZ4PAAAAgHiugNAF+B/wChIAkBvvLw5W/G3lxPuK\n" + "ppExBfZeC1bT0271FtQhVLufabDeKpkbTGcbeK/Wgv2DA1cwuV/6AuaypTfvzyNo\n" + "Rn0iFnxV2AcCgebpRYztva4AOaGKsjp1FlRU77+IwZS8PFrro4jiENXTnmlvBCG3\n" + "G6lRq1OHIPMiAAAAFQCNR3fP4j87IO2086Db40C/jaMosgAAAAABAg==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_dsa_public_testkey[] = + "ssh-dss AAAAB3NzaC1kc3MAAACBAJTK9U8SSfdSdkOPMLNLNIelOW3OvQRz7WbP8k" + "AKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+fAC7ltzHDxOQhKrthE5DjT0+rDA+/LQ3c" + "ZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hodQn5qX3HUnFOZzpPQYGrWXK74JNRTKHblo0" + "MXAAAAFQCvOI9tBplPs3sI0MLCF7lW+gvzxwAAAIBeG4hWwnFRAnmdZBEt/ujdcQZD" + "4RxWYc7MwHXEKweNiekSGFyj6v8cNlIPfWTMN4BlTJzPfVaoYvzJev45lEuoSwYLt3" + "AQDM+JcO6XTMdyXTKIo+tGsuA0kd4pxPol+UGeAruNBEhVSDcXfXTh9tVravBqeIuX" + "gZIFk9cylR2eDwAAAIB4roDQBfgf8AoSAJAb7y8OVvxt5cT7iqaRMQX2XgtW09Nu9R" + "bUIVS7n2mw3iqZG0xnG3iv1oL9gwNXMLlf+gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM" + "7b2uADmhirI6dRZUVO+/iMGUvDxa66OI4hDV055pbwQhtxupUatThyDzIg==\n"; + +static const char torture_dsa_testkey_cert[] = + "ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNza" + "C5jb20AAAAgKAd9MpIBrzctQyJvCYYJ2WUD5fyWlXMSv1G/3VihbCAAAACBAJTK9U8" + "SSfdSdkOPMLNLNIelOW3OvQRz7WbP8kAKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+fA" + "C7ltzHDxOQhKrthE5DjT0+rDA+/LQ3cZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hodQn5" + "qX3HUnFOZzpPQYGrWXK74JNRTKHblo0MXAAAAFQCvOI9tBplPs3sI0MLCF7lW+gvzx" + "wAAAIBeG4hWwnFRAnmdZBEt/ujdcQZD4RxWYc7MwHXEKweNiekSGFyj6v8cNlIPfWT" + "MN4BlTJzPfVaoYvzJev45lEuoSwYLt3AQDM+JcO6XTMdyXTKIo+tGsuA0kd4pxPol+" + "UGeAruNBEhVSDcXfXTh9tVravBqeIuXgZIFk9cylR2eDwAAAIB4roDQBfgf8AoSAJA" + "b7y8OVvxt5cT7iqaRMQX2XgtW09Nu9RbUIVS7n2mw3iqZG0xnG3iv1oL9gwNXMLlf+" + "gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM7b2uADmhirI6dRZUVO+/iMGUvDxa66OI4hD" + "V055pbwQhtxupUatThyDzIgAAAAAAAAAAAAAAAQAAAA5saWJzc2hfdG9ydHVyZQAAA" + "AAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5" + "nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvc" + "nQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXI" + "tcmMAAAAAAAAAAAAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBAKKMHL9hp/LTng8sP" + "5YHTKaAbC7R003VVTjqOR8DBb4/5RZG2ZW+idp/RiBOuBzTxOOb0pyHSWcM+FGH077" + "ZzSjR6dTIDK4ytWehI8mbBqg2fgYZr3CgOiHJJIQSZw1e1PXTK6LwFoWutWfRqQ6gd" + "Z2kX28KiUMEkRxdae9Qp39H2SsToQFcdKKYZmBs98d6quD3jmb92JdAB01XUZqpnhC" + "w1MXrlzdm4Esm/PNcxIHWihx+aFskspD+LsI5+k+xE7i9jLA3Rtx2Rw4Zy8y5n8zmf" + "3DWnTAq4GrjJbkCnOXWBdk0ng2lMcLp/zTzEAVWXRExywDAYJBpoUVJLn5VQyEAAAE" + "PAAAAB3NzaC1yc2EAAAEAAt4V9aGqeahOfUvhG7M8/Mn26aLB/HXbICYFJF7dY6urm" + "SIoS2KBqISCFGXTituiwGlZeAJ+pVgCMYo07Nxtd6oqIjsgKfJqDNx7e4pGw/YJnkm" + "BqMO/k/ygu2mLmQF0lnpmG2KyjKEljMibHaKlFkcVNbwfOb4p8N3OHm66g5mbCUTRZ" + "DHqMSJb3YtnObLexD13RydwxkG5AfCnOWxy5O4agXGEYwr/48AQBHYg9obGtpD1qyF" + "4mMXgzaLViFtcwah6wHGlW0UPQMvrq/RqigAkyUszSccfibkIXJ+wGAgsRYhVAMwME" + "JqPZ6GHOEIjLBKUegsclHb7Pk0YO8Auaw== " + "aris@aris-air\n"; + +/**************************************************************************** + * ECDSA KEYS + ****************************************************************************/ + +static const char torture_ecdsa256_private_testkey[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "MHcCAQEEIBCDeeYYAtX3EnsP0ratwVpNTaA/4K1N6VvHMiUZlVdhoAoGCCqGSM49\n" + "AwEHoUQDQgAEx+9ud88Q5GWtLd+yMtYaapC85g+2ZLp7VtFHA0EbNHqBUQxoh+Ik\n" + "89Mlr7AUxcFPd+kCo+NE6yq/mNQcL7E6iQ==\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_private_testkey_passphrase[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,5C825E6FE821D0DE99D8403F4B4020CB\n" + "\n" + "TaUq8Qenb52dKAYcQGIYfdT7Z2DroySk38w51kw/gd8o79ZHaAQv60GtaNoy0203\n" + "2X1o29E6c0WsY9DKhSHKm/zzvZmL+ChZYqqh3sd1gp55aJsHNN4axiIu2YCbCavh\n" + "8VZn2VJDaitLy8ARqA/lMGQfqHSa3EOqti9FzWG/P6s=\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIHsMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAhvndbkbElTnAICCAAw\n" + "DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEOu4ierPcQpcA9RJNHUbTCoEgZBe\n" + "iusOkUYp4JZJEIpi98VlqnROzDXHpTTpEGiUDC/k+cuKvoPop5+Jx0qXp+A1NJxu\n" + "kx3j+U0ISGY7J6b2Pqt1msC/FzqpeFM7ybuHDRz+c5ZBONTp8wrs52d5NdjrYguz\n" + "UO6n9+yydSsO0FqbwPaqNZ6goBN0TfhYnToG4ZPJxlHa7gf7Su4KSMYKZdOtfx4=\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNl\n" + "Y2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTH7253zxDkZa0t37Iy\n" + "1hpqkLzmD7ZkuntW0UcDQRs0eoFRDGiH4iTz0yWvsBTFwU936QKj40TrKr+Y1Bwv\n" + "sTqJAAAAmOuDchHrg3IRAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy\n" + "NTYAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPtmS6e1bRRwNBGzR6gVEMaIfiJPPT\n" + "Ja+wFMXBT3fpAqPjROsqv5jUHC+xOokAAAAgEIN55hgC1fcSew/Stq3BWk1NoD/g\n" + "rU3pW8cyJRmVV2EAAAAA\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_private_openssh_testkey_pasphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABA+\n" + "O0w3yPZF2q0FjVBhQjn2AAAAEAAAAAEAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAy\n" + "NTYAAAAIbmlzdHAyNTYAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPtmS6e1bRRwNB\n" + "GzR6gVEMaIfiJPPTJa+wFMXBT3fpAqPjROsqv5jUHC+xOokAAACghvb4EX8M06UB\n" + "zigxOn9bg5cZkZ2yWY8jzxtOWH4YJXsuhON/jePDJuI2ro5u4iKFD1u2JLfcshdh\n" + "vKZyjixU9KdewykQQt/wFkrCfNUyCH8jFiQsAqhBfopRFyDJV9pmcUBL/3fJqwut\n" + "ZeBSfA7tXORp3xrwFI1tXiiUCM+/nhxiCsFaCJXeiM3tN+kFtwQ8kamINqwaC8Vj\n" + "lFLKHDfwJQ==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_public_testkey[] = + "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNT" + "YAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPtmS6e1bRRwNBGzR6gVEMaIfiJPPTJa+w" + "FMXBT3fpAqPjROsqv5jUHC+xOok= aris@kalix86\n"; + +static const char torture_ecdsa256_testkey_cert[] = + "ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzd" + "HAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgHvXWcdSrQeZL2/Z68V8ntbL7rDo" + "Qwrsc+ps6HbMGZrkAAAAIbmlzdHAyNTYAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPt" + "mS6e1bRRwNBGzR6gVEMaIfiJPPTJa+wFMXBT3fpAqPjROsqv5jUHC+xOokAAAAAAAA" + "AAAAAAAEAAAAHbXlpZGVudAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVc" + "GVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGl" + "uZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0e" + "QAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1" + "uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEEx+9ud88Q5GWtLd+yMtYaapC85g+2ZLp7V" + "tFHA0EbNHqBUQxoh+Ik89Mlr7AUxcFPd+kCo+NE6yq/mNQcL7E6iQAAAGQAAAATZWN" + "kc2Etc2hhMi1uaXN0cDI1NgAAAEkAAAAhALDSBnmFF59tgTKDQ4meTJEI7/BP2Zgf1" + "AKg1H3kIijQAAAAIFYrqSg6GI03ohXqUVsZ3lCB/XIism2aV5Vz2bg1d9zo " + "./ec256.pub"; + +static const char torture_ecdsa384_private_testkey[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "MIGkAgEBBDBY8jEa5DtRy4AVeTWhPJ/TK257behiC3uafEi6YA2oHORibqX55EDN\n" + "wz29MT40mQSgBwYFK4EEACKhZANiAARXc4BN6BrVo1QMi3+i/B85Lu7SMuzBi+1P\n" + "bJti8xz+Szgq64gaBGOK9o+WOdLAd/w7p7DJLdztJ0bYoyT4V3B3ZqR9RyGq6mYC\n" + "jkXlc5YbYHjueBbp0oeNXqsXHNAWQZo=\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_private_testkey_passphrase[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,5C825E6FE821D0DE99D8403F4B4020CB\n" + "\n" + "TaUq8Qenb52dKAYcQGIYfdT7Z2DroySk38w51kw/gd8o79ZHaAQv60GtaNoy0203\n" + "2X1o29E6c0WsY9DKhSHKm/zzvZmL+ChZYqqh3sd1gp55aJsHNN4axiIu2YCbCavh\n" + "8VZn2VJDaitLy8ARqA/lMGQfqHSa3EOqti9FzWG/P6s=\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIIBHDBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIEuMnFkuHkDkCAggA\n" + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBA/fjhqXxV/Dk7cg8XgPxzuBIHA\n" + "TbiloDCPfKKlkm9ZguahtfJOxcVBbMtrFAK2vA/jMXGnbB9Qe13uLl8fTd6QB4tE\n" + "Zbyucq4OA0L2HyhuEsJiLvf0ICX8APrBajNv3B8F7ZStrXx7hcJUg8qTlsbdovYq\n" + "nCjOKoq/F6ax/r1F9Rr5PlXQDoSKDJ3mQkZc4n8VNKFfXOPQ7C4rEYzglSyzGwyQ\n" + "2EwRwnkkJqcYotRyH4JWtXCRak7znLVDeGbavhpP6paSVsK8OpycAoJstfQb0L4q\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNl\n" + "Y2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQRXc4BN6BrVo1QMi3+i\n" + "/B85Lu7SMuzBi+1PbJti8xz+Szgq64gaBGOK9o+WOdLAd/w7p7DJLdztJ0bYoyT4\n" + "V3B3ZqR9RyGq6mYCjkXlc5YbYHjueBbp0oeNXqsXHNAWQZoAAADIITfDfiE3w34A\n" + "AAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAAhuaXN0cDM4NAAAAGEEV3OATega1aNU\n" + "DIt/ovwfOS7u0jLswYvtT2ybYvMc/ks4KuuIGgRjivaPljnSwHf8O6ewyS3c7SdG\n" + "2KMk+Fdwd2akfUchqupmAo5F5XOWG2B47ngW6dKHjV6rFxzQFkGaAAAAMFjyMRrk\n" + "O1HLgBV5NaE8n9Mrbntt6GILe5p8SLpgDagc5GJupfnkQM3DPb0xPjSZBAAAAAA=\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABB4N\n" + "dKGEoxFeg6dqiR2vTl6AAAAEAAAAAEAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzOD\n" + "QAAAAIbmlzdHAzODQAAABhBFdzgE3oGtWjVAyLf6L8Hzku7tIy7MGL7U9sm2LzHP5\n" + "LOCrriBoEY4r2j5Y50sB3/DunsMkt3O0nRtijJPhXcHdmpH1HIarqZgKOReVzlhtg\n" + "eO54FunSh41eqxcc0BZBmgAAANDOL7sWcylFf8SsjGVFvr36mpyUBpAJ/e7o4RbQg\n" + "H8FDu1IxscOfbLDoB3CV7UEIgG58nVsDamfL6rXV/tzWnPxYxi6jUHcKT1BugO/Jt\n" + "/ncelMeoAS6MAZhElaGKzU1cJMlMTV9ofmuKuAwllQULG7L8lwHs9whBK4JmWPaGL\n" + "pU3i9ZoT33/g6pcvA83vicCNqj7ggl6Vb9MeO/zGW1+oV2HC3WiLTqBsYxEJu4YCM\n" + "ewfx9pWeWaCllNy/F1rCBu3cxqzcge9hqIlNtpT7Dq3k\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_public_testkey[] = + "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzOD" + "QAAABhBFdzgE3oGtWjVAyLf6L8Hzku7tIy7MGL7U9sm2LzHP5LOCrriBoEY4r2j5Y5" + "0sB3/DunsMkt3O0nRtijJPhXcHdmpH1HIarqZgKOReVzlhtgeO54FunSh41eqxcc0B" + "ZBmg== aris@kalix86"; + +static const char torture_ecdsa384_testkey_cert[] = + "ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzd" + "HAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgvggfi3v98HjOiqVi1O5aPy7JvMd" + "rTZe68GZ0qCaAN5MAAAAIbmlzdHAzODQAAABhBFdzgE3oGtWjVAyLf6L8Hzku7tIy7" + "MGL7U9sm2LzHP5LOCrriBoEY4r2j5Y50sB3/DunsMkt3O0nRtijJPhXcHdmpH1HIar" + "qZgKOReVzlhtgeO54FunSh41eqxcc0BZBmgAAAAAAAAAAAAAAAQAAAAdteWlkZW50A" + "AAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmR" + "pbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtc" + "G9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXN" + "lci1yYwAAAAAAAAAAAAAAiAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwM" + "zg0AAAAYQRXc4BN6BrVo1QMi3+i/B85Lu7SMuzBi+1PbJti8xz+Szgq64gaBGOK9o+" + "WOdLAd/w7p7DJLdztJ0bYoyT4V3B3ZqR9RyGq6mYCjkXlc5YbYHjueBbp0oeNXqsXH" + "NAWQZoAAACEAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAABpAAAAMQD5f0pF6U6eeBO" + "PrOV7Y3w5NuTzvuyDAq0kTv6VYNMp83TYpIJw16+tMAplOSzPTvwAAAAwWD9StvMEP" + "b+SDH2G5qqkMk+F5IaHI9fev8zcFzzdOlilLc/+CFM0NKMAFtOrrhv0 " + "./ec384.pub"; + +static const char torture_ecdsa521_private_testkey[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "MIHbAgEBBEG83nSJ2SLoiBvEku1JteQKWx/Xt6THksgC7rrIaTUmNzk+60f0sCCm\n" + "Gll0dgrZLmeIw+TtnG1E20VZflCKq+IdkaAHBgUrgQQAI6GBiQOBhgAEAc6D728d\n" + "baQkHnSPtztaRwJw63CBl15cykB4SXXuwWdNOtPzBijUULMTTvBXbra8gL4ATd9d\n" + "Qnuwn8KQUh2T/z+BARjWPKhcHcGx57XpXCEkawzMYaHUUnRdeFEmNRsbXypsf0mJ\n" + "KATU3h8gzTMkbrx8DJTFHEIjXBShs44HsSYVl3Xy\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa521_private_testkey_passphrase[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,24C4F383915BC07D9C63209BF6AD3DEE\n" + "\n" + "M+JGfpGfoH3Wn6XWSoHrGGevaS6p2vJGQdkFEIgUfh16s+U/LcRhAhRnhX/MV6Ds\n" + "OZTpusrjInlZXNUR97fJbmjr/600qUlh4y3U9ikiX3IXE+RI80TPNdishOOjKRF7\n" + "aWDW8UxTlFfU2Zc1Ew0pTvMXXcuTpozW1NNVY+6S9uWfHwq1/EcR35dbnEmG0gId\n" + "qsiEdVKh7p+9Qto8jcVWzMh7ANMcIwmxQ4zbvnqypwgAgpMbamWqBZ9q4egsVZKd\n" + "uRzL95L05ctOBGYNYqpPNIX3UdQU07kzwNC+yaHOb2s=\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa521_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIIBXTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIY6X14D05Q7gCAggA\n" + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBCmngDUX2/kg+45m4qoCBLiBIIB\n" + "ANHV+GC6Hnend9cVScT5oNtOS2a/TD82N1h+9cYmxn953IRNk2rF7LFYFFeZzcZi\n" + "e840YFYFRiTScm1GbKgwyFLYzYguvpUpS3qz3yZMygoX3xlvFw0l8FWsfeUmOzG1\n" + "uQQPGeoFCus43D3k1iQCOafEe0DPbyfcF/IxajZ+P0N8A5ikgPsOfpTLAdWiYgFt\n" + "wkafVfXx5ZH1u8S34+kmoKRhf5zBFQI1BHD6bCQDANPBkbP4KEjH5mHRO99nHK9r\n" + "EhdLDBEXRo9xb1BhgPLdQA0AdPPqZ6Wugy3KyxkEiH/GB/oBoIpg0oALnowL129g\n" + "BV6jZHwXHuO4/CLJ9rN2tdE=\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_ecdsa521_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNl\n" + "Y2RzYS1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBzoPvbx1tpCQedI+3\n" + "O1pHAnDrcIGXXlzKQHhJde7BZ0060/MGKNRQsxNO8FdutryAvgBN311Ce7CfwpBS\n" + "HZP/P4EBGNY8qFwdwbHntelcISRrDMxhodRSdF14USY1GxtfKmx/SYkoBNTeHyDN\n" + "MyRuvHwMlMUcQiNcFKGzjgexJhWXdfIAAAEAt6sYz7erGM8AAAATZWNkc2Etc2hh\n" + "Mi1uaXN0cDUyMQAAAAhuaXN0cDUyMQAAAIUEAc6D728dbaQkHnSPtztaRwJw63CB\n" + "l15cykB4SXXuwWdNOtPzBijUULMTTvBXbra8gL4ATd9dQnuwn8KQUh2T/z+BARjW\n" + "PKhcHcGx57XpXCEkawzMYaHUUnRdeFEmNRsbXypsf0mJKATU3h8gzTMkbrx8DJTF\n" + "HEIjXBShs44HsSYVl3XyAAAAQgC83nSJ2SLoiBvEku1JteQKWx/Xt6THksgC7rrI\n" + "aTUmNzk+60f0sCCmGll0dgrZLmeIw+TtnG1E20VZflCKq+IdkQAAAAABAg==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa521_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAj\n" + "9WBFa/piJcPFEE4CGZTKAAAAEAAAAAEAAACsAAAAE2VjZHNhLXNoYTItbmlzdHA1\n" + "MjEAAAAIbmlzdHA1MjEAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl1\n" + "7sFnTTrT8wYo1FCzE07wV262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee1\n" + "6VwhJGsMzGGh1FJ0XXhRJjUbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOO\n" + "B7EmFZd18gAAAQDLjaKp+DLEHFb98f5WnVFg6LgDN847sfeuPZVfVjeSAiIv016O\n" + "ld7DXb137B2xYVsuce6sHbypr10dJOvgMTLdzTl+crYNJL+8UufJP0rOIFaDenzQ\n" + "RW8wydwiQxwt1ZqtD8ASqFmadxngufJKZzPLGfjCbCz3uATKa2sXN66nRXRZJbVA\n" + "IlNYDY8ivAStNhfItUMqyM6PkYlKJECtJw7w7TYKpvts7t72JmtgqVjS45JI/YZ+\n" + "kitIG0YmG8rzL9d1vBB5m+MH/fnFz2uJqbQYCH9Ctc8HZodAVoTNDzXHU2mYF9PE\n" + "Z6+gi3jd+kOyUk3NifHcre9K6ie7LL33JayM\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + + +static const char torture_ecdsa521_public_testkey[] = + "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1Mj" + "EAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl17sFnTTrT8wYo1FCzE07w" + "V262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee16VwhJGsMzGGh1FJ0XXhRJj" + "UbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOOB7EmFZd18g== aris@kalix86"; + +static const char torture_ecdsa521_testkey_cert[] = + "ecdsa-sha2-nistp521-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzd" + "HA1MjEtY2VydC12MDFAb3BlbnNzaC5jb20AAAAggFIwlsx63C++kmCBDF4O14fvu5j" + "Icsm8uMbMp0smOVwAAAAIbmlzdHA1MjEAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwg" + "ZdeXMpAeEl17sFnTTrT8wYo1FCzE07wV262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jy" + "oXB3Bsee16VwhJGsMzGGh1FJ0XXhRJjUbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI" + "1wUobOOB7EmFZd18gAAAAAAAAAAAAAAAQAAAAdteWlkZW50AAAAAAAAAAAAAAAA///" + "///////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blc" + "m1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5" + "nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAA" + "AAArAAAABNlY2RzYS1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBzoPvbx1" + "tpCQedI+3O1pHAnDrcIGXXlzKQHhJde7BZ0060/MGKNRQsxNO8FdutryAvgBN311Ce" + "7CfwpBSHZP/P4EBGNY8qFwdwbHntelcISRrDMxhodRSdF14USY1GxtfKmx/SYkoBNT" + "eHyDNMyRuvHwMlMUcQiNcFKGzjgexJhWXdfIAAACnAAAAE2VjZHNhLXNoYTItbmlzd" + "HA1MjEAAACMAAAAQgCJzTxw/hz2qE8Qkd4XW9Qn7fPxML6Ebtttg9C18AguyGyE6Nk" + "YH1NcToYxwQxrgzDXowXYm9eCbq9JEvaXDEtIfAAAAEIBk06LmKAYR2HDwwt4f5wVI" + "PKJ0pHVLZEx3FMZI3SfwS9mVm+oojLkZ2hr8X0xn28zbN045d8daB7BB1mHMGNT+YA" + "= ./ec521.pub"; + +/**************************************************************************** + * ED25519 KEYS + ****************************************************************************/ + +static const char torture_ed25519_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MC4CAQAwBQYDK2VwBCIEIGBhcqLe61tkqVjIHKEzwB3oINasSHWGbIWXQWcLPmGN\n" + "-----END PRIVATE KEY-----\n"; + +static const char torture_ed25519_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + "QyNTUxOQAAACAVlp8bgmIjsrzGC7ZIKBMhCpS1fpJTPgVOjYdz5gIqlwAAAJBzsDN1c7Az\n" + "dQAAAAtzc2gtZWQyNTUxOQAAACAVlp8bgmIjsrzGC7ZIKBMhCpS1fpJTPgVOjYdz5gIqlw\n" + "AAAEBgYXKi3utbZKlYyByhM8Ad6CDWrEh1hmyFl0FnCz5hjRWWnxuCYiOyvMYLtkgoEyEK\n" + "lLV+klM+BU6Nh3PmAiqXAAAADGFyaXNAa2FsaXg4NgE=\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ed25519_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jYmMAAAAGYmNyeXB0AAAAGAAAABDYuz+a8i\n" + "nb/BgGjQjQtvkUAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCYiOyvMYL\n" + "tkgoEyEKlLV+klM+BU6Nh3PmAiqXAAAAkOBxqvzvPSns3TbhjkCayvANI66100OELnpDOm\n" + "JBGgXr5q846NkAovH3pmJ4O7qzPLTQ/cm0+959VUODRhM1i96qBg5MTNtV33lf5Y57Klzu\n" + "JegbiexcqkHIzriH42K0XSOEpfW8f/rTH7ffjbE/7l8HRNwf7AmcnxLx/d8J8FTBr+8aU7\n" + "qMU3xAJ4ixnwhYFg==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ed25519_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAie1RBk/ub+EwICCAAw\n" + "DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEECRLkPChQx/sZPYLdNJhxMUEQFLj\n" + "7nelAdOx3WXIBbCOfOqg3aAn8C5cXPtIQ+fiui1V8wlXXV8RBiuDCC97ScLs91D5\n" + "qQhQtw0vgfnq1um/izg=\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_ed25519_public_testkey[] = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCYiOyvMYLtkgoEyEKlLV+klM+" + "BU6Nh3PmAiqX aris@kalix86"; + +static const char torture_ed25519_testkey_cert[] = + "ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQ" + "G9wZW5zc2guY29tAAAAILrR4sPB+b6BRId/OkQha9nWwoACXqUTILz1TrmG4R9CAAA" + "AIBWWnxuCYiOyvMYLtkgoEyEKlLV+klM+BU6Nh3PmAiqXAAAAAAAAAAAAAAABAAAAB" + "215aWRlbnQAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTE" + "tZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAF" + "nBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnB" + "lcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCY" + "iOyvMYLtkgoEyEKlLV+klM+BU6Nh3PmAiqXAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEB" + "d8AogGWM6njfejbazFVyfnjNiWqatx6IV3Nnqc3LjCiPY19fqIPe2YJSzytHwLTD5X" + "IjD2bJpq2ZfjQwXpO0J ./ed.pub"; + +static const char *torture_get_testkey_internal(enum ssh_keytypes_e type, + bool with_passphrase, + int pubkey, + int format) +{ + switch (type) { + case SSH_KEYTYPE_DSS: + if (pubkey) { + return torture_dsa_public_testkey; + } else if (with_passphrase) { + if (format == 1) { + return torture_dsa_private_openssh_testkey_passphrase; + } + if (format == 2) { + return torture_dsa_private_pkcs8_testkey_passphrase; + } else { + return torture_dsa_private_testkey_passphrase; + } + } + if (format == 1) { + return torture_dsa_private_openssh_testkey; + } + return torture_dsa_private_testkey; + case SSH_KEYTYPE_RSA: + if (pubkey) { + return torture_rsa_public_testkey; + } else if (with_passphrase) { + if (format == 1) { + return torture_rsa_private_openssh_testkey_passphrase; + } + if (format == 2) { + return torture_rsa_private_pkcs8_testkey_passphrase; + } else { + return torture_rsa_private_testkey_passphrase; + } + } + if (format == 1) { + return torture_rsa_private_openssh_testkey; + } + return torture_rsa_private_testkey; + case SSH_KEYTYPE_ECDSA_P521: + if (pubkey) { + return torture_ecdsa521_public_testkey; + } else if (with_passphrase) { + if (format == 1) { + return torture_ecdsa521_private_openssh_testkey_passphrase; + } + if (format == 2) { + return torture_ecdsa521_private_pkcs8_testkey_passphrase; + } else { + return torture_ecdsa521_private_testkey_passphrase; + } + } + if (format == 1) { + return torture_ecdsa521_private_openssh_testkey; + } + return torture_ecdsa521_private_testkey; + case SSH_KEYTYPE_ECDSA_P384: + if (pubkey) { + return torture_ecdsa384_public_testkey; + } else if (with_passphrase){ + if (format == 1) { + return torture_ecdsa384_private_openssh_testkey_passphrase; + } + if (format == 2) { + return torture_ecdsa384_private_pkcs8_testkey_passphrase; + } else { + return torture_ecdsa384_private_testkey_passphrase; + } + } + if (format == 1) { + return torture_ecdsa384_private_openssh_testkey; + } + return torture_ecdsa384_private_testkey; + case SSH_KEYTYPE_ECDSA_P256: + if (pubkey) { + return torture_ecdsa256_public_testkey; + } else if (with_passphrase){ + if (format == 1) { + return torture_ecdsa256_private_openssh_testkey_pasphrase; + } + if (format == 2) { + return torture_ecdsa256_private_pkcs8_testkey_passphrase; + } else { + return torture_ecdsa256_private_testkey_passphrase; + } + } + if (format == 1) { + return torture_ecdsa256_private_openssh_testkey; + } + return torture_ecdsa256_private_testkey; + case SSH_KEYTYPE_ED25519: + if (pubkey) { + return torture_ed25519_public_testkey; + } else if (with_passphrase) { + if (format == 1) { + return torture_ed25519_private_openssh_testkey_passphrase; + } + if (format == 2) { + return torture_ed25519_private_pkcs8_testkey_passphrase; + } + /* ed25519 keys are not available in legacy PEM format */ + return NULL; + } + if (format == 1) { + return torture_ed25519_private_openssh_testkey; + } + /* ed25519 keys are not available in legacy PEM format */ + return torture_ed25519_private_pkcs8_testkey; + case SSH_KEYTYPE_DSS_CERT01: + return torture_dsa_testkey_cert; + case SSH_KEYTYPE_RSA_CERT01: + return torture_rsa_testkey_cert; + case SSH_KEYTYPE_ECDSA_P256_CERT01: + return torture_ecdsa256_testkey_cert; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + return torture_ecdsa384_testkey_cert; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + return torture_ecdsa521_testkey_cert; + case SSH_KEYTYPE_ED25519_CERT01: + return torture_ed25519_testkey_cert; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + return NULL; + } + + return NULL; +} + +/* Return the encrypted private key in a new OpenSSH format */ +const char *torture_get_openssh_testkey(enum ssh_keytypes_e type, + bool with_passphrase) +{ + return torture_get_testkey_internal(type, with_passphrase, 0, 1); +} + +/* Return the private key in PEM format */ +const char *torture_get_testkey(enum ssh_keytypes_e type, + bool with_passphrase) +{ +#if defined(HAVE_LIBCRYPTO) + return torture_get_testkey_internal(type, with_passphrase, 0, 2); +#else + return torture_get_testkey_internal(type, with_passphrase, 0, 0); +#endif +} + +const char *torture_get_testkey_pub(enum ssh_keytypes_e type) +{ + return torture_get_testkey_internal(type, 0, 1, 0); +} + +const char *torture_get_testkey_passphrase(void) +{ + return TORTURE_TESTKEY_PASSWORD; +} diff --git a/tests/torture_key.h b/tests/torture_key.h new file mode 100644 index 0000000..961fdb9 --- /dev/null +++ b/tests/torture_key.h @@ -0,0 +1,42 @@ +/* + * torture_key.h - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 by Andreas Schneider + * + * 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. + */ + +#ifndef _TORTURE_KEY_H +#define _TORTURE_KEY_H + +#include + +#define TORTURE_TESTKEY_PASSWORD "libssh-rocks" + +/* Return the encrypted private key in a new OpenSSH format */ +const char *torture_get_openssh_testkey(enum ssh_keytypes_e type, + bool with_passphrase); + +/* Return the private key in the legacy PEM format */ +const char *torture_get_testkey(enum ssh_keytypes_e type, + bool with_passphrase); +const char *torture_get_testkey_passphrase(void); + +const char *torture_get_testkey_pub(enum ssh_keytypes_e type); + +#endif /* _TORTURE_KEY_H */ diff --git a/tests/torture_pki.c b/tests/torture_pki.c new file mode 100644 index 0000000..cbf6495 --- /dev/null +++ b/tests/torture_pki.c @@ -0,0 +1,102 @@ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#elif (defined _WIN32) || (defined _WIN64) +#include +#define read _read +#define open _open +#define write _write +#define close _close +#endif + +#include "torture_pki.h" + +char *torture_pki_read_file(const char *filename) +{ + char *key; + int fd; + int size; + int rc; + struct stat sb; + + if (filename == NULL || filename[0] == '\0') { + return NULL; + } + + fd = open(filename, O_RDONLY); + if (fd < 0) { + return NULL; + } + + rc = fstat(fd, &sb); + if (rc != 0) { + close(fd); + return NULL; + } + + key = malloc(sb.st_size + 1); + if (key == NULL) { + close(fd); + return NULL; + } + + size = read(fd, key, sb.st_size); + close(fd); + if (size != sb.st_size) { + free(key); + return NULL; + } + + key[size] = '\0'; + return key; +} + +int torture_read_one_line(const char *filename, char *buffer, size_t len) +{ + FILE *fp; + size_t nmemb; + + fp = fopen(filename, "r"); + if (fp == NULL) { + return -1; + } + + nmemb = fread(buffer, len - 2, 1, fp); + if (nmemb != 0 || ferror(fp)) { + fclose(fp); + return -1; + } + buffer[len - 1] = '\0'; + + fclose(fp); + + return 0; +} + +/** + * @internal + * + * Returns the character len of a public key string, omitting the comment part + */ +size_t torture_pubkey_len(const char *pubkey) +{ + const char *ptr; + + ptr = strchr(pubkey, ' '); + if (ptr != NULL) { + ptr = strchr(ptr + 1, ' '); + if (ptr != NULL) { + return ptr - pubkey; + } + } + + return 0; +} diff --git a/tests/torture_pki.h b/tests/torture_pki.h new file mode 100644 index 0000000..460cc91 --- /dev/null +++ b/tests/torture_pki.h @@ -0,0 +1,3 @@ +char *torture_pki_read_file(const char *filename); +int torture_read_one_line(const char *filename, char *buffer, size_t len); +size_t torture_pubkey_len(const char *pubkey); diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt new file mode 100644 index 0000000..6f49e0d --- /dev/null +++ b/tests/unittests/CMakeLists.txt @@ -0,0 +1,102 @@ +project(unittests C) + +include_directories(${OPENSSL_INCLUDE_DIR}) + +set(LIBSSH_UNIT_TESTS + torture_buffer + torture_bytearray + torture_callbacks + torture_crypto + torture_init + torture_list + torture_misc + torture_config + torture_options + torture_isipaddr + torture_knownhosts_parsing + torture_hashes + torture_packet_filter + torture_temp_dir + torture_temp_file + torture_push_pop_dir + torture_session_keys + torture_tokens +) + +set(LIBSSH_THREAD_UNIT_TESTS + torture_rand + torture_threads_init + torture_threads_buffer + torture_threads_crypto +) + +if (UNIX AND NOT WIN32) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + # this uses a socketpair + torture_packet + # requires ssh-keygen + torture_keyfiles + torture_pki + torture_pki_rsa + torture_pki_ed25519 + # requires /dev/null + torture_channel + ) + + if (WITH_SERVER) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_bind_config) + + if (WITH_GEX) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_moduli) + endif() + endif() + + + if (HAVE_DSA) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_pki_dsa + ) + endif() + + if (HAVE_ECC) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_pki_ecdsa + ) + endif() + + set(LIBSSH_THREAD_UNIT_TESTS + ${LIBSSH_THREAD_UNIT_TESTS} + # requires pthread + torture_threads_pki_rsa + ) + # Not working correctly + #if (WITH_SERVER) + # add_cmocka_test(torture_server_x11 torture_server_x11.c ${TEST_TARGET_LIBRARIES}) + #endif (WITH_SERVER) +endif (UNIX AND NOT WIN32) + +foreach(_UNIT_TEST ${LIBSSH_UNIT_TESTS}) + add_cmocka_test(${_UNIT_TEST} + SOURCES ${_UNIT_TEST}.c + COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} + LINK_LIBRARIES ${TEST_TARGET_LIBRARIES} + ) +endforeach() + +if (CMAKE_USE_PTHREADS_INIT) + foreach(_UNIT_TEST ${LIBSSH_THREAD_UNIT_TESTS}) + add_cmocka_test(${_UNIT_TEST} + SOURCES ${_UNIT_TEST}.c + COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} + LINK_LIBRARIES ${TEST_TARGET_LIBRARIES} Threads::Threads + ) + endforeach() +endif () + diff --git a/tests/unittests/torture_bind_config.c b/tests/unittests/torture_bind_config.c new file mode 100644 index 0000000..11d8672 --- /dev/null +++ b/tests/unittests/torture_bind_config.c @@ -0,0 +1,1315 @@ +/* + * torture_bind_config.c - Tests for server side configuration + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "torture_key.h" + +#include +#include + +extern LIBSSH_THREAD int ssh_log_level; + +#define LOGLEVEL "verbose" +#define LOGLEVEL2 "fatal" +#define LOGLEVEL3 "DEBUG1" +#define LOGLEVEL4 "DEBUG2" +#define LISTEN_ADDRESS "::1" +#define LISTEN_ADDRESS2 "::2" +#define KEXALGORITHMS "ecdh-sha2-nistp521,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha1" +#define KEXALGORITHMS2 "ecdh-sha2-nistp521" +#define CIPHERS "aes128-ctr,aes192-ctr,aes256-ctr" +#define CIPHERS2 "aes256-ctr" +#define HOSTKEYALGORITHMS "ssh-ed25519,ecdsa-sha2-nistp521,ssh-rsa" +#define HOSTKEYALGORITHMS_UNKNOWN "ssh-ed25519,ecdsa-sha2-nistp521,unknown,ssh-rsa" +#define HOSTKEYALGORITHMS2 "rsa-sha2-256" +#define PUBKEYACCEPTEDTYPES "rsa-sha2-512,ssh-rsa,ecdsa-sha2-nistp521" +#define PUBKEYACCEPTEDTYPES_UNKNOWN "rsa-sha2-512,ssh-rsa,unknown,ecdsa-sha2-nistp521" +#define PUBKEYACCEPTEDTYPES2 "rsa-sha2-256,ssh-rsa" +#define MACS "hmac-sha1,hmac-sha2-256,hmac-sha2-512,hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com" +#define MACS2 "hmac-sha1" + +#ifdef HAVE_DSA +#define LIBSSH_DSA_TESTKEY "libssh_testkey.id_dsa" +#endif +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_ED25519_TESTKEY "libssh_testkey.id_ed25519" +#ifdef HAVE_ECC +#define LIBSSH_ECDSA_521_TESTKEY "libssh_testkey.id_ecdsa521" +#endif + +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS "libssh_test_bind_config_listenaddress" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2 "libssh_test_bind_config_listenaddress2" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE "libssh_test_bind_config_listenaddress_twice" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC "libssh_test_bind_config_listenaddress_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_PORT "libssh_test_bind_config_port" +#define LIBSSH_TEST_BIND_CONFIG_PORT2 "libssh_test_bind_config_port2" +#define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE "libssh_test_bind_config_port_twice" +#define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC "libssh_test_bind_config_port_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY "libssh_test_bind_config_hostkey" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY2 "libssh_test_bind_config_hostkey2" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE "libssh_test_bind_config_hostkey_twice" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC "libssh_test_bind_config_hostkey_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL "libssh_test_bind_config_loglevel" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL2 "libssh_test_bind_config_loglevel2" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE "libssh_test_bind_config_loglevel_twice" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC "libssh_test_bind_config_loglevel_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS "libssh_test_bind_config_ciphers" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS2 "libssh_test_bind_config_ciphers2" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE "libssh_test_bind_config_ciphers_twice" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC "libssh_test_bind_config_ciphers_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_MACS "libssh_test_bind_config_macs" +#define LIBSSH_TEST_BIND_CONFIG_MACS2 "libssh_test_bind_config_macs2" +#define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE "libssh_test_bind_config_macs_twice" +#define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC "libssh_test_bind_config_macs_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS "libssh_test_bind_config_kexalgorithms" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2 "libssh_test_bind_config_kexalgorithms2" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE "libssh_test_bind_config_kexalgorithms_twice" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC "libssh_test_bind_config_kexalgorithms_twice_rec" + +#define LIBSSH_TEST_BIND_CONFIG_FULL "libssh_test_bind_config_full" +#define LIBSSH_TEST_BIND_CONFIG_INCLUDE "libssh_test_bind_config_include" +#define LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE "libssh_test_bind_config_include_recursive" +#define LIBSSH_TEST_BIND_CONFIG_CORNER_CASES "libssh_test_bind_config_corner_cases" + +#define LIBSSH_TEST_BIND_CONFIG_MATCH_ALL "libssh_test_bind_config_match_all" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_TWICE "libssh_test_bind_config_match_twice" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_UNSUPPORTED "libssh_test_bind_config_match_unsupported" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_NOT_ALLOWED "libssh_test_bind_config_match_not_allowed" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_CORNER_CASES "libssh_test_bind_config_match_corner_cases" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID "libssh_test_bind_config_match_invalid" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID2 "libssh_test_bind_config_match_invalid2" + +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED "libssh_test_bind_config_pubkey" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2 "libssh_test_bind_config_pubkey2" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE "libssh_test_bind_config_pubkey_twice" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC "libssh_test_bind_config_pubkey_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN "libssh_test_bind_config_pubkey_unknown" + +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS "libssh_test_bind_config_hostkey_alg" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2 "libssh_test_bind_config_hostkey_alg2" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE "libssh_test_bind_config_hostkey_alg_twice" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC "libssh_test_bind_config_hostkey_alg_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN "libssh_test_bind_config_hostkey_alg_unknown" + +const char template[] = "temp_dir_XXXXXX"; + +struct bind_st { + char *cwd; + char *temp_dir; + ssh_bind bind; +}; + +static int setup_config_files(void **state) +{ + struct bind_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct bind_st *)malloc(sizeof(struct bind_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + /* For ed25519 the test keys are not available in legacy PEM format. Using + * the new OpenSSH format for all algorithms */ + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 0)); + + torture_write_file(LIBSSH_ED25519_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); +#ifdef HAVE_ECC + torture_write_file(LIBSSH_ECDSA_521_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); +#endif +#ifdef HAVE_DSA + torture_write_file(LIBSSH_DSA_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_DSS, 0)); +#endif + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS, + "ListenAddress "LISTEN_ADDRESS"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2, + "ListenAddress "LISTEN_ADDRESS2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE, + "ListenAddress "LISTEN_ADDRESS"\n" + "ListenAddress "LISTEN_ADDRESS2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC, + "ListenAddress "LISTEN_ADDRESS"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT, + "Port 123\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT2, + "Port 456\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT_TWICE, + "Port 123\n" + "Port 456\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC, + "Port 123\n" + "Include "LIBSSH_TEST_BIND_CONFIG_PORT2"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY, + "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY2, + "HostKey "LIBSSH_RSA_TESTKEY"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE, + "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" + "HostKey "LIBSSH_RSA_TESTKEY"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC, + "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_HOSTKEY2"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL, + "LogLevel "LOGLEVEL"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL2, + "LogLevel "LOGLEVEL2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE, + "LogLevel "LOGLEVEL"\n" + "LogLevel "LOGLEVEL2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC, + "LogLevel "LOGLEVEL"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_LOGLEVEL2"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS, + "Ciphers "CIPHERS"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS2, + "Ciphers "CIPHERS2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE, + "Ciphers "CIPHERS"\n" + "Ciphers "CIPHERS2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC, + "Ciphers "CIPHERS"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_CIPHERS2"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS, + "MACs "MACS"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS2, + "MACs "MACS2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS_TWICE, + "MACs "MACS"\n" + "MACs "MACS2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC, + "MACs "MACS"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_MACS2"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS, + "KexAlgorithms "KEXALGORITHMS"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2, + "KexAlgorithms "KEXALGORITHMS2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE, + "KexAlgorithms "KEXALGORITHMS"\n" + "KexAlgorithms "KEXALGORITHMS2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC, + "KexAlgorithms "KEXALGORITHMS"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_FULL, + "ListenAddress "LISTEN_ADDRESS"\n" + "Port 123\n" + "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" + "LogLevel "LOGLEVEL"\n" + "Ciphers "CIPHERS"\n" + "MACs "MACS"\n" + "KexAlgorithms "KEXALGORITHMS"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_INCLUDE, + "Include "LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_PORT"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_HOSTKEY"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_LOGLEVEL"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_CIPHERS"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_MACS"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE, + "Include "LIBSSH_TEST_BIND_CONFIG_INCLUDE"\n"); + + /* Unsupported options and corner cases */ + torture_write_file(LIBSSH_TEST_BIND_CONFIG_CORNER_CASES, + "\n" /* empty line */ + "# comment line\n" + " # comment line not starting with hash\n" + "UnknownConfigurationOption yes\n" + "Ciphers "CIPHERS2"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_ALL, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_TWICE, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL3"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_UNSUPPORTED, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match User alice\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match Group sftp_users\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match Host 192.168.0.*\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match LocalAddress 172.30.1.5\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match LocalPort 42\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match Rdomain 4\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match Address 10.0.0.10\n" + "\tLogLevel "LOGLEVEL2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_NOT_ALLOWED, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match All\n" + "\tListenAddress "LISTEN_ADDRESS2"\n" + "\tPort 456\n" + "\tHostKey "LIBSSH_RSA_TESTKEY"\n" + "\tCiphers "CIPHERS2"\n" + "\tMACs "MACS2"\n" + "\tKexAlgorithms "KEXALGORITHMS2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_CORNER_CASES, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match User alice\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL3"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match User alice All\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL3"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL4"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID2, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match All User alice\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL3"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL4"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED, + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2, + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE, + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC, + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN, + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES_UNKNOWN"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS, + "HostKeyAlgorithms "HOSTKEYALGORITHMS"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2, + "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE, + "HostKeyAlgorithms "HOSTKEYALGORITHMS"\n" + "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC, + "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN, + "HostKeyAlgorithms "HOSTKEYALGORITHMS_UNKNOWN"\n"); + return 0; +} + +static int sshbind_setup(void **state) +{ + int rc; + struct bind_st *test_state = NULL; + + rc = setup_config_files((void **)&test_state); + assert_int_equal(rc, 0); + assert_non_null(test_state); + + test_state->bind = ssh_bind_new(); + assert_non_null(test_state->bind); + + *state = test_state; + + return 0; +} + +static int sshbind_teardown(void **state) +{ + struct bind_st *test_state = NULL; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + assert_non_null(test_state->bind); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + ssh_bind_free(test_state->bind); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_bind_config_listen_address(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS); + assert_int_equal(rc, 0); + assert_non_null(bind->bindaddr); + assert_string_equal(bind->bindaddr, LISTEN_ADDRESS); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE); + assert_int_equal(rc, 0); + assert_non_null(bind->bindaddr); + assert_string_equal(bind->bindaddr, LISTEN_ADDRESS); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC); + assert_int_equal(rc, 0); + assert_non_null(bind->bindaddr); + assert_string_equal(bind->bindaddr, LISTEN_ADDRESS); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2); + assert_int_equal(rc, 0); + assert_non_null(bind->bindaddr); + assert_string_equal(bind->bindaddr, LISTEN_ADDRESS2); + +} + +static void torture_bind_config_port(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_PORT); + assert_int_equal(rc, 0); + assert_int_equal(bind->bindport, 123); + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_PORT_TWICE); + assert_int_equal(rc, 0); + assert_int_equal(bind->bindport, 123); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC); + assert_int_equal(rc, 0); + assert_int_equal(bind->bindport, 123); + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_PORT2); + assert_int_equal(rc, 0); + assert_int_equal(bind->bindport, 456); +} + +static void torture_bind_config_hostkey(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_HOSTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE); + assert_int_equal(rc, 0); + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); + assert_non_null(bind->rsakey); + assert_string_equal(bind->rsakey, LIBSSH_RSA_TESTKEY); +} + +static void torture_bind_config_hostkey_twice_rec(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC); + assert_int_equal(rc, 0); + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); + assert_non_null(bind->rsakey); + assert_string_equal(bind->rsakey, LIBSSH_RSA_TESTKEY); +} + +static void torture_bind_config_hostkey_separately(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_HOSTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_HOSTKEY2); + assert_int_equal(rc, 0); + assert_non_null(bind->rsakey); + assert_string_equal(bind->rsakey, LIBSSH_RSA_TESTKEY); + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); +} + +static void torture_bind_config_loglevel(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level, new_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_LOGLEVEL); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 2); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 2); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 2); + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_LOGLEVEL2); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 1); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_ciphers(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + char *fips_ciphers = NULL; + char *fips_ciphers2 = NULL; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + if (ssh_fips_mode()) { + fips_ciphers = ssh_keep_fips_algos(SSH_CRYPT_C_S, CIPHERS); + assert_non_null(fips_ciphers); + fips_ciphers2 = ssh_keep_fips_algos(SSH_CRYPT_C_S, CIPHERS2); + assert_non_null(fips_ciphers2); + } + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_CIPHERS); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], fips_ciphers); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], fips_ciphers); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], CIPHERS); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], CIPHERS); + } + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], fips_ciphers); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], fips_ciphers); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], CIPHERS); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], CIPHERS); + } + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC); + assert_int_equal(rc, 0); + + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], fips_ciphers); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], fips_ciphers); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], CIPHERS); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], CIPHERS); + } + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_CIPHERS2); + assert_int_equal(rc, 0); + + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], fips_ciphers2); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], fips_ciphers2); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], CIPHERS2); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], CIPHERS2); + } + + SAFE_FREE(fips_ciphers); + SAFE_FREE(fips_ciphers2); +} + +static void torture_bind_config_macs(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_MACS); + assert_int_equal(rc, 0); + + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], MACS); + + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], MACS); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE); + assert_int_equal(rc, 0); + + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], MACS); + + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], MACS); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC); + assert_int_equal(rc, 0); + + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], MACS); + + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], MACS); + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_MACS2); + assert_int_equal(rc, 0); + + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], MACS2); + + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], MACS2); +} + +static void torture_bind_config_kexalgorithms(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + char *fips_kex = NULL; + char *fips_kex2 = NULL; + int rc; + + if (ssh_fips_mode()) { + fips_kex = ssh_keep_fips_algos(SSH_KEX, KEXALGORITHMS); + assert_non_null(fips_kex); + fips_kex2 = ssh_keep_fips_algos(SSH_KEX, KEXALGORITHMS2); + assert_non_null(fips_kex2); + } + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_KEX]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_KEX], fips_kex); + } else { + assert_string_equal(bind->wanted_methods[SSH_KEX], KEXALGORITHMS); + } + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_KEX]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_KEX], fips_kex); + } else { + assert_string_equal(bind->wanted_methods[SSH_KEX], KEXALGORITHMS); + } + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_KEX]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_KEX], fips_kex); + } else { + assert_string_equal(bind->wanted_methods[SSH_KEX], KEXALGORITHMS); + } + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_KEX]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_KEX], fips_kex2); + } else { + assert_string_equal(bind->wanted_methods[SSH_KEX], KEXALGORITHMS2); + } + + SAFE_FREE(fips_kex); + SAFE_FREE(fips_kex2); +} + +static void torture_bind_config_pubkey_accepted(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + char *fips_pubkeys = NULL; + char *fips_pubkeys2 = NULL; + + if (ssh_fips_mode()) { + fips_pubkeys = ssh_keep_fips_algos(SSH_HOSTKEYS, PUBKEYACCEPTEDTYPES); + assert_non_null(fips_pubkeys); + fips_pubkeys2 = ssh_keep_fips_algos(SSH_HOSTKEYS, PUBKEYACCEPTEDTYPES2); + assert_non_null(fips_pubkeys2); + } + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED); + assert_int_equal(rc, 0); + assert_non_null(bind->pubkey_accepted_key_types); + if (ssh_fips_mode()) { + assert_string_equal(bind->pubkey_accepted_key_types, fips_pubkeys); + } else { + assert_string_equal(bind->pubkey_accepted_key_types, PUBKEYACCEPTEDTYPES); + } + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2); + assert_int_equal(rc, 0); + assert_non_null(bind->pubkey_accepted_key_types); + if (ssh_fips_mode()) { + assert_string_equal(bind->pubkey_accepted_key_types, fips_pubkeys2); + } else { + assert_string_equal(bind->pubkey_accepted_key_types, PUBKEYACCEPTEDTYPES2); + } + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE); + assert_int_equal(rc, 0); + assert_non_null(bind->pubkey_accepted_key_types); + if (ssh_fips_mode()) { + assert_string_equal(bind->pubkey_accepted_key_types, fips_pubkeys); + } else { + assert_string_equal(bind->pubkey_accepted_key_types, PUBKEYACCEPTEDTYPES); + } + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC); + assert_int_equal(rc, 0); + assert_non_null(bind->pubkey_accepted_key_types); + if (ssh_fips_mode()) { + assert_string_equal(bind->pubkey_accepted_key_types, fips_pubkeys2); + } else { + assert_string_equal(bind->pubkey_accepted_key_types, PUBKEYACCEPTEDTYPES2); + } + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN); + assert_int_equal(rc, 0); + assert_non_null(bind->pubkey_accepted_key_types); + if (ssh_fips_mode()) { + assert_string_equal(bind->pubkey_accepted_key_types, fips_pubkeys); + } else { + assert_string_equal(bind->pubkey_accepted_key_types, PUBKEYACCEPTEDTYPES); + } + + SAFE_FREE(fips_pubkeys); + SAFE_FREE(fips_pubkeys2); +} + +static void torture_bind_config_hostkey_algorithms(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + char *fips_hostkeys = NULL; + char *fips_hostkeys2 = NULL; + + if (ssh_fips_mode()) { + fips_hostkeys = ssh_keep_fips_algos(SSH_HOSTKEYS, HOSTKEYALGORITHMS); + assert_non_null(fips_hostkeys); + fips_hostkeys2 = ssh_keep_fips_algos(SSH_HOSTKEYS, HOSTKEYALGORITHMS2); + assert_non_null(fips_hostkeys2); + } + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], fips_hostkeys); + } else { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], HOSTKEYALGORITHMS); + } + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], fips_hostkeys2); + } else { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], HOSTKEYALGORITHMS2); + } + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], fips_hostkeys); + } else { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], HOSTKEYALGORITHMS); + } + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], fips_hostkeys2); + } else { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], HOSTKEYALGORITHMS2); + } + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], fips_hostkeys); + } else { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], HOSTKEYALGORITHMS); + } + + SAFE_FREE(fips_hostkeys); + SAFE_FREE(fips_hostkeys2); +} + +static int assert_full_bind_config(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int new_level; + + char *fips_ciphers = NULL; + char *fips_kex = NULL; + + if (ssh_fips_mode()) { + fips_ciphers = ssh_keep_fips_algos(SSH_CRYPT_C_S, CIPHERS); + assert_non_null(fips_ciphers); + fips_kex = ssh_keep_fips_algos(SSH_KEX, KEXALGORITHMS); + assert_non_null(fips_kex); + } + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 2); + + assert_non_null(bind->bindaddr); + assert_string_equal(bind->bindaddr, LISTEN_ADDRESS); + + assert_int_equal(bind->bindport, 123); + + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); + + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], fips_ciphers); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], CIPHERS); + } + + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], fips_ciphers); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], CIPHERS); + } + + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], MACS); + + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], MACS); + + assert_non_null(bind->wanted_methods[SSH_KEX]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_KEX], fips_kex); + } else { + assert_string_equal(bind->wanted_methods[SSH_KEX], KEXALGORITHMS); + } + + SAFE_FREE(fips_ciphers); + SAFE_FREE(fips_kex); + + return 0; +} + +static void torture_bind_config_full(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_FULL); + assert_int_equal(rc, 0); + + rc = assert_full_bind_config(state); + assert_int_equal(rc, 0); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_include(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_INCLUDE); + assert_int_equal(rc, 0); + + rc = assert_full_bind_config(state); + assert_int_equal(rc, 0); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_include_recursive(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE); + assert_int_equal(rc, 0); + + rc = assert_full_bind_config(state); + assert_int_equal(rc, 0); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +/** + * @brief Verify the configuration parser does not choke on unknown + * or unsupported configuration options + */ +static void torture_bind_config_corner_cases(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_CORNER_CASES); + assert_int_equal(rc, 0); + + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], CIPHERS2); + + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], CIPHERS2); +} + +static void torture_bind_config_match_all(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level, new_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_ALL); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 1); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_match_twice(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level, new_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_TWICE); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 1); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_match_unsupported(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_UNSUPPORTED); + assert_int_equal(rc, 0); + + rc = assert_full_bind_config(state); + assert_int_equal(rc, 0); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_match_not_allowed(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_NOT_ALLOWED); + assert_int_equal(rc, 0); + + rc = assert_full_bind_config(state); + assert_int_equal(rc, 0); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_match_corner_cases(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level, new_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_CORNER_CASES); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 3); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_match_invalid(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID); + assert_int_equal(rc, -1); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID2); + assert_int_equal(rc, -1); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_rec, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_separately, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_full, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_include, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_include_recursive, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_corner_cases, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_all, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_twice, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_unsupported, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_not_allowed, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_corner_cases, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_invalid, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms, + sshbind_setup, sshbind_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_buffer.c b/tests/unittests/torture_buffer.c new file mode 100644 index 0000000..4502228 --- /dev/null +++ b/tests/unittests/torture_buffer.c @@ -0,0 +1,280 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#define DEBUG_BUFFER +#include "buffer.c" + +#define LIMIT (8*1024*1024) + +static int setup(void **state) { + ssh_buffer buffer; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return -1; + } + ssh_buffer_set_secure(buffer); + *state = (void *) buffer; + + return 0; +} + +static int teardown(void **state) { + SSH_BUFFER_FREE(*state); + + return 0; +} + +/* + * Test if the continuously growing buffer size never exceeds 2 time its + * real capacity + */ +static void torture_growing_buffer(void **state) { + ssh_buffer buffer = *state; + int i; + + for(i=0;iused >= 128){ + if(ssh_buffer_get_len(buffer) * 2 < buffer->allocated){ + assert_true(ssh_buffer_get_len(buffer) * 2 >= buffer->allocated); + } + } + } +} + +/* + * Test if the continuously growing buffer size never exceeds 2 time its + * real capacity, when we remove 1 byte after each call (sliding window) + */ +static void torture_growing_buffer_shifting(void **state) { + ssh_buffer buffer = *state; + int i; + unsigned char c; + for(i=0; i<1024;++i){ + ssh_buffer_add_data(buffer,"S",1); + } + for(i=0;iused >= 128){ + if(ssh_buffer_get_len(buffer) * 4 < buffer->allocated){ + assert_true(ssh_buffer_get_len(buffer) * 4 >= buffer->allocated); + return; + } + } + } +} + +/* + * Test the behavior of ssh_buffer_prepend_data + */ +static void torture_buffer_prepend(void **state) { + ssh_buffer buffer = *state; + uint32_t v; + ssh_buffer_add_data(buffer,"abcdef",6); + ssh_buffer_prepend_data(buffer,"xyz",3); + assert_int_equal(ssh_buffer_get_len(buffer),9); + assert_memory_equal(ssh_buffer_get(buffer), "xyzabcdef", 9); + + /* Now remove 4 bytes and see if we can replace them */ + ssh_buffer_get_u32(buffer,&v); + assert_int_equal(ssh_buffer_get_len(buffer),5); + assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5); + + ssh_buffer_prepend_data(buffer,"aris",4); + assert_int_equal(ssh_buffer_get_len(buffer),9); + assert_memory_equal(ssh_buffer_get(buffer), "arisbcdef", 9); + + /* same thing but we add 5 bytes now */ + ssh_buffer_get_u32(buffer,&v); + assert_int_equal(ssh_buffer_get_len(buffer),5); + assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5); + + ssh_buffer_prepend_data(buffer,"12345",5); + assert_int_equal(ssh_buffer_get_len(buffer),10); + assert_memory_equal(ssh_buffer_get(buffer), "12345bcdef", 10); +} + +/* + * Test the behavior of ssh_buffer_get_ssh_string with invalid data + */ +static void torture_ssh_buffer_get_ssh_string(void **state) { + ssh_buffer buffer; + int i,j,k,l, rc; + /* some values that can go wrong */ + uint32_t values[] = {0xffffffff, 0xfffffffe, 0xfffffffc, 0xffffff00, + 0x80000000, 0x80000004, 0x7fffffff}; + char data[128]; + (void)state; + memset(data,'X',sizeof(data)); + for(i=0; i < (int)(sizeof(values)/sizeof(values[0]));++i){ + for(j=0; j< (int)sizeof(data);++j){ + for(k=1;k<5;++k){ + buffer = ssh_buffer_new(); + assert_non_null(buffer); + + for(l=0;l + +#include "torture.h" +#include "libssh/bytearray.h" + +static void torture_pull_le_u8(void **state) +{ + uint8_t data[2] = {0}; + uint8_t result; + + (void)state; + + result = PULL_LE_U8(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x2a; + result = PULL_LE_U8(data, 0); + assert_int_equal(result, 42); + + + data[0] = 0xf; + result = PULL_LE_U8(data, 0); + assert_int_equal(result, 0xf); + + data[0] = 0xff; + result = PULL_LE_U8(data, 0); + assert_int_equal(result, 0xff); + + data[1] = 0x2a; + result = PULL_LE_U8(data, 1); + assert_int_equal(result, 42); +} + +static void torture_pull_le_u16(void **state) +{ + uint8_t data[2] = {0, 0}; + uint16_t result; + + (void)state; + + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x2a; + data[1] = 0x00; + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 42); + + data[0] = 0xff; + data[1] = 0x00; + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 0x00ff); + + data[0] = 0x00; + data[1] = 0xff; + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 0xff00); + + data[0] = 0xff; + data[1] = 0xff; + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 0xffff); +} + +static void torture_pull_le_u32(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint32_t result; + + (void)state; + + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x2a; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x00; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 42); + + data[0] = 0xff; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x00; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0x00ff); + + data[0] = 0x00; + data[1] = 0xff; + data[2] = 0x00; + data[3] = 0x00; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0xff00); + + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0xff; + data[3] = 0x00; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0xff0000); + + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0xff; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0xff000000); + + data[0] = 0xff; + data[1] = 0xff; + data[2] = 0xff; + data[3] = 0xff; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0xffffffff); +} + +static void torture_push_le_u8(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint8_t data2[4] = {42, 42, 42, 42}; + + (void)state; + + PUSH_LE_U8(data, 0, 42); + PUSH_LE_U8(data, 1, 42); + PUSH_LE_U8(data, 2, 42); + PUSH_LE_U8(data, 3, 42); + assert_memory_equal(data, data2, sizeof(data)); +} + +static void torture_push_le_u16(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint8_t data2[4] = {0xa6, 0x7f, 0x2a, 0x00}; + uint16_t result; + + (void)state; + + PUSH_LE_U16(data, 0, 32678); + PUSH_LE_U16(data, 2, 42); + assert_memory_equal(data, data2, sizeof(data)); + + result = PULL_LE_U16(data, 2); + assert_int_equal(result, 42); + + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 32678); +} + +static void torture_push_le_u32(void **state) +{ + uint8_t data[8] = {0}; + uint8_t data2[8] = {0xa6, 0x7f, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00}; + uint32_t result; + + (void)state; + + PUSH_LE_U32(data, 0, 32678); + PUSH_LE_U32(data, 4, 42); + assert_memory_equal(data, data2, sizeof(data)); + + result = PULL_LE_U32(data, 4); + assert_int_equal(result, 42); + + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 32678); + + PUSH_LE_U32(data, 0, 0xfffefffe); + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0xfffefffe); +} + +static void torture_push_le_u64(void **state) +{ + uint8_t data[16] = {0}; + uint64_t result; + + (void)state; + + PUSH_LE_U64(data, 0, 32678); + + result = PULL_LE_U64(data, 0); + assert_int_equal(result, 32678); + + PUSH_LE_U64(data, 0, 0xfffefffefffefffeUL); + + result = PULL_LE_U64(data, 0); + assert_int_equal(result, 0xfffefffefffefffeUL); +} + +/****************** BIG ENDIAN ********************/ + +static void torture_pull_be_u8(void **state) +{ + uint8_t data[2] = {0}; + uint8_t result; + + (void)state; + + result = PULL_BE_U8(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x2a; + result = PULL_BE_U8(data, 0); + assert_int_equal(result, 42); + + + data[0] = 0xf; + result = PULL_BE_U8(data, 0); + assert_int_equal(result, 0xf); + + data[0] = 0xff; + result = PULL_BE_U8(data, 0); + assert_int_equal(result, 0xff); + + data[1] = 0x2a; + result = PULL_BE_U8(data, 1); + assert_int_equal(result, 42); +} + +static void torture_pull_be_u16(void **state) +{ + uint8_t data[2] = {0, 0}; + uint16_t result; + + (void)state; + + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x00; + data[1] = 0x2a; + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 42); + + data[0] = 0x00; + data[1] = 0xff; + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 0x00ff); + + data[0] = 0xff; + data[1] = 0x00; + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 0xff00); + + data[0] = 0xff; + data[1] = 0xff; + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 0xffff); +} + +static void torture_pull_be_u32(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint32_t result; + + (void)state; + + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x2a; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 42); + + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0xff; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0x00ff); + + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0xff; + data[3] = 0x00; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0xff00); + + data[0] = 0x00; + data[1] = 0xff; + data[2] = 0x00; + data[3] = 0x00; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0xff0000); + + data[0] = 0xff; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x00; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0xff000000); + + data[0] = 0xff; + data[1] = 0xff; + data[2] = 0xff; + data[3] = 0xff; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0xffffffff); +} + +static void torture_push_be_u8(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint8_t data2[4] = {42, 42, 42, 42}; + + (void)state; + + PUSH_BE_U8(data, 0, 42); + PUSH_BE_U8(data, 1, 42); + PUSH_BE_U8(data, 2, 42); + PUSH_BE_U8(data, 3, 42); + assert_memory_equal(data, data2, sizeof(data)); +} + +static void torture_push_be_u16(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint8_t data2[4] = {0x7f, 0xa6, 0x00, 0x2a}; + uint16_t result; + + (void)state; + + PUSH_BE_U16(data, 0, 32678); + PUSH_BE_U16(data, 2, 42); + assert_memory_equal(data, data2, sizeof(data)); + + result = PULL_BE_U16(data, 2); + assert_int_equal(result, 42); + + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 32678); +} + +static void torture_push_be_u32(void **state) +{ + uint8_t data[8] = {0}; + uint8_t data2[8] = {0x00, 0x00, 0x7f, 0xa6, 0x00, 0x00, 0x00, 0x2a}; + uint32_t result; + + (void)state; + + PUSH_BE_U32(data, 0, 32678); + PUSH_BE_U32(data, 4, 42); + assert_memory_equal(data, data2, sizeof(data)); + + result = PULL_BE_U32(data, 4); + assert_int_equal(result, 42); + + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 32678); + + PUSH_BE_U32(data, 0, 0xfffefffe); + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0xfffefffe); +} + +static void torture_push_be_u64(void **state) +{ + uint8_t data[16] = {0}; + uint64_t result; + + (void)state; + + PUSH_BE_U64(data, 0, 32678); + + result = PULL_BE_U64(data, 0); + assert_int_equal(result, 32678); + + PUSH_LE_U64(data, 8, 0xfffefffe); + + result = PULL_LE_U64(data, 8); + assert_int_equal(result, 0xfffefffe); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pull_le_u8), + cmocka_unit_test(torture_pull_le_u16), + cmocka_unit_test(torture_pull_le_u32), + + cmocka_unit_test(torture_push_le_u8), + cmocka_unit_test(torture_push_le_u16), + cmocka_unit_test(torture_push_le_u32), + cmocka_unit_test(torture_push_le_u64), + + /* BIG ENDIAN */ + cmocka_unit_test(torture_pull_be_u8), + cmocka_unit_test(torture_pull_be_u16), + cmocka_unit_test(torture_pull_be_u32), + + cmocka_unit_test(torture_push_be_u8), + cmocka_unit_test(torture_push_be_u16), + cmocka_unit_test(torture_push_be_u32), + cmocka_unit_test(torture_push_be_u64), + }; + + torture_filter_tests(tests); + + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} diff --git a/tests/unittests/torture_callbacks.c b/tests/unittests/torture_callbacks.c new file mode 100644 index 0000000..f5f7e4d --- /dev/null +++ b/tests/unittests/torture_callbacks.c @@ -0,0 +1,259 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#include +#include + +static int myauthcallback (const char *prompt, char *buf, size_t len, + int echo, int verify, void *userdata) { + (void) prompt; + (void) buf; + (void) len; + (void) echo; + (void) verify; + (void) userdata; + return 0; +} + +static int setup(void **state) +{ + struct ssh_callbacks_struct *cb; + + cb = malloc(sizeof(struct ssh_callbacks_struct)); + assert_non_null(cb); + ZERO_STRUCTP(cb); + + cb->userdata = (void *) 0x0badc0de; + cb->auth_function = myauthcallback; + + ssh_callbacks_init(cb); + *state = cb; + + return 0; +} + +static int teardown(void **state) +{ + free(*state); + + return 0; +} + +static void torture_callbacks_size(void **state) { + struct ssh_callbacks_struct *cb = *state;; + + assert_int_not_equal(cb->size, 0); +} + +static void torture_callbacks_exists(void **state) { + struct ssh_callbacks_struct *cb = *state; + + assert_int_not_equal(ssh_callbacks_exists(cb, auth_function), 0); + assert_int_equal(ssh_callbacks_exists(cb, log_function), 0); + + /* + * We redefine size so auth_function is outside the range of + * callbacks->size. + */ + cb->size = (unsigned char *) &cb->auth_function - (unsigned char *) cb; + assert_int_equal(ssh_callbacks_exists(cb, auth_function), 0); + + /* Now make it one pointer bigger so we spill over the auth_function slot */ + cb->size += sizeof(void *); + assert_int_not_equal(ssh_callbacks_exists(cb, auth_function), 0); +} + +struct test_mock_state { + int executed; +}; + +static void test_mock_ssh_logging_callback(int priority, + const char *function, + const char *buffer, + void *userdata) +{ + struct test_mock_state *t = (struct test_mock_state *)userdata; + + check_expected(priority); + check_expected(function); + check_expected(buffer); + + t->executed++; +} + +static void torture_log_callback(void **state) +{ + struct test_mock_state t = { + .executed = 0, + }; + + (void)state; /* unused */ + + ssh_set_log_callback(test_mock_ssh_logging_callback); + ssh_set_log_userdata(&t); + ssh_set_log_level(1); + + expect_value(test_mock_ssh_logging_callback, priority, 1); + expect_string(test_mock_ssh_logging_callback, function, "torture_log_callback"); + expect_string(test_mock_ssh_logging_callback, buffer, "torture_log_callback: test"); + + SSH_LOG(SSH_LOG_WARN, "test"); + + assert_int_equal(t.executed, 1); +} + +static void cb1(ssh_session session, ssh_channel channel, void *userdata){ + int *v = userdata; + (void) session; + (void) channel; + *v += 1; +} + +static void cb2(ssh_session session, ssh_channel channel, int status, void *userdata){ + int *v = userdata; + (void) session; + (void) channel; + (void) status; + *v += 10; +} + +static void torture_callbacks_execute_list(void **state){ + struct ssh_list *list = ssh_list_new(); + int v = 0, w = 0; + struct ssh_channel_callbacks_struct c1 = { + .channel_eof_function = cb1, + .userdata = &v + }; + struct ssh_channel_callbacks_struct c2 = { + .channel_exit_status_function = cb2, + .userdata = &v + }; + struct ssh_channel_callbacks_struct c3 = { + .channel_eof_function = cb1, + .channel_exit_status_function = cb2, + .userdata = &w + }; + + (void)state; + ssh_callbacks_init(&c1); + ssh_callbacks_init(&c2); + ssh_callbacks_init(&c3); + + ssh_list_append(list, &c1); + ssh_callbacks_execute_list(list, + ssh_channel_callbacks, + channel_eof_function, + NULL, + NULL); + assert_int_equal(v, 1); + + v = 0; + ssh_list_append(list, &c2); + ssh_callbacks_execute_list(list, + ssh_channel_callbacks, + channel_eof_function, + NULL, + NULL); + assert_int_equal(v, 1); + ssh_callbacks_execute_list(list, + ssh_channel_callbacks, + channel_exit_status_function, + NULL, + NULL, + 0); + assert_int_equal(v, 11); + + v = 0; + w = 0; + ssh_list_append(list, &c3); + ssh_callbacks_execute_list(list, + ssh_channel_callbacks, + channel_eof_function, + NULL, + NULL); + assert_int_equal(v, 1); + assert_int_equal(w, 1); + ssh_callbacks_execute_list(list, + ssh_channel_callbacks, + channel_exit_status_function, + NULL, + NULL, + 0); + assert_int_equal(v, 11); + assert_int_equal(w, 11); + + ssh_list_free(list); + +} + +static int cb3(ssh_session session, ssh_channel channel, void *userdata){ + int *v = userdata; + (void)session; + (void)channel; + *v = 1; + return 10; +} + +static void torture_callbacks_iterate(void **state){ + struct ssh_list *list = ssh_list_new(); + int v = 0, w = 0; + struct ssh_channel_callbacks_struct c1 = { + .channel_eof_function = cb1, + .channel_shell_request_function = cb3, + .userdata = &v + }; + struct ssh_channel_callbacks_struct c2 = { + .channel_eof_function = cb1, + .channel_shell_request_function = cb3, + .userdata = &v + }; + + (void)state; /* unused */ + + ssh_callbacks_init(&c1); + ssh_callbacks_init(&c2); + + ssh_list_append(list, &c1); + ssh_list_append(list, &c2); + + ssh_callbacks_iterate(list, ssh_channel_callbacks, channel_eof_function){ + ssh_callbacks_iterate_exec(channel_eof_function, NULL, NULL); + } + ssh_callbacks_iterate_end(); + + assert_int_equal(v, 2); + + v = 0; + ssh_callbacks_iterate(list, ssh_channel_callbacks, channel_shell_request_function){ + w = ssh_callbacks_iterate_exec(channel_shell_request_function, NULL, NULL); + if (w) { + break; + } + } + ssh_callbacks_iterate_end(); + + assert_int_equal(w, 10); + assert_int_equal(v, 1); + + ssh_list_free(list); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_callbacks_size, setup, teardown), + cmocka_unit_test_setup_teardown(torture_callbacks_exists, setup, teardown), + cmocka_unit_test(torture_log_callback), + cmocka_unit_test(torture_callbacks_execute_list), + cmocka_unit_test(torture_callbacks_iterate) + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_channel.c b/tests/unittests/torture_channel.c new file mode 100644 index 0000000..c165c57 --- /dev/null +++ b/tests/unittests/torture_channel.c @@ -0,0 +1,54 @@ +#include "config.h" + +#define LIBSSH_STATIC +#include + +#include +#include +#include + +#include "torture.h" +#include "channels.c" + +static void torture_channel_select(void **state) +{ + fd_set readfds; + int fd; + int rc; + int i; + + (void)state; /* unused */ + + ZERO_STRUCT(readfds); + + fd = open("/dev/null", 0); + assert_true(fd > 2); + + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + + for (i = 0; i < 10; i++) { + ssh_channel cin[1] = { NULL, }; + ssh_channel cout[1] = { NULL, }; + struct timeval tv = { .tv_sec = 0, .tv_usec = 1000 }; + + rc = ssh_select(cin, cout, fd + 1, &readfds, &tv); + assert_int_equal(rc, SSH_OK); + } + + close(fd); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_channel_select), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c new file mode 100644 index 0000000..f91112a --- /dev/null +++ b/tests/unittests/torture_config.c @@ -0,0 +1,1017 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/options.h" +#include "libssh/session.h" +#include "libssh/config_parser.h" +#include "match.c" + +extern LIBSSH_THREAD int ssh_log_level; + +#define LIBSSH_TESTCONFIG1 "libssh_testconfig1.tmp" +#define LIBSSH_TESTCONFIG2 "libssh_testconfig2.tmp" +#define LIBSSH_TESTCONFIG3 "libssh_testconfig3.tmp" +#define LIBSSH_TESTCONFIG4 "libssh_testconfig4.tmp" +#define LIBSSH_TESTCONFIG5 "libssh_testconfig5.tmp" +#define LIBSSH_TESTCONFIG6 "libssh_testconfig6.tmp" +#define LIBSSH_TESTCONFIG7 "libssh_testconfig7.tmp" +#define LIBSSH_TESTCONFIG8 "libssh_testconfig8.tmp" +#define LIBSSH_TESTCONFIG9 "libssh_testconfig9.tmp" +#define LIBSSH_TESTCONFIG10 "libssh_testconfig10.tmp" +#define LIBSSH_TESTCONFIG11 "libssh_testconfig11.tmp" +#define LIBSSH_TESTCONFIG12 "libssh_testconfig12.tmp" +#define LIBSSH_TESTCONFIGGLOB "libssh_testc*[36].tmp" +#define LIBSSH_TEST_PUBKEYACCEPTEDKEYTYPES "libssh_test_PubkeyAcceptedKeyTypes.tmp" + +#define USERNAME "testuser" +#define PROXYCMD "ssh -q -W %h:%p gateway.example.com" +#define ID_FILE "/etc/xxx" +#define KEXALGORITHMS "ecdh-sha2-nistp521,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha1" +#define HOSTKEYALGORITHMS "ssh-ed25519,ecdsa-sha2-nistp521,ssh-rsa" +#define PUBKEYACCEPTEDTYPES "rsa-sha2-512,ssh-rsa,ecdsa-sha2-nistp521" +#define MACS "hmac-sha1,hmac-sha2-256,hmac-sha2-512,hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com" +#define USER_KNOWN_HOSTS "%d/my_known_hosts" +#define GLOBAL_KNOWN_HOSTS "/etc/ssh/my_ssh_known_hosts" +#define BIND_ADDRESS "::1" + + + +static int setup_config_files(void **state) +{ + ssh_session session; + int verbosity; + + unlink(LIBSSH_TESTCONFIG1); + unlink(LIBSSH_TESTCONFIG2); + unlink(LIBSSH_TESTCONFIG3); + unlink(LIBSSH_TESTCONFIG4); + unlink(LIBSSH_TESTCONFIG5); + unlink(LIBSSH_TESTCONFIG6); + unlink(LIBSSH_TESTCONFIG7); + unlink(LIBSSH_TESTCONFIG8); + unlink(LIBSSH_TESTCONFIG9); + unlink(LIBSSH_TESTCONFIG10); + unlink(LIBSSH_TESTCONFIG11); + unlink(LIBSSH_TESTCONFIG12); + unlink(LIBSSH_TEST_PUBKEYACCEPTEDKEYTYPES); + + torture_write_file(LIBSSH_TESTCONFIG1, + "User "USERNAME"\nInclude "LIBSSH_TESTCONFIG2"\n\n"); + torture_write_file(LIBSSH_TESTCONFIG2, + "Include "LIBSSH_TESTCONFIG3"\n" + "ProxyCommand "PROXYCMD"\n\n"); + torture_write_file(LIBSSH_TESTCONFIG3, + "\n\nIdentityFile "ID_FILE"\n" + "\n\nKexAlgorithms "KEXALGORITHMS"\n" + "\n\nHostKeyAlgorithms "HOSTKEYALGORITHMS"\n" + "\n\nPubkeyAcceptedTypes "PUBKEYACCEPTEDTYPES"\n" + "\n\nMACs "MACS"\n"); + + /* Multiple Port settings -> parsing returns early. */ + torture_write_file(LIBSSH_TESTCONFIG4, + "Port 123\nPort 456\n"); + + /* Testing glob include */ + torture_write_file(LIBSSH_TESTCONFIG5, + "User "USERNAME"\nInclude "LIBSSH_TESTCONFIGGLOB"\n\n"); + + torture_write_file(LIBSSH_TESTCONFIG6, + "ProxyCommand "PROXYCMD"\n\n"); + + /* new options */ + torture_write_file(LIBSSH_TESTCONFIG7, + "\tBindAddress "BIND_ADDRESS"\n" + "\tConnectTimeout 30\n" + "\tLogLevel DEBUG3\n" + "\tGlobalKnownHostsFile "GLOBAL_KNOWN_HOSTS"\n" + "\tCompression yes\n" + "\tStrictHostkeyChecking no\n" + "\tGSSAPIDelegateCredentials yes\n" + "\tGSSAPIServerIdentity example.com\n" + "\tGSSAPIClientIdentity home.sweet\n" + "\tUserKnownHostsFile "USER_KNOWN_HOSTS"\n"); + + /* authentication methods */ + torture_write_file(LIBSSH_TESTCONFIG8, + "Host gss\n" + "\tGSSAPIAuthentication yes\n" + "Host kbd\n" + "\tKbdInteractiveAuthentication yes\n" + "Host pass\n" + "\tPasswordAuthentication yes\n" + "Host pubkey\n" + "\tPubkeyAuthentication yes\n" + "Host nogss\n" + "\tGSSAPIAuthentication no\n" + "Host nokbd\n" + "\tKbdInteractiveAuthentication no\n" + "Host nopass\n" + "\tPasswordAuthentication no\n" + "Host nopubkey\n" + "\tPubkeyAuthentication no\n"); + + /* unsupported options and corner cases */ + torture_write_file(LIBSSH_TESTCONFIG9, + "\n" /* empty line */ + "# comment line\n" + " # comment line not starting with hash\n" + "UnknownConfigurationOption yes\n" + "GSSAPIKexAlgorithms yes\n" + "ControlMaster auto\n" /* SOC_NA */ + "VisualHostkey yes\n" /* SOC_UNSUPPORTED */ + ""); + + /* Match keyword */ + torture_write_file(LIBSSH_TESTCONFIG10, + "Match host example\n" + "\tHostName example.com\n" + "Match host example1,example2\n" + "\tHostName exampleN\n" + "Match user guest\n" + "\tHostName guest.com\n" + "Match user tester host testhost\n" + "\tHostName testhost.com\n" + "Match !user tester host testhost\n" + "\tHostName nonuser-testhost.com\n" + "Match all\n" + "\tHostName all-matched.com\n" + /* Unsupported options */ + "Match originalhost example\n" + "\tHostName original-example.com\n" + "Match localuser guest\n" + "\tHostName local-guest.com\n" + ""); + + /* ProxyJump */ + torture_write_file(LIBSSH_TESTCONFIG11, + "Host simple\n" + "\tProxyJump jumpbox\n" + "Host user\n" + "\tProxyJump user@jumpbox\n" + "Host port\n" + "\tProxyJump jumpbox:2222\n" + "Host two-step\n" + "\tProxyJump u1@first:222,u2@second:33\n" + "Host none\n" + "\tProxyJump none\n" + "Host only-command\n" + "\tProxyCommand "PROXYCMD"\n" + "\tProxyJump jumpbox\n" + "Host only-jump\n" + "\tProxyJump jumpbox\n" + "\tProxyCommand "PROXYCMD"\n" + "Host ipv6\n" + "\tProxyJump [2620:52:0::fed]\n" + ""); + + /* RekeyLimit combinations */ + torture_write_file(LIBSSH_TESTCONFIG12, + "Host default\n" + "\tRekeyLimit default none\n" + "Host data1\n" + "\tRekeyLimit 42G\n" + "Host data2\n" + "\tRekeyLimit 31M\n" + "Host data3\n" + "\tRekeyLimit 521K\n" + "Host time1\n" + "\tRekeyLimit default 3D\n" + "Host time2\n" + "\tRekeyLimit default 2h\n" + "Host time3\n" + "\tRekeyLimit default 160m\n" + "Host time4\n" + "\tRekeyLimit default 9600\n" + ""); + + torture_write_file(LIBSSH_TEST_PUBKEYACCEPTEDKEYTYPES, + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n"); + + session = ssh_new(); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + *state = session; + + return 0; +} + +static int teardown(void **state) +{ + unlink(LIBSSH_TESTCONFIG1); + unlink(LIBSSH_TESTCONFIG2); + unlink(LIBSSH_TESTCONFIG3); + unlink(LIBSSH_TESTCONFIG4); + unlink(LIBSSH_TESTCONFIG5); + unlink(LIBSSH_TESTCONFIG6); + unlink(LIBSSH_TESTCONFIG7); + unlink(LIBSSH_TESTCONFIG8); + unlink(LIBSSH_TESTCONFIG9); + unlink(LIBSSH_TESTCONFIG10); + unlink(LIBSSH_TESTCONFIG11); + unlink(LIBSSH_TESTCONFIG12); + unlink(LIBSSH_TEST_PUBKEYACCEPTEDKEYTYPES); + + ssh_free(*state); + + return 0; +} + +/** + * @brief tests ssh_config_parse_file with Include directives + */ +static void torture_config_from_file(void **state) { + ssh_session session = *state; + int ret; + char *v = NULL; + char *fips_algos = NULL; + + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG1); + assert_true(ret == 0); + + /* Test the variable presence */ + + ret = ssh_options_get(session, SSH_OPTIONS_PROXYCOMMAND, &v); + assert_true(ret == 0); + assert_non_null(v); + + assert_string_equal(v, PROXYCMD); + SSH_STRING_FREE_CHAR(v); + + ret = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &v); + assert_true(ret == 0); + assert_non_null(v); + + assert_string_equal(v, ID_FILE); + SSH_STRING_FREE_CHAR(v); + + ret = ssh_options_get(session, SSH_OPTIONS_USER, &v); + assert_true(ret == 0); + assert_non_null(v); + + assert_string_equal(v, USERNAME); + SSH_STRING_FREE_CHAR(v); + + if (ssh_fips_mode()) { + fips_algos = ssh_keep_fips_algos(SSH_KEX, KEXALGORITHMS); + assert_non_null(fips_algos); + assert_string_equal(session->opts.wanted_methods[SSH_KEX], fips_algos); + SAFE_FREE(fips_algos); + fips_algos = ssh_keep_fips_algos(SSH_HOSTKEYS, HOSTKEYALGORITHMS); + assert_non_null(fips_algos); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], fips_algos); + SAFE_FREE(fips_algos); + fips_algos = ssh_keep_fips_algos(SSH_HOSTKEYS, PUBKEYACCEPTEDTYPES); + assert_non_null(fips_algos); + assert_string_equal(session->opts.pubkey_accepted_types, fips_algos); + SAFE_FREE(fips_algos); + fips_algos = ssh_keep_fips_algos(SSH_MAC_C_S, MACS); + assert_non_null(fips_algos); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], fips_algos); + SAFE_FREE(fips_algos); + fips_algos = ssh_keep_fips_algos(SSH_MAC_S_C, MACS); + assert_non_null(fips_algos); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], fips_algos); + SAFE_FREE(fips_algos); + } else { + assert_non_null(session->opts.wanted_methods[SSH_KEX]); + assert_string_equal(session->opts.wanted_methods[SSH_KEX], KEXALGORITHMS); + assert_non_null(session->opts.wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], HOSTKEYALGORITHMS); + assert_non_null(session->opts.pubkey_accepted_types); + assert_string_equal(session->opts.pubkey_accepted_types, PUBKEYACCEPTEDTYPES); + assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], MACS); + assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], MACS); + } +} + +/** + * @brief tests ssh_config_parse_file with multiple Port settings. + */ +static void torture_config_double_ports(void **state) { + ssh_session session = *state; + int ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG4); + assert_true(ret == 0); +} + +static void torture_config_glob(void **state) { + ssh_session session = *state; + int ret; +#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) + char *v; +#endif /* HAVE_GLOB && HAVE_GLOB_GL_FLAGS_MEMBER */ + + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG5); + assert_true(ret == 0); /* non-existing files should not error */ + + /* Test the variable presence */ + +#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) + ret = ssh_options_get(session, SSH_OPTIONS_PROXYCOMMAND, &v); + assert_true(ret == 0); + assert_non_null(v); + + assert_string_equal(v, PROXYCMD); + SSH_STRING_FREE_CHAR(v); + + ret = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &v); + assert_true(ret == 0); + assert_non_null(v); + + assert_string_equal(v, ID_FILE); + SSH_STRING_FREE_CHAR(v); +#endif /* HAVE_GLOB && HAVE_GLOB_GL_FLAGS_MEMBER */ +} + +/** + * @brief Verify the new options are passed from configuration + */ +static void torture_config_new(void **state) +{ + ssh_session session = *state; + int ret = 0; + + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG7); + assert_true(ret == 0); + + assert_string_equal(session->opts.knownhosts, USER_KNOWN_HOSTS); + assert_string_equal(session->opts.global_knownhosts, GLOBAL_KNOWN_HOSTS); + assert_int_equal(session->opts.timeout, 30); + assert_string_equal(session->opts.bindaddr, BIND_ADDRESS); +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "zlib@openssh.com,zlib"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "zlib@openssh.com,zlib"); +#else + assert_null(session->opts.wanted_methods[SSH_COMP_C_S]); + assert_null(session->opts.wanted_methods[SSH_COMP_S_C]); +#endif /* WITH_ZLIB */ + assert_int_equal(session->opts.StrictHostKeyChecking, 0); + assert_int_equal(session->opts.gss_delegate_creds, 1); + assert_string_equal(session->opts.gss_server_identity, "example.com"); + assert_string_equal(session->opts.gss_client_identity, "home.sweet"); + + assert_int_equal(ssh_get_log_level(), SSH_LOG_TRACE); + assert_int_equal(session->common.log_verbosity, SSH_LOG_TRACE); +} + +/** + * @brief Verify the authentication methods from configuration are effective + */ +static void torture_config_auth_methods(void **state) { + ssh_session session = *state; + int ret = 0; + + /* gradually disable all the methods based on different hosts */ + ssh_options_set(session, SSH_OPTIONS_HOST, "nogss"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG8); + assert_true(ret == 0); + assert_false(session->opts.flags & SSH_OPT_FLAG_GSSAPI_AUTH); + assert_true(session->opts.flags & SSH_OPT_FLAG_KBDINT_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "nokbd"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG8); + assert_true(ret == 0); + assert_false(session->opts.flags & SSH_OPT_FLAG_KBDINT_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "nopass"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG8); + assert_true(ret == 0); + assert_false(session->opts.flags & SSH_OPT_FLAG_PASSWORD_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "nopubkey"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG8); + assert_true(ret == 0); + assert_false(session->opts.flags & SSH_OPT_FLAG_PUBKEY_AUTH); + + /* no method should be left enabled */ + assert_int_equal(session->opts.flags, 0); + + /* gradually enable them again */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "gss"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG8); + assert_true(ret == 0); + assert_true(session->opts.flags & SSH_OPT_FLAG_GSSAPI_AUTH); + assert_false(session->opts.flags & SSH_OPT_FLAG_KBDINT_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "kbd"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG8); + assert_true(ret == 0); + assert_true(session->opts.flags & SSH_OPT_FLAG_KBDINT_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "pass"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG8); + assert_true(ret == 0); + assert_true(session->opts.flags & SSH_OPT_FLAG_PASSWORD_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "pubkey"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG8); + assert_true(ret == 0); + assert_true(session->opts.flags & SSH_OPT_FLAG_PUBKEY_AUTH); +} + +/** + * @brief Verify the configuration parser does not choke on unknown + * or unsupported configuration options + */ +static void torture_config_unknown(void **state) { + ssh_session session = *state; + int ret = 0; + + /* test corner cases */ + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG9); + assert_true(ret == 0); + ret = ssh_config_parse_file(session, "/etc/ssh/ssh_config"); + assert_true(ret == 0); + ret = ssh_config_parse_file(session, GLOBAL_CLIENT_CONFIG); + assert_true(ret == 0); +} + + +/** + * @brief Verify the configuration parser accepts Match keyword with + * full OpenSSH syntax. + */ +static void torture_config_match(void **state) +{ + ssh_session session = *state; + char *localuser = NULL; + char config[1024]; + int ret = 0; + + /* Without any settings we should get all-matched.com hostname */ + ssh_options_set(session, SSH_OPTIONS_HOST, "unmatched"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.host, "all-matched.com"); + + /* Hostname example does simple hostname matching */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "example"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.host, "example.com"); + + /* We can match also both hosts from a comma separated list */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "example1"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.host, "exampleN"); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "example2"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.host, "exampleN"); + + /* We can match by user */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_USER, "guest"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.host, "guest.com"); + + /* We can combine two options on a single line to match both of them */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_USER, "tester"); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.host, "testhost.com"); + + /* We can also negate conditions */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_USER, "not-tester"); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.host, "nonuser-testhost.com"); + + /* Match final is not completely supported, but should do quite much the + * same as "match all". The trailing "all" is not mandatory. */ + torture_write_file(LIBSSH_TESTCONFIG10, + "Match final all\n" + "\tHostName final-all.com\n" + ""); + torture_reset_config(session); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.host, "final-all.com"); + + torture_write_file(LIBSSH_TESTCONFIG10, + "Match final\n" + "\tHostName final.com\n" + ""); + torture_reset_config(session); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.host, "final.com"); + + /* Match canonical is not completely supported, but should do quite much the + * same as "match all". The trailing "all" is not mandatory. */ + torture_write_file(LIBSSH_TESTCONFIG10, + "Match canonical all\n" + "\tHostName canonical-all.com\n" + ""); + torture_reset_config(session); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.host, "canonical-all.com"); + + torture_write_file(LIBSSH_TESTCONFIG10, + "Match canonical all\n" + "\tHostName canonical.com\n" + ""); + torture_reset_config(session); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.host, "canonical.com"); + + localuser = ssh_get_local_username(); + assert_non_null(localuser); + snprintf(config, sizeof(config), + "Match localuser %s\n" + "\tHostName otherhost\n" + "", localuser); + free(localuser); + torture_write_file(LIBSSH_TESTCONFIG10, config); + torture_reset_config(session); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.host, "otherhost"); + + /* Try to create some invalid configurations */ + /* Missing argument to Match*/ + torture_write_file(LIBSSH_TESTCONFIG10, + "Match\n" + "\tHost missing.com\n" + ""); + torture_reset_config(session); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Missing argument to unsupported option originalhost */ + torture_write_file(LIBSSH_TESTCONFIG10, + "Match originalhost\n" + "\tHost originalhost.com\n" + ""); + torture_reset_config(session); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Missing argument to option localuser */ + torture_write_file(LIBSSH_TESTCONFIG10, + "Match localuser\n" + "\tUser localuser2\n" + ""); + torture_reset_config(session); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Missing argument to option user */ + torture_write_file(LIBSSH_TESTCONFIG10, + "Match user\n" + "\tUser user2\n" + ""); + torture_reset_config(session); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Missing argument to option host */ + torture_write_file(LIBSSH_TESTCONFIG10, + "Match host\n" + "\tUser host2\n" + ""); + torture_reset_config(session); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Missing argument to unsupported option exec */ + torture_write_file(LIBSSH_TESTCONFIG10, + "Match exec\n" + "\tUser exec\n" + ""); + torture_reset_config(session); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); +} + +/** + * @brief Verify we can parse ProxyJump configuration option + */ +static void torture_config_proxyjump(void **state) { + ssh_session session = *state; + int ret = 0; + + /* Simplest version with just a hostname */ + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.ProxyCommand, "ssh -W [%h]:%p jumpbox"); + + /* With username */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "user"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.ProxyCommand, + "ssh -l user -W [%h]:%p jumpbox"); + + /* With port */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "port"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.ProxyCommand, + "ssh -p 2222 -W [%h]:%p jumpbox"); + + /* Two step jump */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "two-step"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.ProxyCommand, + "ssh -l u1 -p 222 -J u2@second:33 -W [%h]:%p first"); + + /* none */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "none"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code(session, ret); + assert_true(session->opts.ProxyCommand == NULL); + + /* If also ProxyCommand is specifed, the first is applied */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "only-command"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.ProxyCommand, PROXYCMD); + + /* If also ProxyCommand is specifed, the first is applied */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "only-jump"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.ProxyCommand, + "ssh -W [%h]:%p jumpbox"); + + /* IPv6 address */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "ipv6"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code(session, ret); + assert_string_equal(session->opts.ProxyCommand, + "ssh -W [%h]:%p 2620:52:0::fed"); + + /* Try to create some invalid configurations */ + /* Non-numeric port */ + torture_write_file(LIBSSH_TESTCONFIG11, + "Host bad-port\n" + "\tProxyJump jumpbox:22bad22\n" + ""); + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Too many @ */ + torture_write_file(LIBSSH_TESTCONFIG11, + "Host bad-hostname\n" + "\tProxyJump user@principal.com@jumpbox:22\n" + ""); + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "bad-hostname"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Braces mismatch in hostname */ + torture_write_file(LIBSSH_TESTCONFIG11, + "Host mismatch\n" + "\tProxyJump [::1\n" + ""); + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "mismatch"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Bad host-port separator */ + torture_write_file(LIBSSH_TESTCONFIG11, + "Host beef\n" + "\tProxyJump [dead::beef]::22\n" + ""); + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "beef"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Missing hostname */ + torture_write_file(LIBSSH_TESTCONFIG11, + "Host no-host\n" + "\tProxyJump user@:22\n" + ""); + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-host"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Missing user */ + torture_write_file(LIBSSH_TESTCONFIG11, + "Host no-user\n" + "\tProxyJump @host:22\n" + ""); + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-user"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Missing port */ + torture_write_file(LIBSSH_TESTCONFIG11, + "Host no-port\n" + "\tProxyJump host:\n" + ""); + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-port"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Non-numeric port in second jump */ + torture_write_file(LIBSSH_TESTCONFIG11, + "Host bad-port-2\n" + "\tProxyJump localhost,jumpbox:22bad22\n" + ""); + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port-2"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Too many @ in second jump */ + torture_write_file(LIBSSH_TESTCONFIG11, + "Host bad-hostname\n" + "\tProxyJump localhost,user@principal.com@jumpbox:22\n" + ""); + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "bad-hostname"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Braces mismatch in second jump */ + torture_write_file(LIBSSH_TESTCONFIG11, + "Host mismatch\n" + "\tProxyJump localhost,[::1:20\n" + ""); + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "mismatch"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Bad host-port separator in second jump */ + torture_write_file(LIBSSH_TESTCONFIG11, + "Host beef\n" + "\tProxyJump localhost,[dead::beef]::22\n" + ""); + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "beef"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Missing hostname in second jump */ + torture_write_file(LIBSSH_TESTCONFIG11, + "Host no-host\n" + "\tProxyJump localhost,user@:22\n" + ""); + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-host"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Missing user in second jump */ + torture_write_file(LIBSSH_TESTCONFIG11, + "Host no-user\n" + "\tProxyJump localhost,@host:22\n" + ""); + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-user"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* Missing port in second jump */ + torture_write_file(LIBSSH_TESTCONFIG11, + "Host no-port\n" + "\tProxyJump localhost,host:\n" + ""); + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-port"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); +} + +/** + * @brief Verify the configuration parser handles all the possible + * versions of RekeyLimit configuration option. + */ +static void torture_config_rekey(void **state) +{ + ssh_session session = *state; + int ret = 0; + + /* Default values */ + ssh_options_set(session, SSH_OPTIONS_HOST, "default"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG12); + assert_ssh_return_code(session, ret); + assert_int_equal(session->opts.rekey_data, 0); + assert_int_equal(session->opts.rekey_time, 0); + + /* 42 GB */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "data1"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG12); + assert_ssh_return_code(session, ret); + assert_int_equal(session->opts.rekey_data, (uint64_t) 42 * 1024 * 1024 * 1024); + assert_int_equal(session->opts.rekey_time, 0); + + /* 41 MB */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "data2"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG12); + assert_ssh_return_code(session, ret); + assert_int_equal(session->opts.rekey_data, 31 * 1024 * 1024); + assert_int_equal(session->opts.rekey_time, 0); + + /* 521 KB */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "data3"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG12); + assert_ssh_return_code(session, ret); + assert_int_equal(session->opts.rekey_data, 521 * 1024); + assert_int_equal(session->opts.rekey_time, 0); + + /* default 3D */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "time1"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG12); + assert_ssh_return_code(session, ret); + assert_int_equal(session->opts.rekey_data, 0); + assert_int_equal(session->opts.rekey_time, 3 * 24 * 60 * 60 * 1000); + + /* default 2h */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "time2"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG12); + assert_ssh_return_code(session, ret); + assert_int_equal(session->opts.rekey_data, 0); + assert_int_equal(session->opts.rekey_time, 2 * 60 * 60 * 1000); + + /* default 160m */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "time3"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG12); + assert_ssh_return_code(session, ret); + assert_int_equal(session->opts.rekey_data, 0); + assert_int_equal(session->opts.rekey_time, 160 * 60 * 1000); + + /* default 9600 [s] */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "time4"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG12); + assert_ssh_return_code(session, ret); + assert_int_equal(session->opts.rekey_data, 0); + assert_int_equal(session->opts.rekey_time, 9600 * 1000); + +} + +/** + * @brief test ssh_config_parse_file with PubkeyAcceptedKeyTypes + */ +static void torture_config_pubkeyacceptedkeytypes(void **state) +{ + ssh_session session = *state; + int rc; + char *fips_algos; + + rc = ssh_config_parse_file(session, LIBSSH_TEST_PUBKEYACCEPTEDKEYTYPES); + assert_int_equal(rc, SSH_OK); + + if (ssh_fips_mode()) { + fips_algos = ssh_keep_fips_algos(SSH_HOSTKEYS, PUBKEYACCEPTEDTYPES); + assert_non_null(fips_algos); + assert_string_equal(session->opts.pubkey_accepted_types, fips_algos); + SAFE_FREE(fips_algos); + } else { + assert_string_equal(session->opts.pubkey_accepted_types, PUBKEYACCEPTEDTYPES); + } +} + +/* match_pattern() sanity tests + */ +static void torture_config_match_pattern(void **state) +{ + int rv = 0; + + (void) state; + + /* Simple test "a" matches "a" */ + rv = match_pattern("a", "a", MAX_MATCH_RECURSION); + assert_int_equal(rv, 1); + + /* Simple test "a" does not match "b" */ + rv = match_pattern("a", "b", MAX_MATCH_RECURSION); + assert_int_equal(rv, 0); + + /* NULL arguments are correctly handled */ + rv = match_pattern("a", NULL, MAX_MATCH_RECURSION); + assert_int_equal(rv, 0); + rv = match_pattern(NULL, "a", MAX_MATCH_RECURSION); + assert_int_equal(rv, 0); + + /* Simple wildcard ? is handled in pattern */ + rv = match_pattern("a", "?", MAX_MATCH_RECURSION); + assert_int_equal(rv, 1); + rv = match_pattern("aa", "?", MAX_MATCH_RECURSION); + assert_int_equal(rv, 0); + rv = match_pattern("?", "a", MAX_MATCH_RECURSION); /* Wildcard in search string */ + assert_int_equal(rv, 0); + rv = match_pattern("?", "?", MAX_MATCH_RECURSION); + assert_int_equal(rv, 1); + + /* Simple wildcard * is handled in pattern */ + rv = match_pattern("a", "*", MAX_MATCH_RECURSION); + assert_int_equal(rv, 1); + rv = match_pattern("aa", "*", MAX_MATCH_RECURSION); + assert_int_equal(rv, 1); + rv = match_pattern("*", "a", MAX_MATCH_RECURSION); /* Wildcard in search string */ + assert_int_equal(rv, 0); + rv = match_pattern("*", "*", MAX_MATCH_RECURSION); + assert_int_equal(rv, 1); + + /* More complicated patterns */ + rv = match_pattern("a", "*a", MAX_MATCH_RECURSION); + assert_int_equal(rv, 1); + rv = match_pattern("a", "a*", MAX_MATCH_RECURSION); + assert_int_equal(rv, 1); + rv = match_pattern("abababc", "*abc", MAX_MATCH_RECURSION); + assert_int_equal(rv, 1); + rv = match_pattern("ababababca", "*abc", MAX_MATCH_RECURSION); + assert_int_equal(rv, 0); + rv = match_pattern("ababababca", "*abc*", MAX_MATCH_RECURSION); + assert_int_equal(rv, 1); + + /* Multiple wildcards in row */ + rv = match_pattern("aa", "??", MAX_MATCH_RECURSION); + assert_int_equal(rv, 1); + rv = match_pattern("bba", "??a", MAX_MATCH_RECURSION); + assert_int_equal(rv, 1); + rv = match_pattern("aaa", "**a", MAX_MATCH_RECURSION); + assert_int_equal(rv, 1); + rv = match_pattern("bbb", "**a", MAX_MATCH_RECURSION); + assert_int_equal(rv, 0); + + /* Consecutive asterisks do not make sense and do not need to recurse */ + rv = match_pattern("hostname", "**********pattern", 5); + assert_int_equal(rv, 0); + rv = match_pattern("hostname", "pattern**********", 5); + assert_int_equal(rv, 0); + rv = match_pattern("pattern", "***********pattern", 5); + assert_int_equal(rv, 1); + rv = match_pattern("pattern", "pattern***********", 5); + assert_int_equal(rv, 1); + + /* Limit the maximum recursion */ + rv = match_pattern("hostname", "*p*a*t*t*e*r*n*", 5); + assert_int_equal(rv, 0); + rv = match_pattern("pattern", "*p*a*t*t*e*r*n*", 5); /* Too much recursion */ + assert_int_equal(rv, 0); + +} + + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_config_from_file), + cmocka_unit_test(torture_config_double_ports), + cmocka_unit_test(torture_config_glob), + cmocka_unit_test(torture_config_new), + cmocka_unit_test(torture_config_auth_methods), + cmocka_unit_test(torture_config_unknown), + cmocka_unit_test(torture_config_match), + cmocka_unit_test(torture_config_proxyjump), + cmocka_unit_test(torture_config_rekey), + cmocka_unit_test(torture_config_pubkeyacceptedkeytypes), + cmocka_unit_test(torture_config_match_pattern), + }; + + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, setup_config_files, teardown); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_crypto.c b/tests/unittests/torture_crypto.c new file mode 100644 index 0000000..fd5e775 --- /dev/null +++ b/tests/unittests/torture_crypto.c @@ -0,0 +1,123 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/crypto.h" + +uint8_t key[32] = + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e" + "\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d" + "\x1e\x1f"; + +uint8_t IV[16] = + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e" + "\x1f"; + +uint8_t cleartext[144] = + "\xb4\xfc\x5d\xc2\x49\x8d\x2c\x29\x4a\xc9\x9a\xb0\x1b\xf8\x29" + "\xee\x85\x6d\x8c\x04\x34\x7c\x65\xf4\x89\x97\xc5\x71\x70\x41" + "\x91\x40\x19\x60\xe1\xf1\x8f\x4d\x8c\x17\x51\xd6\xbc\x69\x6e" + "\xf2\x21\x87\x18\x6c\xef\xc4\xf4\xd9\xe6\x1b\x94\xf7\xd8\xb2" + "\xe9\x24\xb9\xe7\xe6\x19\xf5\xec\x55\x80\x9a\xc8\x7d\x70\xa3" + "\x50\xf8\x03\x10\x35\x49\x9b\x53\x58\xd7\x4c\xfc\x5f\x02\xd6" + "\x28\xea\xcc\x43\xee\x5e\x2b\x8a\x7a\x66\xf7\x00\xee\x09\x18" + "\x30\x1b\x47\xa2\x16\x69\xc4\x6e\x44\x3f\xbd\xec\x52\xce\xe5" + "\x41\xf2\xe0\x04\x4f\x5a\x55\x58\x37\xba\x45\x8d\x15\x53\xf6" + "\x31\x91\x13\x8c\x51\xed\x08\x07\xdb"; + +uint8_t aes256_cbc_encrypted[144] = + "\x7f\x1b\x92\xac\xc5\x16\x05\x55\x74\xac\xb4\xe0\x91\x8c\xf8" + "\x0d\xa9\x72\xa5\x09\xb8\x44\xee\x55\x02\x13\xb7\x52\x0a\xf0" + "\xac\xd0\x21\x0e\x58\x7b\x34\xfe\xdb\x36\x01\x60\x7d\x18\x3a" + "\xa9\x15\x18\x5b\x13\xca\xdd\x77\x7d\xdf\x64\xc6\xd5\x75\x4b" + "\x02\x02\x37\xb1\xf4\x33\xff\x93\xe6\x32\x08\xda\xcb\x5d\xa2" + "\x8f\x17\x1f\x99\x92\x60\x22\x9d\x6b\xe6\xb2\x5e\xb0\x5d\x26" + "\x3f\xde\xb8\xc1\xb0\x70\x80\x1c\x00\xd0\x93\x2b\xeb\x0f\xd7" + "\x70\x7a\x9a\x7a\xa6\x21\x23\x2c\x02\xb7\xcd\x88\x10\x9c\x2d" + "\x0c\xd3\xfa\xc1\x33\x5b\xe1\xa1\xd4\x3d\x8f\xb8\x50\xc5\xb5" + "\x72\xdd\x6d\x32\x1f\x58\x00\x48\xbe"; + +static int get_cipher(struct ssh_cipher_struct *cipher, const char *ciphername) +{ + struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); + size_t i; + int cmp; + + assert_non_null(cipher); + + for (i = 0; ciphers[i].name != NULL; i++) { + cmp = strcmp(ciphername, ciphers[i].name); + if (cmp == 0){ + memcpy(cipher, &ciphers[i], sizeof(*cipher)); + return SSH_OK; + } + } + + return SSH_ERROR; +} + +static void torture_crypto_aes256_cbc(void **state) +{ + uint8_t output[sizeof(cleartext)] = {0}; + uint8_t iv[16] = {0}; + struct ssh_cipher_struct cipher = {0}; + int rc; + (void)state; + + rc = get_cipher(&cipher, "aes256-cbc"); + assert_int_equal(rc, SSH_OK); + + assert_non_null(cipher.set_encrypt_key); + assert_non_null(cipher.encrypt); + + memcpy(iv, IV, sizeof(IV)); + cipher.set_encrypt_key(&cipher, + key, + iv + ); + + cipher.encrypt(&cipher, + cleartext, + output, + sizeof(cleartext) + ); + + assert_memory_equal(output, aes256_cbc_encrypted, sizeof(aes256_cbc_encrypted)); + ssh_cipher_clear(&cipher); + + rc = get_cipher(&cipher, "aes256-cbc"); + assert_int_equal(rc, SSH_OK); + + assert_non_null(cipher.set_decrypt_key); + assert_non_null(cipher.decrypt); + + memcpy(iv, IV, sizeof(IV)); + cipher.set_decrypt_key(&cipher, + key, + iv + ); + + memset(output, '\0', sizeof(output)); + cipher.decrypt(&cipher, + aes256_cbc_encrypted, + output, + sizeof(aes256_cbc_encrypted) + ); + + assert_memory_equal(output, cleartext, sizeof(cleartext)); + + ssh_cipher_clear(&cipher); +} + +int torture_run_tests(void) { + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(torture_crypto_aes256_cbc), + }; + + ssh_init(); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_hashes.c b/tests/unittests/torture_hashes.c new file mode 100644 index 0000000..a304140 --- /dev/null +++ b/tests/unittests/torture_hashes.c @@ -0,0 +1,164 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "torture_key.h" +#include "legacy.c" +#include "dh.c" + +static int setup_rsa_key(void **state) +{ + int rc=0; + enum ssh_keytypes_e type; + char *b64_key, *p; + ssh_key key; + + const char *q; + + b64_key = strdup(torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + assert_non_null(b64_key); + + q = p = b64_key; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + type = ssh_key_type_from_name(q); + assert_true(type == SSH_KEYTYPE_RSA); + + q = ++p; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_true(rc == 0); + + free(b64_key); + *state = key; + + return 0; +} + +static int teardown(void **state) +{ + SSH_KEY_FREE(*state); + return 0; +} + +static void torture_md5_hash(void **state) +{ + ssh_key pubkey = *state; + char *hash = NULL; + char *hexa = NULL; + size_t hlen; + int rc = 0; + + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_MD5, + (unsigned char **)&hash, &hlen); + if (ssh_fips_mode()) { + /* When in FIPS mode, expect the call to fail */ + assert_int_equal(rc, SSH_ERROR); + } else { + assert_int_equal(rc, SSH_OK); + + hexa = ssh_get_hexa((unsigned char *)hash, hlen); + SSH_STRING_FREE_CHAR(hash); + assert_string_equal(hexa, + "50:15:a0:9b:92:bf:33:1c:01:c5:8c:fe:18:fa:ce:78"); + + SSH_STRING_FREE_CHAR(hexa); + } +} + +static void torture_sha1_hash(void **state) +{ + ssh_key pubkey = *state; + char *hash = NULL; + char *sha1 = NULL; + int rc = 0; + size_t hlen; + + rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_SHA1, + (unsigned char **)&hash, &hlen); + assert_true(rc == 0); + + sha1 = ssh_get_b64_unpadded((unsigned char *)hash, hlen); + SSH_STRING_FREE_CHAR(hash); + assert_string_equal(sha1, "6wP+houujQmxLBiFugTcoeoODCM"); + + SSH_STRING_FREE_CHAR(sha1); +} + +static void torture_sha256_hash(void **state) +{ + ssh_key pubkey = *state; + char *hash = NULL; + char *sha256 = NULL; + int rc = 0; + size_t hlen; + + rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_SHA256, + (unsigned char **)&hash, &hlen); + assert_true(rc == 0); + + sha256 = ssh_get_b64_unpadded((unsigned char *)hash, hlen); + SSH_STRING_FREE_CHAR(hash); + assert_string_equal(sha256, "jXstVLLe84fSDo1kEYGn6iumnPCSorhaiWxnJz8VTII"); + + SSH_STRING_FREE_CHAR(sha256); + +} + +static void torture_sha256_fingerprint(void **state) +{ + ssh_key pubkey = *state; + char *hash = NULL; + char *sha256 = NULL; + int rc = 0; + size_t hlen; + + rc = ssh_get_publickey_hash(pubkey, + SSH_PUBLICKEY_HASH_SHA256, + (unsigned char **)&hash, + &hlen); + assert_true(rc == 0); + + sha256 = ssh_get_fingerprint_hash(SSH_PUBLICKEY_HASH_SHA256, + (unsigned char *)hash, + hlen); + SSH_STRING_FREE_CHAR(hash); + assert_string_equal(sha256, + "SHA256:jXstVLLe84fSDo1kEYGn6iumnPCSorhaiWxnJz8VTII"); + + SSH_STRING_FREE_CHAR(sha256); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_md5_hash, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_sha1_hash, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_sha256_hash, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_sha256_fingerprint, + setup_rsa_key, + teardown), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} diff --git a/tests/unittests/torture_init.c b/tests/unittests/torture_init.c new file mode 100644 index 0000000..6a97a18 --- /dev/null +++ b/tests/unittests/torture_init.c @@ -0,0 +1,46 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" + +static void torture_ssh_init(void **state) { + int rc; + + (void) state; + + rc = ssh_init(); + assert_int_equal(rc, SSH_OK); + rc = ssh_finalize(); + assert_int_equal(rc, SSH_OK); +} + +static void torture_ssh_init_after_finalize(void **state) { + + int rc; + + (void) state; + + rc = ssh_init(); + assert_int_equal(rc, SSH_OK); + rc = ssh_finalize(); + assert_int_equal(rc, SSH_OK); + rc = ssh_init(); + assert_int_equal(rc, SSH_OK); + rc = ssh_finalize(); + assert_int_equal(rc, SSH_OK); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_ssh_init), + cmocka_unit_test(torture_ssh_init_after_finalize), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} diff --git a/tests/unittests/torture_isipaddr.c b/tests/unittests/torture_isipaddr.c new file mode 100644 index 0000000..91fd5a1 --- /dev/null +++ b/tests/unittests/torture_isipaddr.c @@ -0,0 +1,66 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" + +#include "misc.c" +#include "error.c" + +/* + * Test the behavior of ssh_is_ipaddr() + */ +static void torture_ssh_is_ipaddr(void **state) +{ + (void)state; + + assert_int_equal(ssh_is_ipaddr("127.0.0.1"),1); + assert_int_equal(ssh_is_ipaddr("0.0.0.0"),1); + assert_int_equal(ssh_is_ipaddr("1.1.1.1"),1); + assert_int_equal(ssh_is_ipaddr("255.255.255.255"),1); + assert_int_equal(ssh_is_ipaddr("128.128.128.128"),1); + assert_int_equal(ssh_is_ipaddr("1.10.100.1"),1); + assert_int_equal(ssh_is_ipaddr("0.1.10.100"),1); + + assert_int_equal(ssh_is_ipaddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),1); + assert_int_equal(ssh_is_ipaddr("fe80:0000:0000:0000:0202:b3ff:fe1e:8329"),1); + assert_int_equal(ssh_is_ipaddr("fe80:0:0:0:202:b3ff:fe1e:8329"),1); + assert_int_equal(ssh_is_ipaddr("fe80::202:b3ff:fe1e:8329"),1); + assert_int_equal(ssh_is_ipaddr("::1"),1); + + assert_int_equal(ssh_is_ipaddr("::ffff:192.0.2.128"),1); + + assert_int_equal(ssh_is_ipaddr("0.0.0.0.0"),0); + assert_int_equal(ssh_is_ipaddr("0.0.0.0.a"),0); + assert_int_equal(ssh_is_ipaddr("a.0.0.0"),0); + assert_int_equal(ssh_is_ipaddr("0a.0.0.0.0"),0); + assert_int_equal(ssh_is_ipaddr(""),0); + assert_int_equal(ssh_is_ipaddr("0.0.0."),0); + assert_int_equal(ssh_is_ipaddr("0.0"),0); + assert_int_equal(ssh_is_ipaddr("0"),0); + + /* + * FIXME: Temporary workaround for Wine bug + */ +#ifndef _WIN32 + assert_int_equal(ssh_is_ipaddr("255.255.255"),0); +#endif + + assert_int_equal(ssh_is_ipaddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334:1002"), 0); + assert_int_equal(ssh_is_ipaddr("fe80:x:202:b3ff:fe1e:8329"), 0); + assert_int_equal(ssh_is_ipaddr("fe80:x:202:b3ff:fe1e:8329"), 0); + assert_int_equal(ssh_is_ipaddr(":1"), 0); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_ssh_is_ipaddr) + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_keyfiles.c b/tests/unittests/torture_keyfiles.c new file mode 100644 index 0000000..2f06e88 --- /dev/null +++ b/tests/unittests/torture_keyfiles.c @@ -0,0 +1,329 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "torture_key.h" +#include "legacy.c" + +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#ifdef HAVE_DSA +#define LIBSSH_DSA_TESTKEY "libssh_testkey.id_dsa" +#endif + +static int setup_rsa_key(void **state) +{ + ssh_session session; + + unlink(LIBSSH_RSA_TESTKEY); + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(LIBSSH_RSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + session = ssh_new(); + *state = session; + + return 0; +} + +#ifdef HAVE_DSA +static int setup_dsa_key(void **state) +{ + ssh_session session; + + unlink(LIBSSH_DSA_TESTKEY); + unlink(LIBSSH_DSA_TESTKEY ".pub"); + + torture_write_file(LIBSSH_DSA_TESTKEY, + torture_get_testkey(SSH_KEYTYPE_DSS, 0)); + torture_write_file(LIBSSH_DSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_DSS)); + + session = ssh_new(); + *state = session; + + return 0; +} +#endif + +static int setup_both_keys(void **state) { + int rc; + + rc = setup_rsa_key(state); + if (rc != 0) { + return rc; + } +#ifdef HAVE_DSA + ssh_free(*state); + + rc = setup_dsa_key(state); +#endif + + return rc; +} + +static int setup_both_keys_passphrase(void **state) +{ + ssh_session session; + + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_testkey(SSH_KEYTYPE_RSA, 1)); + torture_write_file(LIBSSH_RSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + +#ifdef HAVE_DSA + torture_write_file(LIBSSH_DSA_TESTKEY, + torture_get_testkey(SSH_KEYTYPE_DSS, 1)); + torture_write_file(LIBSSH_DSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_DSS)); +#endif + + session = ssh_new(); + *state = session; + + return 0; +} + +static int teardown(void **state) +{ +#ifdef HAVE_DSA + unlink(LIBSSH_DSA_TESTKEY); + unlink(LIBSSH_DSA_TESTKEY ".pub"); +#endif + + unlink(LIBSSH_RSA_TESTKEY); + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + ssh_free(*state); + + return 0; +} + +static void torture_pubkey_from_file(void **state) { + ssh_session session = *state; + ssh_string pubkey = NULL; + int type, rc; + + rc = ssh_try_publickey_from_file(session, LIBSSH_RSA_TESTKEY, &pubkey, &type); + + assert_true(rc == 0); + + SSH_STRING_FREE(pubkey); + + /* test if it returns 1 if pubkey doesn't exist */ + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + rc = ssh_try_publickey_from_file(session, LIBSSH_RSA_TESTKEY, &pubkey, &type); + assert_true(rc == 1); + + /* This free is unnecessary, but the static analyser does not know */ + SSH_STRING_FREE(pubkey); + + /* test if it returns -1 if privkey doesn't exist */ + unlink(LIBSSH_RSA_TESTKEY); + + rc = ssh_try_publickey_from_file(session, LIBSSH_RSA_TESTKEY, &pubkey, &type); + assert_true(rc == -1); + + /* This free is unnecessary, but the static analyser does not know */ + SSH_STRING_FREE(pubkey); +} + +static int torture_read_one_line(const char *filename, char *buffer, size_t len) +{ + FILE *fp; + size_t nmemb; + + fp = fopen(filename, "r"); + if (fp == NULL) { + return -1; + } + + nmemb = fread(buffer, len - 2, 1, fp); + if (nmemb != 0 || ferror(fp)) { + fclose(fp); + return -1; + } + buffer[len - 1] = '\0'; + + fclose(fp); + + return 0; +} + +static void torture_pubkey_generate_from_privkey(void **state) { + ssh_session session = *state; + ssh_private_key privkey = NULL; + ssh_public_key pubkey = NULL; + ssh_string pubkey_orig = NULL; + ssh_string pubkey_new = NULL; + char pubkey_line_orig[512] = {0}; + char pubkey_line_new[512] = {0}; + char *p; + int type_orig = 0; + int type_new = 0; + int rc; + + /* read the publickey */ + rc = ssh_try_publickey_from_file(session, LIBSSH_RSA_TESTKEY, &pubkey_orig, + &type_orig); + assert_true(rc == 0); + assert_non_null(pubkey_orig); + + rc = torture_read_one_line(LIBSSH_RSA_TESTKEY ".pub", pubkey_line_orig, + sizeof(pubkey_line_orig)); + assert_true(rc == 0); + + /* remove the public key, generate it from the private key and write it. */ + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + privkey = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, 0, NULL); + assert_non_null(privkey); + + pubkey = publickey_from_privatekey(privkey); + assert_non_null(pubkey); + type_new = privkey->type; + privatekey_free(privkey); + + pubkey_new = publickey_to_string(pubkey); + publickey_free(pubkey); + + assert_non_null(pubkey_new); + + assert_true(ssh_string_len(pubkey_orig) == ssh_string_len(pubkey_new)); + assert_memory_equal(ssh_string_data(pubkey_orig), + ssh_string_data(pubkey_new), + ssh_string_len(pubkey_orig)); + + rc = ssh_publickey_to_file(session, LIBSSH_RSA_TESTKEY ".pub", pubkey_new, type_new); + assert_true(rc == 0); + + rc = torture_read_one_line(LIBSSH_RSA_TESTKEY ".pub", pubkey_line_new, + sizeof(pubkey_line_new)); + assert_true(rc == 0); + + /* do not compare hostname */ + p = strrchr(pubkey_line_orig, ' '); + if (p != NULL) { + *p = '\0'; + } + p = strrchr(pubkey_line_new, ' '); + if (p != NULL) { + *p = '\0'; + } + + assert_string_equal(pubkey_line_orig, pubkey_line_new); + + SSH_STRING_FREE(pubkey_orig); + SSH_STRING_FREE(pubkey_new); +} + +/** + * @brief tests the privatekey_from_file function without passphrase + */ +static void torture_privatekey_from_file(void **state) { + ssh_session session = *state; + ssh_private_key key = NULL; + + key = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, SSH_KEYTYPE_RSA, NULL); + assert_non_null(key); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + +#ifdef HAVE_DSA + key = privatekey_from_file(session, LIBSSH_DSA_TESTKEY, SSH_KEYTYPE_DSS, NULL); + assert_non_null(key); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } +#endif + + /* Test the automatic type discovery */ + key = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, 0, NULL); + assert_non_null(key); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + +#ifdef HAVE_DSA + key = privatekey_from_file(session, LIBSSH_DSA_TESTKEY, 0, NULL); + assert_non_null(key); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } +#endif +} + +/** + * @brief tests the privatekey_from_file function with passphrase + */ +static void torture_privatekey_from_file_passphrase(void **state) { + ssh_session session = *state; + ssh_private_key key = NULL; + + key = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, SSH_KEYTYPE_RSA, TORTURE_TESTKEY_PASSWORD); + assert_non_null(key); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + +#ifdef HAVE_DSA + key = privatekey_from_file(session, LIBSSH_DSA_TESTKEY, SSH_KEYTYPE_DSS, TORTURE_TESTKEY_PASSWORD); + assert_non_null(key); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } +#endif + + /* Test the automatic type discovery */ + key = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, 0, TORTURE_TESTKEY_PASSWORD); + assert_non_null(key); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + +#ifdef HAVE_DSA + key = privatekey_from_file(session, LIBSSH_DSA_TESTKEY, 0, TORTURE_TESTKEY_PASSWORD); + assert_non_null(key); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } +#endif +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pubkey_from_file, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pubkey_generate_from_privkey, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_privatekey_from_file, + setup_both_keys, + teardown), + cmocka_unit_test_setup_teardown(torture_privatekey_from_file_passphrase, + setup_both_keys_passphrase, + teardown), + }; + + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_knownhosts_parsing.c b/tests/unittests/torture_knownhosts_parsing.c new file mode 100644 index 0000000..1c2ccc1 --- /dev/null +++ b/tests/unittests/torture_knownhosts_parsing.c @@ -0,0 +1,696 @@ +#include "config.h" + +#include + +#define LIBSSH_STATIC +#include +#include "torture.h" + +#include "knownhosts.c" + +#if (defined _WIN32) || (defined _WIN64) +#ifndef S_IRWXO +#define S_IRWXO 0 +#endif +#ifndef S_IRWXG +#define S_IRWXG 0 +#endif +#endif + +#define LOCALHOST_RSA_LINE "localhost,127.0.0.1 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDD7g+vV5cvxxGN0Ldmda4WZCPgRaxV1tV+1KRZoGUNUI61h0X4bmmGaAPRQBCz4G1d9bawqDqEqnpFWazrxBU5cQtISSjzuDJKovLGliky/ShTszee1Thszg3qVNk9gGOWj7jn/HDaOxRlp003Bp47MOdnMnK/oftllFDfY2fF5IRpE6sSIGtg2ZDtF95TV5/9W2oMOIAy8u/83tuibYlNPa1X/von5LgdaPLn6Bk16bQKIhAhlMtFZH8MBYEWe4ZtOGaSWKOsK9MM/RTMlwPi6PkfoHNl4MCMupjx+CdLXwbQEt9Ww+bBIaCui2VWBEiruVbIgJh0W2Tal0e2BzYZ What a Wurst!" +#define LOCALHOST_ECDSA_SHA1_NISTP256_LINE "localhost ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFWmI0n0Tn5+zR7pPGcKYszRbJ/T0T3QfzRBSMMiyebGKRY8tjkU5h2l/UMugzOrOyWqMGQDgQn+a0aMunhKMg0=" +#define LOCALHOST_DEFAULT_ED25519 "localhost ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" +#define LOCALHOST_PORT_ED25519 "[localhost]:2222 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" +#define LOCALHOST_PATTERN_ED25519 "local* ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" +#define LOCALHOST_HASHED_ED25519 "|1|ayWjmTf9mYgj7PuQNVOa7Lqkj5s=|hkbEh8FN6IkLo6t6GQGuBwamgsM= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" +#define LOCALHOST_PORT_WILDCARD "[localhost]:* ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" +#define LOCALHOST_STANDARD_PORT "[localhost]:22 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" + +#define TMP_FILE_NAME "/tmp/known_hosts_XXXXXX" + +const char template[] = "temp_dir_XXXXXX"; + +static int setup_knownhosts_file(void **state) +{ + char *tmp_file = NULL; + size_t nwritten; + FILE *fp = NULL; + int rc = 0; + + tmp_file = torture_create_temp_file(TMP_FILE_NAME); + assert_non_null(tmp_file); + + *state = tmp_file; + + fp = fopen(tmp_file, "w"); + assert_non_null(fp); + + nwritten = fwrite(LOCALHOST_PATTERN_ED25519, + sizeof(char), + strlen(LOCALHOST_PATTERN_ED25519), + fp); + if (nwritten != strlen(LOCALHOST_PATTERN_ED25519)) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite("\n", sizeof(char), 1, fp); + if (nwritten != 1) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite(LOCALHOST_RSA_LINE, + sizeof(char), + strlen(LOCALHOST_RSA_LINE), + fp); + if (nwritten != strlen(LOCALHOST_RSA_LINE)) { + rc = -1; + goto close_fp; + } + +close_fp: + fclose(fp); + + return rc; +} + +static int setup_knownhosts_file_duplicate(void **state) +{ + char *tmp_file = NULL; + size_t nwritten; + FILE *fp = NULL; + int rc = 0; + + tmp_file = torture_create_temp_file(TMP_FILE_NAME); + assert_non_null(tmp_file); + + *state = tmp_file; + + fp = fopen(tmp_file, "w"); + assert_non_null(fp); + + /* ed25519 key */ + nwritten = fwrite(LOCALHOST_PATTERN_ED25519, + sizeof(char), + strlen(LOCALHOST_PATTERN_ED25519), + fp); + if (nwritten != strlen(LOCALHOST_PATTERN_ED25519)) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite("\n", sizeof(char), 1, fp); + if (nwritten != 1) { + rc = -1; + goto close_fp; + } + + /* RSA key */ + nwritten = fwrite(LOCALHOST_RSA_LINE, + sizeof(char), + strlen(LOCALHOST_RSA_LINE), + fp); + if (nwritten != strlen(LOCALHOST_RSA_LINE)) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite("\n", sizeof(char), 1, fp); + if (nwritten != 1) { + rc = -1; + goto close_fp; + } + + /* ed25519 key again */ + nwritten = fwrite(LOCALHOST_PATTERN_ED25519, + sizeof(char), + strlen(LOCALHOST_PATTERN_ED25519), + fp); + if (nwritten != strlen(LOCALHOST_PATTERN_ED25519)) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite("\n", sizeof(char), 1, fp); + if (nwritten != 1) { + rc = -1; + goto close_fp; + } + +close_fp: + fclose(fp); + + return rc; +} + +static int teardown_knownhosts_file(void **state) +{ + char *tmp_file = *state; + + if (tmp_file == NULL) { + return -1; + } + + unlink(tmp_file); + SAFE_FREE(tmp_file); + + return 0; +} + +static void torture_knownhosts_parse_line_rsa(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_RSA_LINE, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_RSA); + assert_string_equal(entry->comment, "What a Wurst!"); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); + + rc = ssh_known_hosts_parse_line("127.0.0.1", + LOCALHOST_RSA_LINE, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "127.0.0.1"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_RSA); + assert_string_equal(entry->comment, "What a Wurst!"); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_ecdsa(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_ECDSA_SHA1_NISTP256_LINE, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ECDSA_P256); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_default_ed25519(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_DEFAULT_ED25519, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_port_ed25519(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("[localhost]:2222", + LOCALHOST_PORT_ED25519, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "[localhost]:2222"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_port_wildcard(void **state) +{ + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_PORT_WILDCARD, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_standard_port(void **state) +{ + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_STANDARD_PORT, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_pattern_ed25519(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_PATTERN_ED25519, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_hashed_ed25519(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_HASHED_ED25519, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_read_file(void **state) +{ + const char *knownhosts_file = *state; + struct ssh_list *entry_list = NULL; + struct ssh_iterator *it = NULL; + struct ssh_knownhosts_entry *entry = NULL; + enum ssh_keytypes_e type; + int rc; + + rc = ssh_known_hosts_read_entries("localhost", + knownhosts_file, + &entry_list); + assert_int_equal(rc, SSH_OK); + assert_non_null(entry_list); + it = ssh_list_get_iterator(entry_list); + assert_non_null(it); + + /* First key in known hosts file is ED25519 */ + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + assert_non_null(entry); + + assert_string_equal(entry->hostname, "localhost"); + type = ssh_key_type(entry->publickey); + assert_int_equal(type, SSH_KEYTYPE_ED25519); + assert_non_null(it->next); + + it = it->next; + + /* Second key in known hosts file is RSA */ + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + assert_non_null(entry); + + assert_string_equal(entry->hostname, "localhost"); + type = ssh_key_type(entry->publickey); + assert_int_equal(type, SSH_KEYTYPE_RSA); + assert_null(it->next); + + it = ssh_list_get_iterator(entry_list); + for (;it != NULL; it = it->next) { + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + SSH_KNOWNHOSTS_ENTRY_FREE(entry); + } + ssh_list_free(entry_list); +} + +static void torture_knownhosts_get_algorithms_names(void **state) +{ + const char *knownhosts_file = *state; + ssh_session session; + const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa"; + char *names = NULL; + bool process_config = false; + + session = ssh_new(); + assert_non_null(session); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); + + names = ssh_known_hosts_get_algorithms_names(session); + assert_non_null(names); + assert_string_equal(names, expect); + + SAFE_FREE(names); + ssh_free(session); +} + +static void torture_knownhosts_algorithms_wanted(void **state) +{ + const char *knownhosts_file = *state; + char *algo_list = NULL; + ssh_session session; + bool process_config = false; + const char *wanted = "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256," + "rsa-sha2-256,ecdsa-sha2-nistp521"; + const char *expect = "rsa-sha2-256,ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256,ecdsa-sha2-nistp521"; + int verbose = 4; + + session = ssh_new(); + assert_non_null(session); + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + /* Set the wanted list of hostkeys, ordered by preference */ + ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); + + algo_list = ssh_client_select_hostkeys(session); + assert_non_null(algo_list); + assert_string_equal(algo_list, expect); + free(algo_list); + + ssh_free(session); +} + +static void torture_knownhosts_algorithms_negative(UNUSED_PARAM(void **state)) +{ + const char *wanted = NULL; + const char *expect = NULL; + + char *algo_list = NULL; + + char *cwd = NULL; + char *tmp_dir = NULL; + + bool process_config = false; + int verbose = 4; + int rc = 0; + + ssh_session session; + /* Create temporary directory */ + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + session = ssh_new(); + assert_non_null(session); + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose); + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + + /* Test with unknown key type in known_hosts */ + wanted = "rsa-sha2-256"; + ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted); + torture_write_file("unknown_key_type", "localhost unknown AAAABBBBCCCC"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "unknown_key_type"); + algo_list = ssh_client_select_hostkeys(session); + assert_non_null(algo_list); + assert_string_equal(algo_list, wanted); + SAFE_FREE(algo_list); + + /* Test with unsupported, but existing types */ + wanted = "rsa-sha2-256-cert-v01@openssh.com," + "rsa-sha2-512-cert-v01@openssh.com"; + ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted); + algo_list = ssh_client_select_hostkeys(session); + assert_null(algo_list); + + /* In FIPS mode, test filtering keys not allowed */ + if (ssh_fips_mode()) { + wanted = "ssh-ed25519,rsa-sha2-256,ssh-rsa"; + expect = "rsa-sha2-256"; + ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted); + torture_write_file("no_fips", LOCALHOST_DEFAULT_ED25519); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "no_fips"); + algo_list = ssh_client_select_hostkeys(session); + assert_non_null(algo_list); + assert_string_equal(algo_list, expect); + SAFE_FREE(algo_list); + } + + ssh_free(session); + + /* Teardown */ + rc = torture_change_dir(cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(tmp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(tmp_dir); + SAFE_FREE(cwd); +} + +#ifndef _WIN32 /* There is no /dev/null on Windows */ +static void torture_knownhosts_host_exists(void **state) +{ + const char *knownhosts_file = *state; + enum ssh_known_hosts_e found; + ssh_session session; + + session = ssh_new(); + assert_non_null(session); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); + + /* This makes sure the system's known_hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, "/dev/null"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* This makes sure the check will not fail when the system's known_hosts is + * not accessible*/ + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, "./unaccessible"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* This makes sure the check will fail for an unknown host */ + ssh_options_set(session, SSH_OPTIONS_HOST, "wurstbrot"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_UNKNOWN); + + ssh_free(session); +} + +static void torture_knownhosts_host_exists_global(void **state) +{ + const char *knownhosts_file = *state; + enum ssh_known_hosts_e found; + ssh_session session; + + session = ssh_new(); + assert_non_null(session); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, knownhosts_file); + + /* This makes sure the user's known_hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "/dev/null"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* This makes sure the check will not fail when the user's known_hosts is + * not accessible*/ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "./unaccessible"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* This makes sure the check will fail for an unknown host */ + ssh_options_set(session, SSH_OPTIONS_HOST, "wurstbrot"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_UNKNOWN); + + ssh_free(session); +} + +static void torture_knownhosts_algorithms(void **state) +{ + const char *knownhosts_file = *state; + char *algo_list = NULL; + ssh_session session; + bool process_config = false; + const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa," + "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256" +#ifdef HAVE_DSA + ",ssh-dss" +#endif + ; + const char *expect_fips = "rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp521," + "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256"; + + session = ssh_new(); + assert_non_null(session); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); + /* This makes sure the system's known_hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, "/dev/null"); + + algo_list = ssh_client_select_hostkeys(session); + assert_non_null(algo_list); + if (ssh_fips_mode()) { + assert_string_equal(algo_list, expect_fips); + } else { + assert_string_equal(algo_list, expect); + } + free(algo_list); + + ssh_free(session); +} + +static void torture_knownhosts_algorithms_global(void **state) +{ + const char *knownhosts_file = *state; + char *algo_list = NULL; + ssh_session session; + bool process_config = false; + const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa," + "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256" +#ifdef HAVE_DSA + ",ssh-dss" +#endif + ; + const char *expect_fips = "rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp521," + "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256"; + + session = ssh_new(); + assert_non_null(session); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + /* This makes sure the current-user's known hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "/dev/null"); + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, knownhosts_file); + + algo_list = ssh_client_select_hostkeys(session); + assert_non_null(algo_list); + if (ssh_fips_mode()) { + assert_string_equal(algo_list, expect_fips); + } else { + assert_string_equal(algo_list, expect); + } + free(algo_list); + + ssh_free(session); +} + +#endif /* _WIN32 There is no /dev/null on Windows */ + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_knownhosts_parse_line_rsa), + cmocka_unit_test(torture_knownhosts_parse_line_ecdsa), + cmocka_unit_test(torture_knownhosts_parse_line_default_ed25519), + cmocka_unit_test(torture_knownhosts_parse_line_port_ed25519), + cmocka_unit_test(torture_knownhosts_parse_line_port_wildcard), + cmocka_unit_test(torture_knownhosts_parse_line_standard_port), + cmocka_unit_test(torture_knownhosts_parse_line_pattern_ed25519), + cmocka_unit_test(torture_knownhosts_parse_line_hashed_ed25519), + cmocka_unit_test_setup_teardown(torture_knownhosts_read_file, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_read_file, + setup_knownhosts_file_duplicate, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_get_algorithms_names, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_algorithms_wanted, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test(torture_knownhosts_algorithms_negative), +#ifndef _WIN32 + cmocka_unit_test_setup_teardown(torture_knownhosts_host_exists, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_host_exists_global, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_algorithms, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_algorithms_global, + setup_knownhosts_file, + teardown_knownhosts_file), +#endif + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_list.c b/tests/unittests/torture_list.c new file mode 100644 index 0000000..663c551 --- /dev/null +++ b/tests/unittests/torture_list.c @@ -0,0 +1,131 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "error.c" +#include "misc.c" + +static void torture_ssh_list_new(void **state) { + struct ssh_list *xlist; + + (void) state; + + xlist = ssh_list_new(); + + assert_non_null(xlist); + assert_null(xlist->root); + assert_null(xlist->end); + + assert_int_equal(ssh_list_count(xlist), 0); + + ssh_list_free(xlist); +} + +static void torture_ssh_list_append(void **state) { + struct ssh_list *xlist; + int rc; + + (void) state; + + xlist = ssh_list_new(); + assert_non_null(xlist); + + rc = ssh_list_append(xlist, "item1"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->end->data, "item1"); + + rc = ssh_list_append(xlist, "item2"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->end->data, "item2"); + + rc = ssh_list_append(xlist, "item3"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->root->next); + assert_non_null(xlist->root->next->data); + assert_non_null(xlist->root->next->next); + assert_non_null(xlist->root->next->next->data); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->root->next->data, "item2"); + assert_string_equal((const char *) xlist->root->next->next->data, "item3"); + assert_string_equal((const char *) xlist->end->data, "item3"); + + assert_int_equal(ssh_list_count(xlist), 3); + + ssh_list_free(xlist); +} + +static void torture_ssh_list_prepend(void **state) { + struct ssh_list *xlist; + int rc; + + (void) state; + + xlist = ssh_list_new(); + assert_non_null(xlist); + + rc = ssh_list_prepend(xlist, "item1"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->end->data, "item1"); + + rc = ssh_list_append(xlist, "item2"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->end->data, "item2"); + + rc = ssh_list_prepend(xlist, "item3"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->root->next); + assert_non_null(xlist->root->next->data); + assert_non_null(xlist->root->next->next); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item3"); + assert_string_equal((const char *) xlist->root->next->data, "item1"); + assert_string_equal((const char *) xlist->root->next->next->data, "item2"); + assert_string_equal((const char *) xlist->end->data, "item2"); + + assert_int_equal(ssh_list_count(xlist), 3); + + ssh_list_free(xlist); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_ssh_list_new), + cmocka_unit_test(torture_ssh_list_append), + cmocka_unit_test(torture_ssh_list_prepend), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_misc.c b/tests/unittests/torture_misc.c new file mode 100644 index 0000000..ef06d65 --- /dev/null +++ b/tests/unittests/torture_misc.c @@ -0,0 +1,687 @@ +#include "config.h" + +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#ifndef _WIN32 + +#define _POSIX_PTHREAD_SEMANTICS +#include +#endif + +#define LIBSSH_STATIC +#include + +#include "torture.h" +#include "misc.c" +#include "error.c" + +#define TORTURE_TEST_DIR "/usr/local/bin/truc/much/.." + +const char template[] = "temp_dir_XXXXXX"; + +static int setup(void **state) +{ + ssh_session session = ssh_new(); + *state = session; + + return 0; +} + +static int teardown(void **state) +{ + ssh_free(*state); + + return 0; +} + +static void torture_get_user_home_dir(void **state) { +#ifndef _WIN32 + struct passwd *pwd = getpwuid(getuid()); +#endif /* _WIN32 */ + char *user; + + (void) state; + + user = ssh_get_user_home_dir(); + assert_non_null(user); +#ifndef _WIN32 + assert_string_equal(user, pwd->pw_dir); +#endif /* _WIN32 */ + + SAFE_FREE(user); +} + +static void torture_basename(void **state) { + char *path; + + (void) state; + + path=ssh_basename(TORTURE_TEST_DIR "/test"); + assert_non_null(path); + assert_string_equal(path, "test"); + SAFE_FREE(path); + path=ssh_basename(TORTURE_TEST_DIR "/test/"); + assert_non_null(path); + assert_string_equal(path, "test"); + SAFE_FREE(path); +} + +static void torture_dirname(void **state) { + char *path; + + (void) state; + + path=ssh_dirname(TORTURE_TEST_DIR "/test"); + assert_non_null(path); + assert_string_equal(path, TORTURE_TEST_DIR ); + SAFE_FREE(path); + path=ssh_dirname(TORTURE_TEST_DIR "/test/"); + assert_non_null(path); + assert_string_equal(path, TORTURE_TEST_DIR); + SAFE_FREE(path); +} + +static void torture_ntohll(void **state) { + uint64_t value = 0x0123456789abcdef; + uint32_t sample = 1; + unsigned char *ptr = (unsigned char *) &sample; + uint64_t check; + + (void) state; + + if (ptr[0] == 1){ + /* we're in little endian */ + check = 0xefcdab8967452301; + } else { + /* big endian */ + check = value; + } + value = ntohll(value); + assert_true(value == check); +} + +#ifdef _WIN32 + +static void torture_path_expand_tilde_win(void **state) { + char *d; + + (void) state; + + d = ssh_path_expand_tilde("~\\.ssh"); + assert_non_null(d); + print_message("Expanded path: %s\n", d); + free(d); + + d = ssh_path_expand_tilde("/guru/meditation"); + assert_string_equal(d, "/guru/meditation"); + free(d); +} + +#else /* _WIN32 */ + +static void torture_path_expand_tilde_unix(void **state) { + char h[256]; + char *d; + char *user; + char *home; + + (void) state; + + user = getenv("USER"); + if (user == NULL){ + user = getenv("LOGNAME"); + } + /* in certain CIs there no such variables */ + if (!user){ + struct passwd *pw = getpwuid(getuid()); + if (pw){ + user = pw->pw_name; + } + } + + home = getenv("HOME"); + assert_non_null(home); + snprintf(h, 256 - 1, "%s/.ssh", home); + + d = ssh_path_expand_tilde("~/.ssh"); + assert_non_null(d); + assert_string_equal(d, h); + free(d); + + d = ssh_path_expand_tilde("/guru/meditation"); + assert_non_null(d); + assert_string_equal(d, "/guru/meditation"); + free(d); + + snprintf(h, 256 - 1, "~%s/.ssh", user); + d = ssh_path_expand_tilde(h); + assert_non_null(d); + + snprintf(h, 256 - 1, "%s/.ssh", home); + assert_string_equal(d, h); + free(d); +} + +#endif /* _WIN32 */ + +static void torture_path_expand_escape(void **state) { + ssh_session session = *state; + const char *s = "%d/%h/by/%r"; + char *e; + + session->opts.sshdir = strdup("guru"); + session->opts.host = strdup("meditation"); + session->opts.username = strdup("root"); + + e = ssh_path_expand_escape(session, s); + assert_non_null(e); + assert_string_equal(e, "guru/meditation/by/root"); + free(e); +} + +static void torture_path_expand_known_hosts(void **state) { + ssh_session session = *state; + char *tmp; + + session->opts.sshdir = strdup("/home/guru/.ssh"); + + tmp = ssh_path_expand_escape(session, "%d/known_hosts"); + assert_non_null(tmp); + assert_string_equal(tmp, "/home/guru/.ssh/known_hosts"); + free(tmp); +} + +static void torture_path_expand_percent(void **state) { + ssh_session session = *state; + char *tmp; + + session->opts.sshdir = strdup("/home/guru/.ssh"); + + tmp = ssh_path_expand_escape(session, "%d/config%%1"); + assert_non_null(tmp); + assert_string_equal(tmp, "/home/guru/.ssh/config%1"); + free(tmp); +} + +static void torture_timeout_elapsed(void **state){ + struct ssh_timestamp ts; + (void) state; + ssh_timestamp_init(&ts); + usleep(50000); + assert_true(ssh_timeout_elapsed(&ts,25)); + assert_false(ssh_timeout_elapsed(&ts,30000)); + assert_false(ssh_timeout_elapsed(&ts,75)); + assert_true(ssh_timeout_elapsed(&ts,0)); + assert_false(ssh_timeout_elapsed(&ts,-1)); +} + +static void torture_timeout_update(void **state){ + struct ssh_timestamp ts; + (void) state; + ssh_timestamp_init(&ts); + usleep(50000); + assert_int_equal(ssh_timeout_update(&ts,25), 0); + assert_in_range(ssh_timeout_update(&ts,30000),29000,29960); + assert_in_range(ssh_timeout_update(&ts,75),1,40); + assert_int_equal(ssh_timeout_update(&ts,0),0); + assert_int_equal(ssh_timeout_update(&ts,-1),-1); +} + +static void torture_ssh_analyze_banner(void **state) { + int rc = 0; + ssh_session session = NULL; + (void) state; + +#define reset_banner_test() \ + do { \ + rc = 0; \ + ssh_free(session); \ + session = ssh_new(); \ + assert_non_null(session); \ + } while (0) + +#define assert_banner_rejected(is_server) \ + do { \ + rc = ssh_analyze_banner(session, is_server); \ + assert_int_not_equal(0, rc); \ + } while (0); + +#define assert_client_banner_rejected(banner) \ + do { \ + reset_banner_test(); \ + session->clientbanner = strdup(banner); \ + assert_non_null(session->clientbanner); \ + assert_banner_rejected(1 /*server*/); \ + SAFE_FREE(session->clientbanner); \ + } while (0) + +#define assert_server_banner_rejected(banner) \ + do { \ + reset_banner_test(); \ + session->serverbanner = strdup(banner); \ + assert_non_null(session->serverbanner); \ + assert_banner_rejected(0 /*client*/); \ + SAFE_FREE(session->serverbanner); \ + } while (0) + +#define assert_banner_accepted(is_server) \ + do { \ + rc = ssh_analyze_banner(session, is_server); \ + assert_int_equal(0, rc); \ + } while (0) + +#define assert_client_banner_accepted(banner) \ + do { \ + reset_banner_test(); \ + session->clientbanner = strdup(banner); \ + assert_non_null(session->clientbanner); \ + assert_banner_accepted(1 /*server*/); \ + SAFE_FREE(session->clientbanner); \ + } while (0) + +#define assert_server_banner_accepted(banner) \ + do { \ + reset_banner_test(); \ + session->serverbanner = strdup(banner); \ + assert_non_null(session->serverbanner); \ + assert_banner_accepted(0 /*client*/); \ + SAFE_FREE(session->serverbanner); \ + } while (0) + + /* no banner is set */ + reset_banner_test(); + assert_banner_rejected(0 /*client*/); + reset_banner_test(); + assert_banner_rejected(1 /*server*/); + + /* banner is too short */ + assert_client_banner_rejected("abc"); + assert_server_banner_rejected("abc"); + + /* banner doesn't start "SSH-" */ + assert_client_banner_rejected("abc-2.0"); + assert_server_banner_rejected("abc-2.0"); + + /* SSH v1 */ + assert_client_banner_rejected("SSH-1.0"); + assert_server_banner_rejected("SSH-1.0"); + + /* SSH v1.9 gets counted as both v1 and v2 */ + assert_client_banner_accepted("SSH-1.9"); + assert_server_banner_accepted("SSH-1.9"); + + /* SSH v2 */ + assert_client_banner_accepted("SSH-2.0"); + assert_server_banner_accepted("SSH-2.0"); + + /* OpenSSH banners: too short to extract major and minor versions */ + assert_client_banner_accepted("SSH-2.0-OpenSSH"); + assert_int_equal(0, session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH"); + assert_int_equal(0, session->openssh); + + /* OpenSSH banners: big enough to extract major and minor versions */ + assert_client_banner_accepted("SSH-2.0-OpenSSH_5.9p1"); + assert_int_equal(SSH_VERSION_INT(5, 9, 0), session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH_5.9p1"); + assert_int_equal(SSH_VERSION_INT(5, 9, 0), session->openssh); + + assert_client_banner_accepted("SSH-2.0-OpenSSH_1.99"); + assert_int_equal(SSH_VERSION_INT(1, 99, 0), session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH_1.99"); + assert_int_equal(SSH_VERSION_INT(1, 99, 0), session->openssh); + + /* OpenSSH banners: major, minor version limits result in zero */ + assert_client_banner_accepted("SSH-2.0-OpenSSH_0.99p1"); + assert_int_equal(0, session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH_0.99p1"); + assert_int_equal(0, session->openssh); + assert_client_banner_accepted("SSH-2.0-OpenSSH_1.101p1"); + assert_int_equal(0, session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH_1.101p1"); + assert_int_equal(0, session->openssh); + + /* OpenSSH banners: bogus major results in zero */ + assert_client_banner_accepted("SSH-2.0-OpenSSH_X.9p1"); + assert_int_equal(0, session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH_X.9p1"); + assert_int_equal(0, session->openssh); + + /* OpenSSH banners: bogus minor results in zero */ + assert_server_banner_accepted("SSH-2.0-OpenSSH_5.Yp1"); + assert_int_equal(0, session->openssh); + assert_client_banner_accepted("SSH-2.0-OpenSSH_5.Yp1"); + assert_int_equal(0, session->openssh); + + /* OpenSSH banners: ssh-keyscan(1) */ + assert_client_banner_accepted("SSH-2.0-OpenSSH-keyscan"); + assert_int_equal(0, session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH-keyscan"); + assert_int_equal(0, session->openssh); + + ssh_free(session); +} + +static void torture_ssh_dir_writeable(UNUSED_PARAM(void **state)) +{ + char *tmp_dir = NULL; + int rc = 0; + FILE *file = NULL; + char buffer[256]; + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + rc = ssh_dir_writeable(tmp_dir); + assert_int_equal(rc, 1); + + /* Create a file */ + snprintf(buffer, sizeof(buffer), "%s/a", tmp_dir); + + file = fopen(buffer, "w"); + assert_non_null(file); + + fprintf(file, "Hello world!\n"); + fclose(file); + + /* Negative test for checking a normal file */ + rc = ssh_dir_writeable(buffer); + assert_int_equal(rc, 0); + + /* Negative test for non existent file */ + snprintf(buffer, sizeof(buffer), "%s/b", tmp_dir); + rc = ssh_dir_writeable(buffer); + assert_int_equal(rc, 0); + +#ifndef _WIN32 + /* Negative test for directory without write permission */ + rc = ssh_mkdir(buffer, 0400); + assert_return_code(rc, errno); + + rc = ssh_dir_writeable(buffer); + assert_int_equal(rc, 0); +#endif + + torture_rmdirs(tmp_dir); + + SAFE_FREE(tmp_dir); +} + +static void torture_ssh_mkdirs(UNUSED_PARAM(void **state)) +{ + char *tmp_dir = NULL; + char *cwd = NULL; + char buffer[256]; + + ssize_t count = 0; + + int rc; + + /* Get current working directory */ + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + /* Create a base disposable directory */ + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + /* Create a single directory */ + count = snprintf(buffer, sizeof(buffer), "%s/a", tmp_dir); + assert_return_code(count, errno); + + rc = ssh_mkdirs(buffer, 0700); + assert_return_code(rc, errno); + + rc = ssh_dir_writeable(buffer); + assert_int_equal(rc, 1); + + /* Create directories recursively */ + count = snprintf(buffer, sizeof(buffer), "%s/b/c/d", tmp_dir); + assert_return_code(count, errno); + + rc = ssh_mkdirs(buffer, 0700); + assert_return_code(rc, errno); + + rc = ssh_dir_writeable(buffer); + assert_int_equal(rc, 1); + + /* Change directory */ + rc = torture_change_dir(tmp_dir); + assert_return_code(rc, errno); + + /* Create single local directory */ + rc = ssh_mkdirs("e", 0700); + assert_return_code(rc, errno); + + rc = ssh_dir_writeable("e"); + assert_int_equal(rc, 1); + + /* Create local directories recursively */ + rc = ssh_mkdirs("f/g/h", 0700); + assert_return_code(rc, errno); + + rc = ssh_dir_writeable("f/g/h"); + assert_int_equal(rc, 1); + + /* Negative test for creating "." directory */ + rc = ssh_mkdirs(".", 0700); + assert_int_equal(rc, -1); + assert_int_equal(errno, EINVAL); + + /* Negative test for creating "/" directory */ + rc = ssh_mkdirs("/", 0700); + assert_int_equal(rc, -1); + assert_int_equal(errno, EINVAL); + + /* Negative test for creating "" directory */ + rc = ssh_mkdirs("", 0700); + assert_int_equal(rc, -1); + assert_int_equal(errno, EINVAL); + + /* Negative test for creating NULL directory */ + rc = ssh_mkdirs(NULL, 0700); + assert_int_equal(rc, -1); + assert_int_equal(errno, EINVAL); + + /* Negative test for creating existing directory */ + rc = ssh_mkdirs("a", 0700); + assert_int_equal(rc, -1); + assert_int_equal(errno, EEXIST); + + /* Return to original directory */ + rc = torture_change_dir(cwd); + assert_return_code(rc, errno); + + /* Cleanup */ + torture_rmdirs(tmp_dir); + + SAFE_FREE(tmp_dir); + SAFE_FREE(cwd); +} + +static void torture_ssh_quote_file_name(UNUSED_PARAM(void **state)) +{ + char buffer[2048]; + int rc; + + /* Only ordinary chars */ + rc = ssh_quote_file_name("a b", buffer, 2048); + assert_int_equal(rc, 5); + assert_string_equal(buffer, "'a b'"); + + /* Single quote in file name */ + rc = ssh_quote_file_name("a'b", buffer, 2048); + assert_int_equal(rc, 9); + assert_string_equal(buffer, "'a'\"'\"'b'"); + + /* Exclamation in file name */ + rc = ssh_quote_file_name("a!b", buffer, 2048); + assert_int_equal(rc, 8); + assert_string_equal(buffer, "'a'\\!'b'"); + + /* All together */ + rc = ssh_quote_file_name("'a!b'", buffer, 2048); + assert_int_equal(rc, 14); + assert_string_equal(buffer, "\"'\"'a'\\!'b'\"'\""); + + rc = ssh_quote_file_name("a'!b", buffer, 2048); + assert_int_equal(rc, 11); + assert_string_equal(buffer, "'a'\"'\"\\!'b'"); + + rc = ssh_quote_file_name("a'$b", buffer, 2048); + assert_int_equal(rc, 10); + assert_string_equal(buffer, "'a'\"'\"'$b'"); + + rc = ssh_quote_file_name("a'`b", buffer, 2048); + assert_int_equal(rc, 10); + assert_string_equal(buffer, "'a'\"'\"'`b'"); + + + rc = ssh_quote_file_name(" ", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "' '"); + + rc = ssh_quote_file_name(" ", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "' '"); + + + rc = ssh_quote_file_name("\r", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "'\r'"); + + rc = ssh_quote_file_name("\n", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "'\n'"); + + rc = ssh_quote_file_name("\r\n", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "'\r\n'"); + + + rc = ssh_quote_file_name("\\r", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "'\\r'"); + + rc = ssh_quote_file_name("\\n", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "'\\n'"); + + rc = ssh_quote_file_name("\\r\\n", buffer, 2048); + assert_int_equal(rc, 6); + assert_string_equal(buffer, "'\\r\\n'"); + + + rc = ssh_quote_file_name("\t", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "'\t'"); + + rc = ssh_quote_file_name("\v", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "'\v'"); + + rc = ssh_quote_file_name("\t\v", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "'\t\v'"); + + + rc = ssh_quote_file_name("'", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "\"'\""); + + rc = ssh_quote_file_name("''", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "\"''\""); + + + rc = ssh_quote_file_name("\"", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "'\"'"); + + rc = ssh_quote_file_name("\"\"", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "'\"\"'"); + + rc = ssh_quote_file_name("'\"", buffer, 2048); + assert_int_equal(rc, 6); + assert_string_equal(buffer, "\"'\"'\"'"); + + rc = ssh_quote_file_name("\"'", buffer, 2048); + assert_int_equal(rc, 6); + assert_string_equal(buffer, "'\"'\"'\""); + + + /* Worst case */ + rc = ssh_quote_file_name("a'b'", buffer, 3 * 4 + 1); + assert_int_equal(rc, 12); + assert_string_equal(buffer, "'a'\"'\"'b'\"'\""); + + /* Negative tests */ + + /* NULL params */ + rc = ssh_quote_file_name(NULL, buffer, 3 * 4 + 1); + assert_int_equal(rc, SSH_ERROR); + + /* NULL params */ + rc = ssh_quote_file_name("a b", NULL, 3 * 4 + 1); + assert_int_equal(rc, SSH_ERROR); + + /* Small buffer size */ + rc = ssh_quote_file_name("a b", buffer, 0); + assert_int_equal(rc, SSH_ERROR); + + /* Worst case and small buffer size */ + rc = ssh_quote_file_name("a'b'", buffer, 3 * 4); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_ssh_newline_vis(UNUSED_PARAM(void **state)) +{ + int rc; + char buffer[1024]; + + rc = ssh_newline_vis("\n", buffer, 1024); + assert_int_equal(rc, 2); + assert_string_equal(buffer, "\\n"); + + rc = ssh_newline_vis("\n\n\n\n", buffer, 1024); + assert_int_equal(rc, 8); + assert_string_equal(buffer, "\\n\\n\\n\\n"); + + rc = ssh_newline_vis("a\nb\n", buffer, 1024); + assert_int_equal(rc, 6); + assert_string_equal(buffer, "a\\nb\\n"); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_get_user_home_dir), + cmocka_unit_test(torture_basename), + cmocka_unit_test(torture_dirname), + cmocka_unit_test(torture_ntohll), +#ifdef _WIN32 + cmocka_unit_test(torture_path_expand_tilde_win), +#else + cmocka_unit_test(torture_path_expand_tilde_unix), +#endif + cmocka_unit_test_setup_teardown(torture_path_expand_escape, setup, teardown), + cmocka_unit_test_setup_teardown(torture_path_expand_known_hosts, setup, teardown), + cmocka_unit_test_setup_teardown(torture_path_expand_percent, setup, teardown), + cmocka_unit_test(torture_timeout_elapsed), + cmocka_unit_test(torture_timeout_update), + cmocka_unit_test(torture_ssh_analyze_banner), + cmocka_unit_test(torture_ssh_dir_writeable), + cmocka_unit_test(torture_ssh_newline_vis), + cmocka_unit_test(torture_ssh_mkdirs), + cmocka_unit_test(torture_ssh_quote_file_name), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_moduli.c b/tests/unittests/torture_moduli.c new file mode 100644 index 0000000..89f76cc --- /dev/null +++ b/tests/unittests/torture_moduli.c @@ -0,0 +1,114 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "dh-gex.c" + +static const char moduli_content[] = + "# Time Type Tests Tries Size Generator Modulus\n" + "20120821044040 2 6 100 1023 5 D9277DAA27DB131C03B108D41A76B4DA8ACEECCCAE73" + "D2E48CEDAAA70B09EF9F04FB020DCF36C51B8E485B26FABE0337E24232BE4F4E6935483102" + "44937433FB1A5758195DC73B84ADEF8237472C46747D79DC0A2CF8A57CE8DBD8F466A20F85" + "51E7B1B824B2E4987A8816D9BC0741C2798F3EBAD3ADEBCC78FCE6A770E2EC9F\n" + "20120821044502 2 6 100 1535 5 D1391174233D315398FE2830AC6B2B66BCCD01B0A634" + "899F339B7879F1DB85712E9DC4E4B1C6C8355570C1D2DCB53493DF18175A9C53D1128B592B" + "4C72D97136F5542FEB981CBFE8012FDD30361F288A42BD5EBB08BAB0A5640E1AC48763B2AB" + "D1945FEE36B2D55E1D50A1C86CED9DD141C4E7BE2D32D9B562A0F8E2E927020E91F58B57EB" + "9ACDDA106A59302D7E92AD5F6E851A45FA1CFE86029A0F727F65A8F475F33572E2FDAB6073" + "F0C21B8B54C3823DB2EF068927E5D747498F96361507\n" + "20120821050636 2 6 100 2047 2 DD2047CBDBB6F8E919BC63DE885B34D0FD6E3DB2887D" + "8B46FE249886ACED6B46DFCD5553168185FD376122171CD8927E60120FA8D01F01D03E5828" + "1FEA9A1ABE97631C828E41815F34FDCDF787419FE13A3137649AA93D2584230DF5F24B5C00" + "C88B7D7DE4367693428C730376F218A53E853B0851BAB7C53C15DA7839CBE1285DB63F6FA4" + "5C1BB59FE1C5BB918F0F8459D7EF60ACFF5C0FA0F3FCAD1C5F4CE4416D4F4B36B05CDCEBE4" + "FB879E95847EFBC6449CD190248843BC7EDB145FBFC4EDBB1A3C959298F08F3BA2CFBE231B" + "BE204BE6F906209D28BD4820AB3E7BE96C26AE8A809ADD8D1A5A0B008E9570FA4C4697E116" + "B8119892C604293683DF582B\n" + "20120821053137 2 6 100 3071 5 DFAA35D35531E0F524F0099877A482D2AC8D589F3743" + "94A262A8E81A8A4FB2F65FADBAB395E05D147B29D486DFAA41F41597A256DA82A8B6F76401" + "AED53D0253F956CEC610D417E42E3B287F7938FC24D8821B40BFA218A956EB7401BED6C96C" + "68C7FD64F8170A8A76B953DD2F05420118F6B144D8FE48060A2BCB85056B478EDEF96DBC70" + "427053ECD2958C074169E9550DD877779A3CF17C5AC850598C7586BEEA9DCFE9DD2A5FB62D" + "F5F33EA7BC00CDA31B9D2DD721F979EA85B6E63F0C4E30BDDCD3A335522F9004C4ED50B15D" + "C537F55324DD4FA119FB3F101467C6D7E1699DE4B3E3C478A8679B8EB3FA5C9B826B44530F" + "D3BE9AD3063B240B0C853EBDDBD68DD940332D98F148D5D9E1DC977D60A0D23D0CA1198637" + "FEAE4E7FAAC173AF2B84313A666CFB4EE6972811921D0AD867CE57F3BBC8D6CB057E3B6675" + "7BB46C9F72662624D44E14528327E3A7100E81A12C43C4E236118318CD90C8AA185BBB0C76" + "4826DAEAEE8DD245C5B451B4944E6122CC522D1C335C2EEF942284EA9F\n"; + +const char modulus_2048[] = + "DD2047CBDBB6F8E919BC63DE885B34D0FD6E3DB2887D" + "8B46FE249886ACED6B46DFCD5553168185FD376122171CD8927E60120FA8D01F01D03E5828" + "1FEA9A1ABE97631C828E41815F34FDCDF787419FE13A3137649AA93D2584230DF5F24B5C00" + "C88B7D7DE4367693428C730376F218A53E853B0851BAB7C53C15DA7839CBE1285DB63F6FA4" + "5C1BB59FE1C5BB918F0F8459D7EF60ACFF5C0FA0F3FCAD1C5F4CE4416D4F4B36B05CDCEBE4" + "FB879E95847EFBC6449CD190248843BC7EDB145FBFC4EDBB1A3C959298F08F3BA2CFBE231B" + "BE204BE6F906209D28BD4820AB3E7BE96C26AE8A809ADD8D1A5A0B008E9570FA4C4697E116" + "B8119892C604293683DF582B"; + +/* test if the best dhgroup size is chosen out of lists */ +static void torture_dhgroup_better_size(UNUSED_PARAM(void **state)) +{ + /* series of group sizes, as they are read in the file, along with expected + * value at every moment. */ + size_t groups[][5][2] = { + {{1024,1024}, {2048, 1024}, {1500, 1024}, {1023,1024}, {512, 1024}}, + {{512, 512}, {1023, 1023}, {1025, 1025}, {1500, 1025}, {2000, 1025}}, + {{512, 512}, {2049, 512}, {768, 768}, {4096, 768}, {1024, 1024}} + }; + size_t i, j, best; + + for (i = 0; i < 3; ++i) { + best = 0; + for (j = 0; j < 5; ++j) { + bool ok; + + ok = dhgroup_better_size(512, 1024, 2048, best, groups[i][j][0]); + if (ok) { + best = groups[i][j][0]; + assert_int_equal(best, groups[i][j][1]); + } + assert_int_equal(best, groups[i][j][1]); + } + } +} + +static void torture_retrieve_dhgroup_file(UNUSED_PARAM(void **state)) +{ + FILE *moduli = tmpfile(); + size_t size = 0; + char *generator = NULL, *modulus = NULL; + int rc; + + fwrite(moduli_content, 1, sizeof(moduli_content), moduli); + rewind(moduli); + rc = ssh_retrieve_dhgroup_file(moduli, + 1024, + 2048, + 4096, + &size, + &generator, + &modulus); + assert_int_equal(rc, SSH_OK); + assert_int_equal(size, 2048); + assert_string_equal(modulus, modulus_2048); + + SAFE_FREE(generator); + SAFE_FREE(modulus); + fclose(moduli); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_dhgroup_better_size), + cmocka_unit_test(torture_retrieve_dhgroup_file) + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c new file mode 100644 index 0000000..d0fdaed --- /dev/null +++ b/tests/unittests/torture_options.c @@ -0,0 +1,1701 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#ifndef _WIN32 +#define _POSIX_PTHREAD_SEMANTICS +# include +#endif + +#include "torture.h" +#include "torture_key.h" +#include +#include +#include +#include +#ifdef WITH_SERVER +#include +#define LIBSSH_CUSTOM_BIND_CONFIG_FILE "my_bind_config" +#endif +#ifdef HAVE_DSA +#define LIBSSH_DSA_TESTKEY "libssh_testkey.id_dsa" +#endif +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_ED25519_TESTKEY "libssh_testkey.id_ed25519" +#ifdef HAVE_ECC +#define LIBSSH_ECDSA_521_TESTKEY "libssh_testkey.id_ecdsa521" +#endif + +static int setup(void **state) +{ + ssh_session session; + int verbosity; + + session = ssh_new(); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + session->client = 1; + + *state = session; + + return 0; +} + +static int teardown(void **state) +{ + ssh_free(*state); + + return 0; +} + +static void torture_options_set_host(void **state) { + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "localhost"); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "guru@meditation"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "meditation"); + assert_non_null(session->opts.username); + assert_string_equal(session->opts.username, "guru"); +} + +static void torture_options_set_ciphers(void **state) { + ssh_session session = *state; + int rc; + + /* Test known ciphers */ + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, + "aes128-ctr,aes192-ctr,aes256-ctr"); + assert_true(rc == 0); + assert_non_null(session->opts.wanted_methods[SSH_CRYPT_C_S]); + if (ssh_fips_mode()) { + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes256-ctr"); + } else { + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes192-ctr,aes256-ctr"); + } + + /* Test one unknown cipher */ + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, + "aes128-ctr,unknown-crap@example.com,aes256-ctr"); + assert_true(rc == 0); + assert_non_null(session->opts.wanted_methods[SSH_CRYPT_C_S]); + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes256-ctr"); + + /* Test all unknown ciphers */ + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, + "unknown-crap@example.com,more-crap@example.com"); + assert_false(rc == 0); +} + +static void torture_options_set_key_exchange(void **state) +{ + ssh_session session = *state; + int rc; + + /* Test known kexes */ + rc = ssh_options_set(session, + SSH_OPTIONS_KEY_EXCHANGE, + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + assert_true(rc == 0); + assert_non_null(session->opts.wanted_methods[SSH_KEX]); + if (ssh_fips_mode()) { + assert_string_equal(session->opts.wanted_methods[SSH_KEX], + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256"); + } else { + assert_string_equal(session->opts.wanted_methods[SSH_KEX], + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + } + + /* Test one unknown kex */ + rc = ssh_options_set(session, + SSH_OPTIONS_KEY_EXCHANGE, + "diffie-hellman-group16-sha512," + "unknown-crap@example.com," + "diffie-hellman-group18-sha512"); + assert_true(rc == 0); + assert_non_null(session->opts.wanted_methods[SSH_KEX]); + assert_string_equal(session->opts.wanted_methods[SSH_KEX], + "diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512"); + + /* Test all unknown kexes */ + rc = ssh_options_set(session, + SSH_OPTIONS_KEY_EXCHANGE, + "unknown-crap@example.com,more-crap@example.com"); + assert_false(rc == 0); +} + +static void torture_options_set_hostkey(void **state) { + ssh_session session = *state; + int rc; + + /* Test known host keys */ + rc = ssh_options_set(session, + SSH_OPTIONS_HOSTKEYS, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_true(rc == 0); + assert_non_null(session->opts.wanted_methods[SSH_HOSTKEYS]); + if (ssh_fips_mode()) { + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + + /* Test one unknown host key */ + rc = ssh_options_set(session, + SSH_OPTIONS_HOSTKEYS, + "ecdsa-sha2-nistp521," + "unknown-crap@example.com," + "rsa-sha2-256"); + assert_true(rc == 0); + assert_non_null(session->opts.wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp521," + "rsa-sha2-256"); + + /* Test all unknown host keys */ + rc = ssh_options_set(session, + SSH_OPTIONS_HOSTKEYS, + "unknown-crap@example.com,more-crap@example.com"); + assert_false(rc == 0); +} + +static void torture_options_set_pubkey_accepted_types(void **state) { + ssh_session session = *state; + int rc; + enum ssh_digest_e type; + + /* Test known public key algorithms */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_true(rc == 0); + assert_non_null(session->opts.pubkey_accepted_types); + if (ssh_fips_mode()) { + assert_string_equal(session->opts.pubkey_accepted_types, + "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(session->opts.pubkey_accepted_types, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + + if (!ssh_fips_mode()) { + /* Test one unknown public key algorithms */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-ed25519,unknown-crap@example.com,ssh-rsa"); + assert_true(rc == 0); + assert_non_null(session->opts.pubkey_accepted_types); + assert_string_equal(session->opts.pubkey_accepted_types, + "ssh-ed25519,ssh-rsa"); + + /* Test all unknown public key algorithms */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "unknown-crap@example.com,more-crap@example.com"); + assert_false(rc == 0); + + /* Test that the option affects the algorithm selection for RSA keys */ + /* simulate the SHA2 extension was negotiated */ + session->extensions = SSH_EXT_SIG_RSA_SHA256; + + /* previous configuration did not list the SHA2 extension algoritms, so + * it should not be used */ + type = ssh_key_type_to_hash(session, SSH_KEYTYPE_RSA); + assert_int_equal(type, SSH_DIGEST_SHA1); + } + + /* now, lets allow the signature from SHA2 extension and expect + * it to be used */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "rsa-sha2-256,ssh-rsa"); + assert_true(rc == 0); + assert_non_null(session->opts.pubkey_accepted_types); + if (ssh_fips_mode()) { + assert_string_equal(session->opts.pubkey_accepted_types, + "rsa-sha2-256"); + } else { + assert_string_equal(session->opts.pubkey_accepted_types, + "rsa-sha2-256,ssh-rsa"); + } + + /* Test that the option affects the algorithm selection for RSA keys */ + /* simulate the SHA2 extension was negotiated */ + session->extensions = SSH_EXT_SIG_RSA_SHA256; + + type = ssh_key_type_to_hash(session, SSH_KEYTYPE_RSA); + assert_int_equal(type, SSH_DIGEST_SHA256); +} + +static void torture_options_set_macs(void **state) { + ssh_session session = *state; + int rc; + + /* Test known MACs */ + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "hmac-sha1"); + assert_true(rc == 0); + assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], "hmac-sha1"); + + /* Test multiple known MACs */ + rc = ssh_options_set(session, + SSH_OPTIONS_HMAC_S_C, + "hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha1,hmac-sha2-256"); + assert_true(rc == 0); + assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], + "hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha1,hmac-sha2-256"); + + /* Test unknown MACs */ + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "unknown-crap@example.com,hmac-sha1-etm@openssh.com,unknown@example.com"); + assert_true(rc == 0); + assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], "hmac-sha1-etm@openssh.com"); + + /* Test all unknown MACs */ + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "unknown-crap@example.com"); + assert_false(rc == 0); +} + +static void torture_options_get_host(void **state) { + ssh_session session = *state; + int rc; + char* host = NULL; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + assert_true(rc == 0); + assert_string_equal(session->opts.host, "localhost"); + + assert_false(ssh_options_get(session, SSH_OPTIONS_HOST, &host)); + + assert_string_equal(host, "localhost"); + free(host); +} + +static void torture_options_set_port(void **state) { + ssh_session session = *state; + int rc; + unsigned int port = 42; + + rc = ssh_options_set(session, SSH_OPTIONS_PORT, &port); + assert_true(rc == 0); + assert_true(session->opts.port == port); + + rc = ssh_options_set(session, SSH_OPTIONS_PORT_STR, "23"); + assert_true(rc == 0); + assert_true(session->opts.port == 23); + + rc = ssh_options_set(session, SSH_OPTIONS_PORT_STR, "five"); + assert_true(rc == -1); + + rc = ssh_options_set(session, SSH_OPTIONS_PORT, NULL); + assert_true(rc == -1); +} + +static void torture_options_get_port(void **state) { + ssh_session session = *state; + unsigned int given_port = 1234; + unsigned int port_container; + int rc; + rc = ssh_options_set(session, SSH_OPTIONS_PORT, &given_port); + assert_true(rc == 0); + rc = ssh_options_get_port(session, &port_container); + assert_true(rc == 0); + assert_int_equal(port_container, 1234); +} + +static void torture_options_get_user(void **state) { + ssh_session session = *state; + char* user = NULL; + int rc; + rc = ssh_options_set(session, SSH_OPTIONS_USER, "magicaltrevor"); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_get(session, SSH_OPTIONS_USER, &user); + assert_int_equal(rc, SSH_OK); + assert_non_null(user); + assert_string_equal(user, "magicaltrevor"); + free(user); +} + +static void torture_options_set_fd(void **state) { + ssh_session session = *state; + socket_t fd = 42; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_FD, &fd); + assert_true(rc == 0); + assert_true(session->opts.fd == fd); + + rc = ssh_options_set(session, SSH_OPTIONS_FD, NULL); + assert_true(rc == SSH_ERROR); + assert_true(session->opts.fd == SSH_INVALID_SOCKET); +} + +static void torture_options_set_user(void **state) { + ssh_session session = *state; + int rc; +#ifndef _WIN32 +# ifndef NSS_BUFLEN_PASSWD +# define NSS_BUFLEN_PASSWD 4096 +# endif /* NSS_BUFLEN_PASSWD */ + struct passwd pwd; + struct passwd *pwdbuf; + char buf[NSS_BUFLEN_PASSWD]; + + /* get local username */ + rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf); + assert_true(rc == 0); +#endif /* _WIN32 */ + + rc = ssh_options_set(session, SSH_OPTIONS_USER, "guru"); + assert_true(rc == 0); + assert_string_equal(session->opts.username, "guru"); + + + rc = ssh_options_set(session, SSH_OPTIONS_USER, NULL); + assert_true(rc == 0); + +#ifndef _WIN32 + assert_string_equal(session->opts.username, pwd.pw_name); +#endif +} + +/* TODO */ +#if 0 +static voidtorture_options_set_sshdir) +{ +} +END_TEST +#endif + +static void torture_options_set_identity(void **state) { + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, "identity1"); + assert_true(rc == 0); + assert_string_equal(session->opts.identity->root->data, "identity1"); + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "identity2"); + assert_true(rc == 0); + assert_string_equal(session->opts.identity->root->data, "identity2"); + assert_string_equal(session->opts.identity->root->next->data, "identity1"); +} + +static void torture_options_get_identity(void **state) { + ssh_session session = *state; + char *identity = NULL; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, "identity1"); + assert_true(rc == 0); + rc = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_non_null(identity); + assert_string_equal(identity, "identity1"); + SAFE_FREE(identity); + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "identity2"); + assert_int_equal(rc, SSH_OK); + assert_string_equal(session->opts.identity->root->data, "identity2"); + rc = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_non_null(identity); + assert_string_equal(identity, "identity2"); + free(identity); +} + +static void torture_options_set_global_knownhosts(void **state) +{ + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + "/etc/libssh/known_hosts"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.global_knownhosts, + "/etc/libssh/known_hosts"); +} + +static void torture_options_get_global_knownhosts(void **state) +{ + ssh_session session = *state; + char *str = NULL; + int rc; + + rc = ssh_options_set(session, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + "/etc/libssh/known_hosts"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.global_knownhosts, + "/etc/libssh/known_hosts"); + + + rc = ssh_options_get(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, &str); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.global_knownhosts, + "/etc/libssh/known_hosts"); + + SSH_STRING_FREE_CHAR(str); +} + +static void torture_options_set_knownhosts(void **state) +{ + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, + SSH_OPTIONS_KNOWNHOSTS, + "/home/libssh/.ssh/known_hosts"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.knownhosts, + "/home/libssh/.ssh/known_hosts"); + + /* The NULL value should not crash the libssh */ + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, NULL); + assert_ssh_return_code(session, rc); + assert_null(session->opts.knownhosts); + + /* ssh_options_apply() should set the path to correct value */ + rc = ssh_options_apply(session); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.knownhosts); +} + +static void torture_options_get_knownhosts(void **state) +{ + ssh_session session = *state; + char *str = NULL; + int rc; + + rc = ssh_options_set(session, + SSH_OPTIONS_KNOWNHOSTS, + "/home/libssh/.ssh/known_hosts"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.knownhosts, + "/home/libssh/.ssh/known_hosts"); + + + rc = ssh_options_get(session, SSH_OPTIONS_KNOWNHOSTS, &str); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.knownhosts, + "/home/libssh/.ssh/known_hosts"); + + SSH_STRING_FREE_CHAR(str); +} + +static void torture_options_proxycommand(void **state) { + ssh_session session = *state; + int rc; + + /* Enable ProxyCommand */ + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, "ssh -q -A -X -W %h:%p JUMPHOST"); + assert_int_equal(rc, 0); + + assert_string_equal(session->opts.ProxyCommand, "ssh -q -A -X -W %h:%p JUMPHOST"); + + /* Disable ProxyCommand */ + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, "none"); + assert_int_equal(rc, 0); + + assert_null(session->opts.ProxyCommand); +} + +static void torture_options_config_host(void **state) { + ssh_session session = *state; + FILE *config = NULL; + + /* create a new config file */ + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Host testhost1\nPort 42\n" + "Host testhost2,testhost3\nPort 43\n" + "Host testhost4 testhost5\nPort 44\n", + config); + fclose(config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost1"); + ssh_options_parse_config(session, "test_config"); + + assert_int_equal(session->opts.port, 42); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost2"); + ssh_options_parse_config(session, "test_config"); + assert_int_equal(session->opts.port, 43); + + session->opts.port = 0; + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost3"); + ssh_options_parse_config(session, "test_config"); + assert_int_equal(session->opts.port, 43); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost4"); + ssh_options_parse_config(session, "test_config"); + assert_int_equal(session->opts.port, 44); + + session->opts.port = 0; + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost5"); + ssh_options_parse_config(session, "test_config"); + assert_int_equal(session->opts.port, 44); + + unlink("test_config"); +} + +static void torture_options_config_match(void **state) +{ + ssh_session session = *state; + char *localuser = NULL; + FILE *config = NULL; + int rv; + + /* Required for options_parse_config() */ + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost1"); + + /* The Match keyword requires argument */ + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_ERROR); + + /* The Match all keyword needs to be the only one (start) */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match all host local\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_ERROR); + + /* The Match all keyword needs to be the only one (end) */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match host local all\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_ERROR); + + /* The Match host keyword requires an argument */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match host\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_ERROR); + + /* The Match user keyword requires an argument */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match user\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_ERROR); + + /* The Match canonical keyword is the same as match all */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match canonical\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_OK); + assert_int_equal(session->opts.port, 33); + + session->opts.port = 0; + + /* The Match originalhost keyword is ignored */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match originalhost origin\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); + assert_int_equal(session->opts.port, 34); + + session->opts.port = 0; + + /* The Match localuser keyword */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match localuser ", config); + localuser = ssh_get_local_username(); + assert_non_null(localuser); + fputs(localuser, config); + free(localuser); + fputs("\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); + assert_int_equal(session->opts.port, 33); + + session->opts.port = 0; + + /* The Match exec keyword is ignored */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match exec /bin/true\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); + assert_int_equal(session->opts.port, 34); + + session->opts.port = 0; + + /* The Match exec keyword can accept more arguments */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match exec /bin/true 1 \n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); + assert_int_equal(session->opts.port, 34); + + session->opts.port = 0; + + /* Commands containing whitespace characters must be quoted. */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match exec \"/bin/true 1\"\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); + assert_int_equal(session->opts.port, 34); + + session->opts.port = 0; + + unlink("test_config"); +} + +static void torture_options_copy(void **state) +{ + ssh_session session = *state, new = NULL; + struct ssh_iterator *it = NULL, *it2 = NULL; + FILE *config = NULL; + int i, level = 9; + int rv; + + /* Required for options_parse_config() */ + ssh_options_set(session, SSH_OPTIONS_HOST, "example"); + + /* Impossible to set through the configuration */ + rv = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_LEVEL, &level); + assert_ssh_return_code(session, rv); + level = 1; + rv = ssh_options_set(session, SSH_OPTIONS_NODELAY, &level); + assert_ssh_return_code(session, rv); + + /* The Match keyword requires argument */ + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("IdentityFile ~/.ssh/id_ecdsa\n" + "User tester\n" + "Hostname example.com\n" + "BindAddress 127.0.0.2\n" + "GlobalKnownHostsFile /etc/ssh/known_hosts2\n" + "UserKnownHostsFile ~/.ssh/known_hosts2\n" + "KexAlgorithms curve25519-sha256,ecdh-sha2-nistp521\n" + "Ciphers aes256-ctr\n" + "MACs hmac-sha2-256\n" + "HostKeyAlgorithms ssh-ed25519,ecdsa-sha2-nistp521\n" + "Compression yes\n" + "PubkeyAcceptedTypes ssh-ed25519,ecdsa-sha2-nistp521\n" + "ProxyCommand nc 127.0.0.10 22\n" + /* ops.custombanner */ + "ConnectTimeout 42\n" + "Port 222\n" + "StrictHostKeyChecking no\n" + "GSSAPIServerIdentity my.example.com\n" + "GSSAPIClientIdentity home.sweet\n" + "GSSAPIDelegateCredentials yes\n" + "PubkeyAuthentication yes\n" /* sets flags */ + "GSSAPIAuthentication no\n" /* sets flags */ + "", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); + + rv = ssh_options_copy(session, &new); + assert_ssh_return_code(session, rv); + assert_non_null(new); + + /* Check the identities match */ + it = ssh_list_get_iterator(session->opts.identity); + assert_non_null(it); + it2 = ssh_list_get_iterator(new->opts.identity); + assert_non_null(it2); + while (it != NULL && it2 != NULL) { + assert_string_equal(it->data, it2->data); + it = it->next; + it2 = it2->next; + } + assert_null(it); + assert_null(it2); + + assert_string_equal(session->opts.username, new->opts.username); + assert_string_equal(session->opts.host, new->opts.host); + assert_string_equal(session->opts.bindaddr, new->opts.bindaddr); + assert_string_equal(session->opts.sshdir, new->opts.sshdir); + assert_string_equal(session->opts.knownhosts, new->opts.knownhosts); + assert_string_equal(session->opts.global_knownhosts, + new->opts.global_knownhosts); + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (session->opts.wanted_methods[i] == NULL) { + assert_null(new->opts.wanted_methods[i]); + } else { + assert_string_equal(session->opts.wanted_methods[i], + new->opts.wanted_methods[i]); + } + } + assert_string_equal(session->opts.pubkey_accepted_types, + new->opts.pubkey_accepted_types); + assert_string_equal(session->opts.ProxyCommand, new->opts.ProxyCommand); + /* TODO custombanner */ + assert_int_equal(session->opts.timeout, new->opts.timeout); + assert_int_equal(session->opts.timeout_usec, new->opts.timeout_usec); + assert_int_equal(session->opts.port, new->opts.port); + assert_int_equal(session->opts.StrictHostKeyChecking, + new->opts.StrictHostKeyChecking); + assert_int_equal(session->opts.compressionlevel, + new->opts.compressionlevel); + assert_string_equal(session->opts.gss_server_identity, + new->opts.gss_server_identity); + assert_string_equal(session->opts.gss_client_identity, + new->opts.gss_client_identity); + assert_int_equal(session->opts.gss_delegate_creds, + new->opts.gss_delegate_creds); + assert_int_equal(session->opts.flags, new->opts.flags); + assert_int_equal(session->opts.nodelay, new->opts.nodelay); + assert_true(session->opts.config_processed == new->opts.config_processed); + assert_memory_equal(session->opts.options_seen, new->opts.options_seen, + sizeof(session->opts.options_seen)); + + ssh_free(new); +} + +#ifdef WITH_SERVER +const char template[] = "temp_dir_XXXXXX"; + +struct bind_st { + char *cwd; + char *temp_dir; + ssh_bind bind; +}; + +static int ssh_bind_setup_files(void **state) +{ + struct bind_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct bind_st *)malloc(sizeof(struct bind_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + /* For ed25519 the test keys are not available in legacy PEM format. Using + * the new OpenSSH format for all algorithms */ + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 0)); + + torture_write_file(LIBSSH_ED25519_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); +#ifdef HAVE_ECC + torture_write_file(LIBSSH_ECDSA_521_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); +#endif +#ifdef HAVE_DSA + torture_write_file(LIBSSH_DSA_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_DSS, 0)); +#endif + torture_write_file(LIBSSH_CUSTOM_BIND_CONFIG_FILE, + "Port 42\n"); + return 0; +} + + +/* sshbind options */ +static int sshbind_setup(void **state) +{ + int rc; + struct bind_st *test_state = NULL; + + rc = ssh_bind_setup_files((void **)&test_state); + assert_int_equal(rc, 0); + assert_non_null(test_state); + + test_state->bind = ssh_bind_new(); + assert_non_null(test_state->bind); + + *state = test_state; + + return 0; +} + +static int sshbind_teardown(void **state) +{ + struct bind_st *test_state = NULL; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + assert_non_null(test_state->bind); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + ssh_bind_free(test_state->bind); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_bind_options_import_key(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + const char *base64_key; + ssh_key key = ssh_key_new(); + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* set null */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, NULL); + assert_int_equal(rc, -1); + /* set invalid key */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, key); + assert_int_equal(rc, -1); + SSH_KEY_FREE(key); + + /* set rsa key */ + base64_key = torture_get_testkey(SSH_KEYTYPE_RSA, 0); + rc = ssh_pki_import_privkey_base64(base64_key, NULL, NULL, NULL, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, key); + assert_int_equal(rc, 0); +#ifdef HAVE_DSA + /* set dsa key */ + base64_key = torture_get_testkey(SSH_KEYTYPE_DSS, 0); + rc = ssh_pki_import_privkey_base64(base64_key, NULL, NULL, NULL, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, key); + assert_int_equal(rc, 0); +#endif +#ifdef HAVE_ECC + /* set ecdsa key */ + base64_key = torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0); + rc = ssh_pki_import_privkey_base64(base64_key, NULL, NULL, NULL, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, key); + assert_int_equal(rc, 0); +#endif +} + +static void torture_bind_options_hostkey(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* Test RSA key */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY, + LIBSSH_RSA_TESTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->rsakey); + assert_string_equal(bind->rsakey, LIBSSH_RSA_TESTKEY); + + /* Test ED25519 key */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY, + LIBSSH_ED25519_TESTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->ed25519key); + assert_string_equal(bind->ed25519key, LIBSSH_ED25519_TESTKEY); + +#ifdef HAVE_ECC + /* Test ECDSA key */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY, + LIBSSH_ECDSA_521_TESTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); +#endif +#ifdef HAVE_DSA + /* Test DSA key */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY, + LIBSSH_DSA_TESTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->dsakey); + assert_string_equal(bind->dsakey, LIBSSH_DSA_TESTKEY); +#endif +} + +static void torture_bind_options_bindaddr(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + const char *address = "127.0.0.1"; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDADDR, address); + assert_int_equal(rc, 0); + assert_non_null(bind->bindaddr); + assert_string_equal(bind->bindaddr, address); +} + +static void torture_bind_options_bindport(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + unsigned int given_port = 1234; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT, &given_port); + assert_int_equal(rc, 0); + assert_int_equal(bind->bindport, 1234); +} + +static void torture_bind_options_bindport_str(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, "23"); + assert_int_equal(rc, 0); + assert_int_equal(bind->bindport, 23); +} + +static void torture_bind_options_log_verbosity(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int verbosity = SSH_LOG_PACKET; + int previous_level, new_level; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, verbosity); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_options_log_verbosity_str(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level, new_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, SSH_LOG_PACKET); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +#ifdef HAVE_DSA +static void torture_bind_options_dsakey(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_DSAKEY, + LIBSSH_DSA_TESTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->dsakey); + assert_string_equal(bind->dsakey, LIBSSH_DSA_TESTKEY); +} +#endif + +static void torture_bind_options_rsakey(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_RSAKEY, + LIBSSH_RSA_TESTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->rsakey); + assert_string_equal(bind->rsakey, LIBSSH_RSA_TESTKEY); +} + +#ifdef HAVE_ECC +static void torture_bind_options_ecdsakey(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_ECDSAKEY, + LIBSSH_ECDSA_521_TESTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); +} +#endif + +static void torture_bind_options_banner(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + const char *banner = "This is the new banner"; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_BANNER, + banner); + assert_int_equal(rc, 0); + assert_non_null(bind->banner); + assert_string_equal(bind->banner, banner); +} + +static void torture_bind_options_set_ciphers(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + assert_non_null(bind->wanted_methods); + + /* Test known ciphers */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_C_S, + "aes128-ctr,aes192-ctr,aes256-ctr"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes256-ctr"); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes192-ctr,aes256-ctr"); + } + + /* Test one unknown cipher */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_C_S, + "aes128-ctr,unknown-crap@example.com,aes256-ctr"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes256-ctr"); + + /* Test all unknown ciphers */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_C_S, + "unknown-crap@example.com,more-crap@example.com"); + assert_int_not_equal(rc, 0); + + /* Test known ciphers */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_S_C, + "aes128-ctr,aes192-ctr,aes256-ctr"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], + "aes128-ctr,aes256-ctr"); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], + "aes128-ctr,aes192-ctr,aes256-ctr"); + } + + /* Test one unknown cipher */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_S_C, + "aes128-ctr,unknown-crap@example.com,aes256-ctr"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], + "aes128-ctr,aes256-ctr"); + + /* Test all unknown ciphers */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_S_C, + "unknown-crap@example.com,more-crap@example.com"); + assert_int_not_equal(rc, 0); +} + +static void torture_bind_options_set_key_exchange(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + assert_non_null(bind->wanted_methods); + + /* Test known kexes */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_KEY_EXCHANGE, + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_KEX]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_KEX], + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256"); + } else { + assert_string_equal(bind->wanted_methods[SSH_KEX], + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + } + + /* Test one unknown kex */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_KEY_EXCHANGE, + "diffie-hellman-group16-sha512," + "unknown-crap@example.com," + "diffie-hellman-group18-sha512"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_KEX]); + assert_string_equal(bind->wanted_methods[SSH_KEX], + "diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512"); + + /* Test all unknown kexes */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_KEY_EXCHANGE, + "unknown-crap@example.com,more-crap@example.com"); + assert_int_not_equal(rc, 0); +} + +static void torture_bind_options_set_macs(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + assert_non_null(bind->wanted_methods); + + /* Test known MACs */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_S_C, "hmac-sha1"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], "hmac-sha1"); + + /* Test multiple known MACs */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_S_C, + "hmac-sha1,hmac-sha2-256"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], + "hmac-sha1,hmac-sha2-256"); + + /* Test unknown MACs */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_S_C, + "unknown-crap@example.com,hmac-sha1,unknown@example.com"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], "hmac-sha1"); + + /* Test all unknown MACs */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_S_C, "unknown-crap@example.com"); + assert_int_not_equal(rc, 0); + + /* Test known MACs */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_C_S, "hmac-sha1"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], "hmac-sha1"); + + /* Test multiple known MACs */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_C_S, + "hmac-sha1,hmac-sha2-256"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], + "hmac-sha1,hmac-sha2-256"); + + /* Test unknown MACs */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_C_S, + "unknown-crap@example.com,hmac-sha1,unknown@example.com"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], "hmac-sha1"); + + /* Test all unknown MACs */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_C_S, "unknown-crap@example.com"); + assert_int_not_equal(rc, 0); +} + +static void torture_bind_options_parse_config(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + char *cwd = NULL; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_CONFIG_DIR, + (const char *)cwd); + assert_int_equal(rc, 0); + assert_non_null(bind->config_dir); + assert_string_equal(bind->config_dir, cwd); + + rc = ssh_bind_options_parse_config(bind, "%d/"LIBSSH_CUSTOM_BIND_CONFIG_FILE); + assert_int_equal(rc, 0); + assert_int_equal(bind->bindport, 42); + + SAFE_FREE(cwd); +} + +static void torture_bind_options_config_dir(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + const char *new_dir = "/new/dir/"; + const char *replacement_dir = "/replacement/dir/"; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_CONFIG_DIR, + new_dir); + assert_int_equal(rc, 0); + assert_non_null(bind->config_dir); + assert_string_equal(bind->config_dir, new_dir); + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_CONFIG_DIR, + replacement_dir); + assert_int_equal(rc, 0); + assert_non_null(bind->config_dir); + assert_string_equal(bind->config_dir, replacement_dir); +} + +static void torture_bind_options_set_pubkey_accepted_key_types(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* Test known Pubkey Types */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_int_equal(rc, 0); + assert_non_null(bind->pubkey_accepted_key_types); + if (ssh_fips_mode()) { + assert_string_equal(bind->pubkey_accepted_key_types, + "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(bind->pubkey_accepted_key_types, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + + SAFE_FREE(bind->pubkey_accepted_key_types); + + /* Test with some unknown type */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "ecdsa-sha2-nistp384,unknown-type,rsa-sha2-256"); + assert_int_equal(rc, 0); + assert_non_null(bind->pubkey_accepted_key_types); + assert_string_equal(bind->pubkey_accepted_key_types, + "ecdsa-sha2-nistp384,rsa-sha2-256"); + + SAFE_FREE(bind->pubkey_accepted_key_types); + + /* Test with only unknown type */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "unknown-type"); + assert_int_equal(rc, -1); + assert_null(bind->pubkey_accepted_key_types); + + /* Test with something set and then try unknown type */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "ecdsa-sha2-nistp384"); + assert_int_equal(rc, 0); + assert_non_null(bind->pubkey_accepted_key_types); + assert_string_equal(bind->pubkey_accepted_key_types, + "ecdsa-sha2-nistp384"); + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "unknown-type"); + assert_int_equal(rc, -1); + + /* Check that nothing changed */ + assert_non_null(bind->pubkey_accepted_key_types); + assert_string_equal(bind->pubkey_accepted_key_types, + "ecdsa-sha2-nistp384"); +} + +static void torture_bind_options_set_hostkey_algorithms(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* Test known Pubkey Types */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + + SAFE_FREE(bind->wanted_methods[SSH_HOSTKEYS]); + + /* Test with some unknown type */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "ecdsa-sha2-nistp384,unknown-type"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp384"); + + SAFE_FREE(bind->wanted_methods[SSH_HOSTKEYS]); + + /* Test with only unknown type */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "unknown-type"); + assert_int_equal(rc, -1); + assert_null(bind->wanted_methods[SSH_HOSTKEYS]); + + /* Test with something set and then try unknown type */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "ecdsa-sha2-nistp384"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp384"); + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "unknown-type"); + assert_int_equal(rc, -1); + + /* Check that nothing changed */ + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp384"); +} + +#endif /* WITH_SERVER */ + + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_options_set_host, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_get_host, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_port, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_get_port, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_fd, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_user, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_get_user, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_identity, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_get_identity, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_global_knownhosts, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_get_global_knownhosts, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_knownhosts, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_get_knownhosts, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_proxycommand, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_ciphers, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_key_exchange, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_hostkey, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_pubkey_accepted_types, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_macs, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_copy, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_config_host, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_config_match, + setup, teardown), + }; + +#ifdef WITH_SERVER + struct CMUnitTest sshbind_tests[] = { + cmocka_unit_test_setup_teardown(torture_bind_options_import_key, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_hostkey, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_bindaddr, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_bindport, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_bindport_str, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_log_verbosity, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_log_verbosity_str, + sshbind_setup, sshbind_teardown), +#ifdef HAVE_DSA + cmocka_unit_test_setup_teardown(torture_bind_options_dsakey, + sshbind_setup, sshbind_teardown), +#endif + cmocka_unit_test_setup_teardown(torture_bind_options_rsakey, + sshbind_setup, sshbind_teardown), +#ifdef HAVE_ECC + cmocka_unit_test_setup_teardown(torture_bind_options_ecdsakey, + sshbind_setup, sshbind_teardown), +#endif + cmocka_unit_test_setup_teardown(torture_bind_options_banner, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_set_ciphers, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_set_key_exchange, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_set_macs, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_parse_config, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_config_dir, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_set_pubkey_accepted_key_types, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_set_hostkey_algorithms, + sshbind_setup, sshbind_teardown), + }; +#endif /* WITH_SERVER */ + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); +#ifdef WITH_SERVER + rc += cmocka_run_group_tests(sshbind_tests, NULL, NULL); +#endif /* WITH_SERVER */ + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_packet.c b/tests/unittests/torture_packet.c new file mode 100644 index 0000000..922c832 --- /dev/null +++ b/tests/unittests/torture_packet.c @@ -0,0 +1,333 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/socket.h" +#include "libssh/callbacks.h" + +#include "socket.c" + +uint8_t test_data[]="AThis is test data. Use it to check the validity of packet functions" + "AThis is test data. Use it to check the validity of packet functions" + "AThis is test data. Use it to check the validity of packet functions" + "AThis is test data. Use it to check the validity of packet functions"; +uint8_t key[]="iekaeshoa7ooCie2shai8shahngee3ONsee3xoishooj0ojei6aeChieth1iraPh"; +uint8_t iv[]="eixaxughoomah4ui7Aew3ohxuolaifuu"; +uint8_t mac[]="thook2Jai0ahmahyae7ChuuruoPhee8Y"; + +static uint8_t *copy_data(uint8_t *data, size_t len){ + uint8_t *ret = malloc(len); + assert_non_null(ret); + memcpy(ret, data, len); + return ret; +} + +static SSH_PACKET_CALLBACK(copy_packet_data){ + uint8_t *response = user; + size_t len = ssh_buffer_get_len(packet); + (void)type; + (void)session; + + if(len > 1024){ + len = 1024; + } + ssh_buffer_get_data(packet, response, len); + + return 0; +} + +static void +torture_packet(const char *cipher, const char *mac_type, + const char *comp_type, size_t payload_len) +{ + ssh_session session = ssh_new(); + int verbosity = torture_libssh_verbosity(); + struct ssh_crypto_struct *crypto; + struct ssh_cipher_struct *in_cipher; + struct ssh_cipher_struct *out_cipher; + int rc; + int sockets[2]; + uint8_t buffer[1024]; + uint8_t response[1024]; + size_t encrypted_packet_len; + ssh_packet_callback callbacks[]={copy_packet_data}; + struct ssh_packet_callbacks_struct cb = { + .start='A', + .n_callbacks=1, + .callbacks=callbacks, + .user=response + }; + int cmp; + + assert_non_null(session); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + crypto = session->next_crypto; + + rc = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets); + assert_int_equal(rc, 0); + + crypto->kex_methods[SSH_KEX] = strdup("curve25519-sha256@libssh.org"); + crypto->kex_methods[SSH_HOSTKEYS] = strdup("ssh-rsa"); + crypto->kex_methods[SSH_CRYPT_C_S] = strdup(cipher); + crypto->kex_methods[SSH_CRYPT_S_C] = strdup(cipher); + crypto->kex_methods[SSH_MAC_C_S] = strdup(mac_type); + crypto->kex_methods[SSH_MAC_S_C] = strdup(mac_type); + crypto->kex_methods[SSH_COMP_C_S] = strdup(comp_type); + crypto->kex_methods[SSH_COMP_S_C] = strdup(comp_type); + crypto->kex_methods[SSH_LANG_C_S] = strdup("none"); + crypto->kex_methods[SSH_LANG_S_C] = strdup("none"); + rc = crypt_set_algorithms_client(session); + assert_int_equal(rc, SSH_OK); + session->current_crypto = session->next_crypto; + session->next_crypto = crypto_new(); + crypto->encryptkey = copy_data(key, sizeof(key)); + crypto->decryptkey = copy_data(key, sizeof(key)); + crypto->encryptIV = copy_data(iv, sizeof(iv)); + crypto->decryptIV = copy_data(iv, sizeof(iv)); + crypto->encryptMAC = copy_data(mac, sizeof(mac)); + crypto->decryptMAC = copy_data(mac, sizeof(mac)); + + in_cipher = session->current_crypto->in_cipher; + rc = in_cipher->set_decrypt_key(in_cipher, + session->current_crypto->decryptkey, + session->current_crypto->decryptIV); + assert_int_equal(rc, SSH_OK); + + out_cipher = session->current_crypto->out_cipher; + rc = out_cipher->set_encrypt_key(out_cipher, + session->current_crypto->encryptkey, + session->current_crypto->encryptIV); + session->current_crypto->used = SSH_DIRECTION_BOTH; + assert_int_equal(rc, SSH_OK); + + assert_non_null(session->out_buffer); + ssh_buffer_add_data(session->out_buffer, test_data, payload_len); + session->socket->fd = sockets[0]; + session->socket->write_wontblock = 1; + rc = ssh_packet_send(session); + assert_int_equal(rc, SSH_OK); + + rc = recv(sockets[1], buffer, sizeof(buffer), 0); + assert_true(rc > 0); + encrypted_packet_len = rc; + cmp = strcmp(comp_type, "none"); + if (cmp == 0) { + assert_in_range(encrypted_packet_len, + payload_len + 4, + payload_len + (32 * 3)); + } + rc = send(sockets[0], buffer, encrypted_packet_len, 0); + assert_int_equal(rc, encrypted_packet_len); + + ssh_packet_set_callbacks(session, &cb); + explicit_bzero(response, sizeof(response)); + rc = ssh_packet_socket_callback(buffer, encrypted_packet_len, session); + assert_int_not_equal(rc, SSH_ERROR); + if(payload_len > 0){ + assert_memory_equal(response, test_data+1, payload_len-1); + } + close(sockets[0]); + close(sockets[1]); + session->socket->fd = SSH_INVALID_SOCKET; + ssh_free(session); +} + +static void torture_packet_aes128_ctr_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes128-ctr", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_aes192_ctr_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes192-ctr", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_aes256_ctr_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes256-ctr", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_aes128_ctr(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes128-ctr", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes192_ctr(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes192-ctr", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes256_ctr(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes256-ctr", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes128_cbc(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes128-cbc", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes192_cbc(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes192-cbc", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes256_cbc(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes256-cbc", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes128_cbc_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes128-cbc", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_aes192_cbc_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes192-cbc", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_aes256_cbc_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes256-cbc", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_3des_cbc(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("3des-cbc", "hmac-sha1", "none", i); + } +} + +static void torture_packet_3des_cbc_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("3des-cbc", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_chacha20(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("chacha20-poly1305@openssh.com", "none", "none", i); + } +} + +static void torture_packet_aes128_gcm(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes128-gcm@openssh.com", "none", "none", i); + } +} + +static void torture_packet_aes256_gcm(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes256-gcm@openssh.com", "none", "none", i); + } +} + +static void torture_packet_compress_zlib(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes256-ctr", "hmac-sha1", "zlib", i); + } +} + +static void torture_packet_compress_zlib_openssh(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes256-ctr", "hmac-sha1", "zlib@openssh.com", i); + } +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_packet_aes128_ctr), + cmocka_unit_test(torture_packet_aes192_ctr), + cmocka_unit_test(torture_packet_aes256_ctr), + cmocka_unit_test(torture_packet_aes128_ctr_etm), + cmocka_unit_test(torture_packet_aes192_ctr_etm), + cmocka_unit_test(torture_packet_aes256_ctr_etm), + cmocka_unit_test(torture_packet_aes128_cbc), + cmocka_unit_test(torture_packet_aes192_cbc), + cmocka_unit_test(torture_packet_aes256_cbc), + cmocka_unit_test(torture_packet_aes128_cbc_etm), + cmocka_unit_test(torture_packet_aes192_cbc_etm), + cmocka_unit_test(torture_packet_aes256_cbc_etm), + cmocka_unit_test(torture_packet_3des_cbc), + cmocka_unit_test(torture_packet_3des_cbc_etm), + cmocka_unit_test(torture_packet_chacha20), + cmocka_unit_test(torture_packet_aes128_gcm), + cmocka_unit_test(torture_packet_aes256_gcm), + cmocka_unit_test(torture_packet_compress_zlib), + cmocka_unit_test(torture_packet_compress_zlib_openssh), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_packet_filter.c b/tests/unittests/torture_packet_filter.c new file mode 100644 index 0000000..85fb5c1 --- /dev/null +++ b/tests/unittests/torture_packet_filter.c @@ -0,0 +1,535 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * 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. + */ + +/* + * This test checks if the messages accepted by the packet filter were intented + * to be accepted. + * + * The process consists in 2 steps: + * - Try the filter with a message type in an arbitrary state + * - If the message is accepted by the filter, check if the message is in the + * set of accepted states. + * + * Only the values selected by the flag (COMPARE_*) are considered. + * */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/priv.h" +#include "libssh/libssh.h" +#include "libssh/session.h" +#include "libssh/auth.h" +#include "libssh/ssh2.h" +#include "libssh/packet.h" + +#include "packet.c" + +#define COMPARE_SESSION_STATE 1 +#define COMPARE_ROLE (1 << 1) +#define COMPARE_DH_STATE (1 << 2) +#define COMPARE_AUTH_STATE (1 << 3) +#define COMPARE_GLOBAL_REQ_STATE (1 << 4) +#define COMPARE_CURRENT_METHOD (1 << 5) + +#define SESSION_STATE_COUNT 11 +#define DH_STATE_COUNT 4 +#define AUTH_STATE_COUNT 15 +#define GLOBAL_REQ_STATE_COUNT 5 +#define MESSAGE_COUNT 100 // from 1 to 100 + +#define ROLE_CLIENT 0 +#define ROLE_SERVER 1 + +/* + * This is the list of currently unfiltered message types. + * Only unrecognized types should be in this list. + * */ +static uint8_t unfiltered[] = { + 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 22, 23, 24, 25, 26, 27, 28, 29, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 54, 55, 56, 57, 58, 59, + 62, + 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 83, 84, 85, 86, 87, 88, 89, +}; + +typedef struct global_state_st { + /* If the bit in this flag is zero, the corresponding state is not + * considered, working as a wildcard (meaning any value is accepted) */ + uint32_t flags; + uint8_t role; + enum ssh_session_state_e session; + enum ssh_dh_state_e dh; + enum ssh_auth_state_e auth; + enum ssh_channel_request_state_e global_req; +} global_state; + +static int cmp_state(const void *e1, const void *e2) +{ + global_state *s1 = (global_state *) e1; + global_state *s2 = (global_state *) e2; + + /* Compare role (client == 0 or server == 1)*/ + if (s1->role < s2->role) { + return -1; + } + else if (s1->role > s2->role) { + return 1; + } + + /* Compare session state */ + if (s1->session < s2->session) { + return -1; + } + else if (s1->session > s2->session) { + return 1; + } + + /* Compare DH state */ + if (s1->dh < s2->dh) { + return -1; + } + else if (s1->dh > s2->dh) { + return 1; + } + + /* Compare auth */ + if (s1->auth < s2->auth) { + return -1; + } + else if (s1->auth > s2->auth) { + return 1; + } + + /* Compare global_req */ + if (s1->global_req < s2->global_req) { + return -1; + } + else if (s1->global_req > s2->global_req) { + return 1; + } + + /* If all equal, they are equal */ + return 0; +} + +static int cmp_state_search(const void *key, const void *array_element) +{ + global_state *s1 = (global_state *) key; + global_state *s2 = (global_state *) array_element; + + int result = 0; + + if (s2->flags & COMPARE_ROLE) { + /* Compare role (client == 0 or server == 1)*/ + if (s1->role < s2->role) { + return -1; + } + else if (s1->role > s2->role) { + return 1; + } + } + + if (s2->flags & COMPARE_SESSION_STATE) { + /* Compare session state */ + if (s1->session < s2->session) { + result = -1; + goto end; + } + else if (s1->session > s2->session) { + result = 1; + goto end; + } + } + + if (s2->flags & COMPARE_DH_STATE) { + /* Compare DH state */ + if (s1->dh < s2->dh) { + result = -1; + goto end; + } + else if (s1->dh > s2->dh) { + result = 1; + goto end; + } + } + + if (s2->flags & COMPARE_AUTH_STATE) { + /* Compare auth */ + if (s1->auth < s2->auth) { + result = -1; + goto end; + } + else if (s1->auth > s2->auth) { + result = 1; + goto end; + } + } + + if (s2->flags & COMPARE_GLOBAL_REQ_STATE) { + /* Compare global_req */ + if (s1->global_req < s2->global_req) { + result = -1; + goto end; + } + else if (s1->global_req > s2->global_req) { + result = 1; + goto end; + } + } + +end: + return result; +} + +static int is_state_accepted(global_state *tested, global_state *accepted, + int accepted_len) +{ + global_state *found = NULL; + + found = bsearch(tested, accepted, accepted_len, sizeof(global_state), + cmp_state_search); + + if (found != NULL) { + return 1; + } + + return 0; +} + +static int cmp_uint8(const void *i, const void *j) +{ + uint8_t e1 = *((uint8_t *)i); + uint8_t e2 = *((uint8_t *)j); + + if (e1 < e2) { + return -1; + } + else if (e1 > e2) { + return 1; + } + + return 0; +} + +static int check_unfiltered(uint8_t msg_type) +{ + uint8_t *found; + + found = bsearch(&msg_type, unfiltered, sizeof(unfiltered)/sizeof(uint8_t), + sizeof(uint8_t), cmp_uint8); + + if (found != NULL) { + return 1; + } + + return 0; +} + +static void torture_packet_filter_check_unfiltered(void **state) +{ + ssh_session session; + + int role_c; + int auth_c; + int session_c; + int dh_c; + int global_req_c; + + uint8_t msg_type; + + enum ssh_packet_filter_result_e rc; + int in_unfiltered; + + (void)state; + + session = ssh_new(); + + for (msg_type = 1; msg_type <= MESSAGE_COUNT; msg_type++) { + session->in_packet.type = msg_type; + for (role_c = 0; role_c < 2; role_c++) { + session->server = role_c; + for (session_c = 0; session_c < SESSION_STATE_COUNT; session_c++) { + session->session_state = session_c; + for (dh_c = 0; dh_c < DH_STATE_COUNT; dh_c++) { + session->dh_handshake_state = dh_c; + for (auth_c = 0; auth_c < AUTH_STATE_COUNT; auth_c++) { + session->auth.state = auth_c; + for (global_req_c = 0; + global_req_c < GLOBAL_REQ_STATE_COUNT; + global_req_c++) + { + session->global_req_state = global_req_c; + + rc = ssh_packet_incoming_filter(session); + + if (rc == SSH_PACKET_UNKNOWN) { + in_unfiltered = check_unfiltered(msg_type); + + if (!in_unfiltered) { + fprintf(stderr, "Message type %d UNFILTERED " + "in state: role %d, session %d, dh %d, auth %d\n", + msg_type, role_c, session_c, dh_c, auth_c); + } + assert_int_equal(in_unfiltered, 1); + } + else { + in_unfiltered = check_unfiltered(msg_type); + + if (in_unfiltered) { + fprintf(stderr, "Message type %d NOT UNFILTERED " + "in state: role %d, session %d, dh %d, auth %d\n", + msg_type, role_c, session_c, dh_c, auth_c); + } + assert_int_equal(in_unfiltered, 0); + } + } + } + } + } + } + } + ssh_free(session); +} + +static int check_message_in_all_states(global_state accepted[], + int accepted_count, uint8_t msg_type) +{ + ssh_session session; + + int role_c; + int auth_c; + int session_c; + int dh_c; + int global_req_c; + + enum ssh_packet_filter_result_e rc; + int in_accepted; + + global_state key; + + session = ssh_new(); + + /* Sort the accepted array so that the elements can be searched using + * bsearch */ + qsort(accepted, accepted_count, sizeof(global_state), cmp_state); + + session->in_packet.type = msg_type; + + for (role_c = 0; role_c < 2; role_c++) { + session->server = role_c; + key.role = role_c; + for (session_c = 0; session_c < SESSION_STATE_COUNT; session_c++) { + session->session_state = session_c; + key.session = session_c; + for (dh_c = 0; dh_c < DH_STATE_COUNT; dh_c++) { + session->dh_handshake_state = dh_c; + key.dh = dh_c; + for (auth_c = 0; auth_c < AUTH_STATE_COUNT; auth_c++) { + session->auth.state = auth_c; + key.auth = auth_c; + for (global_req_c = 0; + global_req_c < GLOBAL_REQ_STATE_COUNT; + global_req_c++) + { + session->global_req_state = global_req_c; + key.global_req = global_req_c; + + rc = ssh_packet_incoming_filter(session); + + if (rc == SSH_PACKET_ALLOWED) { + in_accepted = is_state_accepted(&key, accepted, + accepted_count); + + if (!in_accepted) { + fprintf(stderr, "Message type %d ALLOWED " + "in state: role %d, session %d, dh %d, auth %d\n", + msg_type, role_c, session_c, dh_c, auth_c); + } + assert_int_equal(in_accepted, 1); + } + else if (rc == SSH_PACKET_DENIED) { + in_accepted = is_state_accepted(&key, accepted, accepted_count); + + if (in_accepted) { + fprintf(stderr, "Message type %d DENIED " + "in state: role %d, session %d, dh %d, auth %d\n", + msg_type, role_c, session_c, dh_c, auth_c); + } + assert_int_equal(in_accepted, 0); + } + else { + fprintf(stderr, "Message type %d UNFILTERED " + "in state: role %d, session %d, dh %d, auth %d\n", + msg_type, role_c, session_c, dh_c, auth_c); + } + } + } + } + } + } + + ssh_free(session); + return 0; +} + +static void torture_packet_filter_check_auth_success(void **state) +{ + int rc; + + global_state accepted[] = { + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_ROLE | + COMPARE_AUTH_STATE | + COMPARE_DH_STATE), + .role = ROLE_CLIENT, + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + .auth = SSH_AUTH_STATE_PUBKEY_AUTH_SENT, + }, + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_ROLE | + COMPARE_AUTH_STATE | + COMPARE_DH_STATE), + .role = ROLE_CLIENT, + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + .auth = SSH_AUTH_STATE_PASSWORD_AUTH_SENT, + }, + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_ROLE | + COMPARE_AUTH_STATE | + COMPARE_DH_STATE), + .role = ROLE_CLIENT, + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + .auth = SSH_AUTH_STATE_GSSAPI_MIC_SENT, + }, + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_ROLE | + COMPARE_AUTH_STATE | + COMPARE_DH_STATE), + .role = ROLE_CLIENT, + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + .auth = SSH_AUTH_STATE_KBDINT_SENT, + }, + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_ROLE | + COMPARE_AUTH_STATE | + COMPARE_DH_STATE | + COMPARE_CURRENT_METHOD), + .role = ROLE_CLIENT, + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + .auth = SSH_AUTH_STATE_AUTH_NONE_SENT, + } + }; + + int accepted_count = 5; + + /* Unused */ + (void) state; + + rc = check_message_in_all_states(accepted, accepted_count, + SSH2_MSG_USERAUTH_SUCCESS); + + assert_int_equal(rc, 0); +} + +static void torture_packet_filter_check_msg_ext_info(void **state) +{ + int rc; + + global_state accepted[] = { + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_DH_STATE), + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + }, + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_DH_STATE), + .session = SSH_SESSION_STATE_AUTHENTICATED, + .dh = DH_STATE_FINISHED, + }, + }; + + int accepted_count = 2; + + /* Unused */ + (void) state; + + rc = check_message_in_all_states(accepted, accepted_count, + SSH2_MSG_EXT_INFO); + + assert_int_equal(rc, 0); +} + +static void torture_packet_filter_check_channel_open(void **state) +{ + int rc; + + /* The only condition to accept a CHANNEL_OPEN is to be authenticated */ + global_state accepted[] = { + { + .flags = COMPARE_SESSION_STATE, + .session = SSH_SESSION_STATE_AUTHENTICATED, + } + }; + + int accepted_count = 1; + + /* Unused */ + (void) state; + + rc = check_message_in_all_states(accepted, accepted_count, + SSH2_MSG_CHANNEL_OPEN); + + assert_int_equal(rc, 0); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_packet_filter_check_auth_success), + cmocka_unit_test(torture_packet_filter_check_channel_open), + cmocka_unit_test(torture_packet_filter_check_unfiltered), + cmocka_unit_test(torture_packet_filter_check_msg_ext_info) + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_pki.c b/tests/unittests/torture_pki.c new file mode 100644 index 0000000..d886245 --- /dev/null +++ b/tests/unittests/torture_pki.c @@ -0,0 +1,447 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "torture.h" +#include "torture_pki.h" +#include "torture_key.h" +#include "pki.c" + +const unsigned char INPUT[] = "1234567890123456789012345678901234567890" + "123456789012345678901234"; + +const char template[] = "temp_dir_XXXXXX"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_cert_dir(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + return 0; +} + +static int teardown_cert_dir(void **state) { + + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_keytype(void **state) { + enum ssh_keytypes_e type; + const char *type_c; + + (void) state; /* unused */ + + type = ssh_key_type(NULL); + assert_true(type == SSH_KEYTYPE_UNKNOWN); + + type = ssh_key_type_from_name(NULL); + assert_true(type == SSH_KEYTYPE_UNKNOWN); + + type = ssh_key_type_from_name("42"); + assert_true(type == SSH_KEYTYPE_UNKNOWN); + + type_c = ssh_key_type_to_char(SSH_KEYTYPE_UNKNOWN); + assert_null(type_c); + + type_c = ssh_key_type_to_char(42); + assert_null(type_c); +} + +static void torture_pki_signature(void **state) +{ + ssh_signature sig; + + (void) state; /* unused */ + + sig = ssh_signature_new(); + assert_non_null(sig); + + ssh_signature_free(sig); +} + +struct key_attrs { + int sign; + int verify; + const char *type_c; + int size_arg; + int sig_length; + const char *sig_type_c; + int expect_success; +}; + +struct key_attrs key_attrs_list[][5] = { + { + {0, 0, "", 0, 0, "", 0}, /* UNKNOWN, AUTO */ + {0, 0, "", 0, 0, "", 0}, /* UNKNOWN, SHA1 */ + {0, 0, "", 0, 0, "", 0}, /* UNKNOWN, SHA256 */ + {0, 0, "", 0, 0, "", 0}, /* UNKNOWN, SHA384 */ + {0, 0, "", 0, 0, "", 0}, /* UNKNOWN, SHA512 */ + }, +#ifdef HAVE_DSA + { + {1, 1, "ssh-dss", 1024, 0, "", 0}, /* DSS, AUTO */ + {1, 1, "ssh-dss", 1024, 20, "ssh-dss", 1}, /* DSS, SHA1 */ + {1, 1, "ssh-dss", 1024, 0, "", 0}, /* DSS, SHA256 */ + {1, 1, "ssh-dss", 1024, 0, "", 0}, /* DSS, SHA384 */ + {1, 1, "ssh-dss", 1024, 0, "", 0}, /* DSS, SHA512 */ + }, +#else + { + {0, 0, "", 0, 0, "", 0}, /* DSS, AUTO */ + {0, 0, "", 0, 0, "", 0}, /* DSS, SHA1 */ + {0, 0, "", 0, 0, "", 0}, /* DSS, SHA256 */ + {0, 0, "", 0, 0, "", 0}, /* DSS, SHA384 */ + {0, 0, "", 0, 0, "", 0}, /* DSS, SHA512 */ + }, +#endif /* HAVE_DSA */ + { + {1, 1, "ssh-rsa", 2048, 0, "", 0}, /* RSA, AUTO */ + {1, 1, "ssh-rsa", 2048, 20, "ssh-rsa", 1}, /* RSA, SHA1 */ + {1, 1, "ssh-rsa", 2048, 32, "rsa-sha2-256", 1}, /* RSA, SHA256 */ + {1, 1, "ssh-rsa", 2048, 0, "", 0}, /* RSA, SHA384 */ + {1, 1, "ssh-rsa", 2048, 64, "rsa-sha2-512", 1}, /* RSA, SHA512 */ + }, + { + {0, 0, "", 0, 0, "", 0}, /* RSA1, AUTO */ + {0, 0, "", 0, 0, "", 0}, /* RSA1, SHA1 */ + {0, 0, "", 0, 0, "", 0}, /* RSA1, SHA256 */ + {0, 0, "", 0, 0, "", 0}, /* RSA1, SHA384 */ + {0, 0, "", 0, 0, "", 0}, /* RSA1, SHA512 */ + }, + { + {0, 1, "", 256, 0, "", 0}, /* ECDSA, AUTO */ + {0, 1, "", 256, 0, "", 0}, /* ECDSA, SHA1 */ + {0, 1, "", 256, 0, "", 0}, /* ECDSA, SHA256 */ + {0, 1, "", 384, 0, "", 0}, /* ECDSA, SHA384 */ + {0, 1, "", 521, 0, "", 0}, /* ECDSA, SHA512 */ + }, + { + {1, 1, "ssh-ed25519", 0, 33, "ssh-ed25519", 1}, /* ED25519, AUTO */ + {1, 1, "ssh-ed25519", 0, 0, "", 0}, /* ED25519, SHA1 */ + {1, 1, "ssh-ed25519", 0, 0, "", 0}, /* ED25519, SHA256 */ + {1, 1, "ssh-ed25519", 0, 0, "", 0}, /* ED25519, SHA384 */ + {1, 1, "ssh-ed25519", 0, 0, "", 0}, /* ED25519, SHA512 */ + }, +#ifdef HAVE_DSA + { + {0, 1, "", 0, 0, "", 0}, /* DSS CERT, AUTO */ + {0, 1, "", 0, 0, "", 0}, /* DSS CERT, SHA1 */ + {0, 1, "", 0, 0, "", 0}, /* DSS CERT, SHA256 */ + {0, 1, "", 0, 0, "", 0}, /* DSS CERT, SHA384 */ + {0, 1, "", 0, 0, "", 0}, /* DSS CERT, SHA512 */ + }, +#else + { + {0, 0, "", 0, 0, "", 0}, /* DSS CERT, AUTO */ + {0, 0, "", 0, 0, "", 0}, /* DSS CERT, SHA1 */ + {0, 0, "", 0, 0, "", 0}, /* DSS CERT, SHA256 */ + {0, 0, "", 0, 0, "", 0}, /* DSS CERT, SHA384 */ + {0, 0, "", 0, 0, "", 0}, /* DSS CERT, SHA512 */ + }, +#endif /* HAVE_DSA */ + { + {0, 1, "", 0, 0, "", 0}, /* RSA CERT, AUTO */ + {0, 1, "", 0, 0, "", 0}, /* RSA CERT, SHA1 */ + {0, 1, "", 0, 0, "", 0}, /* RSA CERT, SHA256 */ + {0, 1, "", 0, 0, "", 0}, /* RSA CERT, SHA384 */ + {0, 1, "", 0, 0, "", 0}, /* RSA CERT, SHA512 */ + }, +#ifdef HAVE_ECC + { + {1, 1, "ecdsa-sha2-nistp256", 256, 0, "", 0}, /* ECDSA P256, AUTO */ + {1, 1, "ecdsa-sha2-nistp256", 256, 0, "", 0}, /* ECDSA P256, SHA1 */ + {1, 1, "ecdsa-sha2-nistp256", 256, 32, "ecdsa-sha2-nistp256", 1}, /* ECDSA P256, SHA256 */ + {1, 1, "ecdsa-sha2-nistp256", 256, 0, "", 0}, /* ECDSA P256, SHA384 */ + {1, 1, "ecdsa-sha2-nistp256", 256, 0, "", 0}, /* ECDSA P256, SHA512 */ + }, + { + {1, 1, "ecdsa-sha2-nistp384", 384, 0, "", 0}, /* ECDSA P384, AUTO */ + {1, 1, "ecdsa-sha2-nistp384", 384, 0, "", 0}, /* ECDSA P384, SHA1 */ + {1, 1, "ecdsa-sha2-nistp384", 384, 0, "", 0}, /* ECDSA P384, SHA256 */ + {1, 1, "ecdsa-sha2-nistp384", 384, 48, "ecdsa-sha2-nistp384", 1}, /* ECDSA P384, SHA384 */ + {1, 1, "ecdsa-sha2-nistp384", 384, 0, "", 0}, /* ECDSA P384, SHA512 */ + }, + { + {1, 1, "ecdsa-sha2-nistp521", 521, 0, "", 0}, /* ECDSA P521, AUTO */ + {1, 1, "ecdsa-sha2-nistp521", 521, 0, "", 0}, /* ECDSA P521, SHA1 */ + {1, 1, "ecdsa-sha2-nistp521", 521, 0, "", 0}, /* ECDSA P521, SHA256 */ + {1, 1, "ecdsa-sha2-nistp521", 521, 0, "", 0}, /* ECDSA P521, SHA384 */ + {1, 1, "ecdsa-sha2-nistp521", 521, 64, "ecdsa-sha2-nistp521", 1}, /* ECDSA P521, SHA512 */ + }, + { + {0, 1, "", 0, 0, "", 0}, /* ECDSA P256 CERT, AUTO */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P256 CERT, SHA1 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P256 CERT, SHA256 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P256 CERT, SHA384 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P256 CERT, SHA512 */ + }, + { + {0, 1, "", 0, 0, "", 0}, /* ECDSA P384 CERT, AUTO */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P384 CERT, SHA1 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P384 CERT, SHA256 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P384 CERT, SHA384 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P384 CERT, SHA512 */ + }, + { + {0, 1, "", 0, 0, "", 0}, /* ECDSA P521 CERT, AUTO */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P521 CERT, SHA1 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P521 CERT, SHA256 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P521 CERT, SHA384 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P521 CERT, SHA512 */ + }, +#endif /* HAVE_ECC */ + { + {0, 1, "", 0, 0, "", 0}, /* ED25519 CERT, AUTO */ + {0, 1, "", 0, 0, "", 0}, /* ED25519 CERT, SHA1 */ + {0, 1, "", 0, 0, "", 0}, /* ED25519 CERT, SHA256 */ + {0, 1, "", 0, 0, "", 0}, /* ED25519 CERT, SHA384 */ + {0, 1, "", 0, 0, "", 0}, /* ED25519 CERT, SHA512 */ + }, +}; + +/* This tests all the base types and their signatures against each other */ +static void torture_pki_verify_mismatch(void **state) +{ + int rc; + int verbosity = torture_libssh_verbosity(); + ssh_key key = NULL, verify_key = NULL, pubkey = NULL, verify_pubkey = NULL; + ssh_signature sign = NULL, import_sig = NULL, new_sig = NULL; + ssh_string blob; + ssh_session session = ssh_new(); + enum ssh_keytypes_e key_type, sig_type; + enum ssh_digest_e hash; + size_t input_length = sizeof(INPUT); + struct key_attrs skey_attrs, vkey_attrs; + + (void) state; + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + for (sig_type = SSH_KEYTYPE_DSS; + sig_type <= SSH_KEYTYPE_ED25519_CERT01; + sig_type++) + { + for (hash = SSH_DIGEST_AUTO; + hash <= SSH_DIGEST_SHA512; + hash++) + { + if (ssh_fips_mode()) { + if (sig_type == SSH_KEYTYPE_DSS || + sig_type == SSH_KEYTYPE_ED25519 || + hash == SSH_DIGEST_SHA1) + { + /* In FIPS mode, skip unsupported algorithms */ + continue; + } + } + + skey_attrs = key_attrs_list[sig_type][hash]; + + if (!skey_attrs.sign) { + continue; + } + + rc = ssh_pki_generate(sig_type, skey_attrs.size_arg, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + assert_int_equal(key->type, sig_type); + assert_string_equal(key->type_c, skey_attrs.type_c); + + SSH_LOG(SSH_LOG_TRACE, "Creating signature %d with hash %d", + sig_type, hash); + + if (skey_attrs.expect_success == 0) { + /* Expect error */ + sign = pki_do_sign(key, INPUT, input_length, hash); + assert_null(sign); + + SSH_KEY_FREE(key); + continue; + } + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Create a valid signature using this key */ + sign = pki_do_sign(key, INPUT, input_length, hash); + assert_non_null(sign); + assert_int_equal(sign->type, key->type); + assert_string_equal(sign->type_c, skey_attrs.sig_type_c); + + /* Create a signature blob that can be imported and verified */ + blob = pki_signature_to_blob(sign); + assert_non_null(blob); + + /* Import and verify with current key + * (this is not tested anywhere else yet) */ + import_sig = pki_signature_from_blob(key, + blob, + sig_type, + hash); + assert_non_null(import_sig); + assert_int_equal(import_sig->type, key->type); + assert_string_equal(import_sig->type_c, skey_attrs.sig_type_c); + + rc = ssh_pki_signature_verify(session, + import_sig, + pubkey, + INPUT, + input_length); + assert_true(rc == SSH_OK); + + for (key_type = SSH_KEYTYPE_DSS; + key_type <= SSH_KEYTYPE_ED25519_CERT01; + key_type++) + { + if (ssh_fips_mode()) { + if (key_type == SSH_KEYTYPE_DSS || + key_type == SSH_KEYTYPE_ED25519) + { + /* In FIPS mode, skip unsupported algorithms */ + continue; + } + } + + vkey_attrs = key_attrs_list[key_type][hash]; + if (!vkey_attrs.verify) { + continue; + } + + SSH_LOG(SSH_LOG_TRACE, "Trying key %d with signature %d", + key_type, sig_type); + + if (is_cert_type(key_type)) { + torture_write_file("libssh_testkey-cert.pub", + torture_get_testkey_pub(key_type)); + rc = ssh_pki_import_cert_file("libssh_testkey-cert.pub", &verify_pubkey); + verify_key = NULL; + } else { + rc = ssh_pki_generate(key_type, vkey_attrs.size_arg, &verify_key); + assert_int_equal(rc, SSH_OK); + assert_non_null(verify_key); + rc = ssh_pki_export_privkey_to_pubkey(verify_key, &verify_pubkey); + } + assert_int_equal(rc, SSH_OK); + assert_non_null(verify_pubkey); + + /* Should gracefully fail, but not crash */ + rc = ssh_pki_signature_verify(session, + sign, + verify_pubkey, + INPUT, + input_length); + assert_true(rc != SSH_OK); + + /* Try the same with the imported signature */ + rc = ssh_pki_signature_verify(session, + import_sig, + verify_pubkey, + INPUT, + input_length); + assert_true(rc != SSH_OK); + + /* Try to import the signature blob with different key */ + new_sig = pki_signature_from_blob(verify_pubkey, + blob, + sig_type, + import_sig->hash_type); + if (ssh_key_type_plain(verify_pubkey->type) == sig_type) { + /* Importing with the same key type should work */ + assert_non_null(new_sig); + assert_int_equal(new_sig->type, key->type); + assert_string_equal(new_sig->type_c, skey_attrs.sig_type_c); + + /* The verification should not work */ + rc = ssh_pki_signature_verify(session, + new_sig, + verify_pubkey, + INPUT, + input_length); + assert_true(rc != SSH_OK); + + ssh_signature_free(new_sig); + } else { + assert_null(new_sig); + } + SSH_KEY_FREE(verify_key); + SSH_KEY_FREE(verify_pubkey); + } + + ssh_string_free(blob); + ssh_signature_free(sign); + ssh_signature_free(import_sig); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + key = NULL; + } + } + + ssh_free(session); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_keytype), + cmocka_unit_test(torture_pki_signature), + cmocka_unit_test_setup_teardown(torture_pki_verify_mismatch, + setup_cert_dir, + teardown_cert_dir), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_pki_dsa.c b/tests/unittests/torture_pki_dsa.c new file mode 100644 index 0000000..b555e48 --- /dev/null +++ b/tests/unittests/torture_pki_dsa.c @@ -0,0 +1,954 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" +#include "pki.c" + +#define LIBSSH_DSA_TESTKEY "libssh_testkey.id_dsa" +#define LIBSSH_DSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_dsa" + +const char template[] = "temp_dir_XXXXXX"; +const unsigned char INPUT[] = "12345678901234567890"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_dsa_key(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + torture_write_file(LIBSSH_DSA_TESTKEY, + torture_get_testkey(SSH_KEYTYPE_DSS, 0)); + torture_write_file(LIBSSH_DSA_TESTKEY_PASSPHRASE, + torture_get_testkey(SSH_KEYTYPE_DSS, 1)); + torture_write_file(LIBSSH_DSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_DSS)); + torture_write_file(LIBSSH_DSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_DSS_CERT01)); + + return 0; +} + +static int setup_openssh_dsa_key(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + torture_write_file(LIBSSH_DSA_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_DSS, 0)); + torture_write_file(LIBSSH_DSA_TESTKEY_PASSPHRASE, + torture_get_openssh_testkey(SSH_KEYTYPE_DSS, 1)); + torture_write_file(LIBSSH_DSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_DSS)); + torture_write_file(LIBSSH_DSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_DSS_CERT01)); + + return 0; +} + +static int teardown(void **state) { + + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_dsa_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_DSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_dsa_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_DSA_TESTKEY_PASSPHRASE, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_dsa_import_privkey_base64(void **state) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_DSS, 0), + passphrase, + NULL, + NULL, + &key); + assert_true(rc == 0); + + SSH_KEY_FREE(key); +} + +static void torture_pki_dsa_import_privkey_base64_comment(void **state) +{ + int rc, file_str_len; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + const char *comment_str = "#this is line-comment\n#this is another\n"; + const char *key_str = NULL; + char *file_str = NULL; + + (void) state; /* unused */ + + key_str = torture_get_testkey(SSH_KEYTYPE_DSS, 0); + assert_non_null(key_str); + + file_str_len = strlen(comment_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", comment_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, + passphrase, + NULL, + NULL, + &key); + assert_true(rc == 0); + + free(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_dsa_import_privkey_base64_whitespace(void **state) +{ + int rc, file_str_len; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + const char *whitespace_str = " \n\t\t\t\t\t\n\n\n\n\n"; + const char *key_str = NULL; + char *file_str = NULL; + + (void) state; /* unused */ + + key_str = torture_get_testkey(SSH_KEYTYPE_DSS, 0); + assert_non_null(key_str); + + file_str_len = strlen(whitespace_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", whitespace_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, + passphrase, + NULL, + NULL, + &key); + assert_true(rc == 0); + + free(file_str); + SSH_KEY_FREE(key); +} + +static int test_sign_verify_data(ssh_key key, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len) +{ + ssh_signature sig; + ssh_key pubkey = NULL; + int rc; + + /* Get the public key to verify signature */ + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Sign the buffer */ + sig = pki_sign_data(key, hash_type, input, input_len); + assert_non_null(sig); + + /* Verify signature */ + rc = pki_verify_data_signature(sig, pubkey, input, input_len); + assert_int_equal(rc, SSH_OK); + + ssh_signature_free(sig); + SSH_KEY_FREE(pubkey); + + return rc; +} + +static void torture_pki_sign_data_dsa(void **state) +{ + int rc; + ssh_key key = NULL; + + (void) state; + + /* Setup */ + rc = ssh_pki_generate(SSH_KEYTYPE_DSS, 2048, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + /* Test using SHA1 */ + rc = test_sign_verify_data(key, SSH_DIGEST_SHA1, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + + /* Cleanup */ + SSH_KEY_FREE(key); +} + +static void torture_pki_fail_sign_with_incompatible_hash(void **state) +{ + int rc; + ssh_key key = NULL; + ssh_key pubkey = NULL; + ssh_signature sig, bad_sig; + + (void) state; + + /* Setup */ + rc = ssh_pki_generate(SSH_KEYTYPE_DSS, 2048, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + /* Get the public key to verify signature */ + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Sign the buffer */ + sig = pki_sign_data(key, SSH_DIGEST_SHA1, INPUT, sizeof(INPUT)); + assert_non_null(sig); + + /* Verify signature */ + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_AUTO */ + bad_sig = pki_sign_data(key, SSH_DIGEST_AUTO, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_AUTO */ + sig->hash_type = SSH_DIGEST_AUTO; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_SHA256 */ + bad_sig = pki_sign_data(key, SSH_DIGEST_SHA256, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_SHA256 */ + sig->hash_type = SSH_DIGEST_SHA256; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_SHA384 */ + bad_sig = pki_sign_data(key, SSH_DIGEST_SHA384, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_SHA384 */ + sig->hash_type = SSH_DIGEST_SHA384; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_SHA512 */ + bad_sig = pki_sign_data(key, SSH_DIGEST_SHA512, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_SHA512 */ + sig->hash_type = SSH_DIGEST_SHA512; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Cleanup */ + ssh_signature_free(sig); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(key); +} + +#ifdef HAVE_LIBCRYPTO +static void torture_pki_dsa_write_privkey(void **state) +{ + ssh_key origkey = NULL; + ssh_key privkey = NULL; + int rc; + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_file(LIBSSH_DSA_TESTKEY, + NULL, + NULL, + NULL, + &origkey); + assert_true(rc == 0); + assert_non_null(origkey); + + unlink(LIBSSH_DSA_TESTKEY); + + rc = ssh_pki_export_privkey_file(origkey, + NULL, + NULL, + NULL, + LIBSSH_DSA_TESTKEY); + assert_true(rc == 0); + + rc = ssh_pki_import_privkey_file(LIBSSH_DSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_true(rc == 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); + + /* Test with passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_DSA_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &origkey); + assert_true(rc == 0); + assert_non_null(origkey); + + unlink(LIBSSH_DSA_TESTKEY_PASSPHRASE); + rc = ssh_pki_export_privkey_file(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_DSA_TESTKEY_PASSPHRASE); + assert_true(rc == 0); + + /* Test with invalid passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_DSA_TESTKEY_PASSPHRASE, + "invalid secret", + NULL, + NULL, + &privkey); + assert_true(rc == SSH_ERROR); + assert_null(privkey); + + rc = ssh_pki_import_privkey_file(LIBSSH_DSA_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_true(rc == 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); +} +#endif + +static void torture_pki_dsa_import_privkey_base64_passphrase(void **state) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_DSS, 1), + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_DSS, 1), + "wrong passphrase !!", + NULL, + NULL, + &key); + assert_true(rc == -1); + assert_null(key); + + /* test if it returns -1 if passphrase is NULL */ + /* libcrypto asks for a passphrase, so skip this test */ +#ifndef HAVE_LIBCRYPTO + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_DSS, 1), + NULL, + NULL, + NULL, + &key); + assert_true(rc == -1); + assert_null(key); +#endif + + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_DSS, 1), + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_DSS, 1), + "wrong passphrase !!", + NULL, + NULL, + &key); + assert_true(rc == -1); + assert_null(key); + + /* This free in unnecessary, but the static analyser does not know */ + SSH_KEY_FREE(key); + +#ifndef HAVE_LIBCRYPTO + /* test if it returns -1 if passphrase is NULL */ + /* libcrypto asks for a passphrase, so skip this test */ + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_DSS, 1), + NULL, + NULL, + NULL, + &key); + assert_true(rc == -1); + assert_null(key); + + /* This free in unnecessary, but the static analyser does not know */ + SSH_KEY_FREE(key); +#endif /* HAVE_LIBCRYPTO */ +} + +static void +torture_pki_dsa_import_openssh_privkey_base64_passphrase(void **state) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + const char *keystring = NULL; + + (void) state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_DSS, 1); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(keystring, + "wrong passphrase !!", + NULL, + NULL, + &key); + assert_true(rc == -1); + assert_null(key); + + /* test if it returns -1 if passphrase is NULL */ + rc = ssh_pki_import_privkey_base64(keystring, + NULL, + NULL, + NULL, + &key); + assert_true(rc == -1); + + rc = ssh_pki_import_privkey_base64(keystring, + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(keystring, + "wrong passphrase !!", + NULL, + NULL, + &key); + assert_true(rc == -1); + assert_null(key); + + /* This free is unnecessary, but the static analyser does not know */ + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is NULL */ + rc = ssh_pki_import_privkey_base64(keystring, + NULL, + NULL, + NULL, + &key); + assert_true(rc == -1); + assert_null(key); + + /* This free is unnecessary, but the static analyser does not know */ + SSH_KEY_FREE(key); +} + + +static void torture_pki_dsa_publickey_from_privatekey(void **state) +{ + int rc; + ssh_key key = NULL; + ssh_key pubkey = NULL; + const char *passphrase = NULL; + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_DSS, 0), + passphrase, + NULL, + NULL, + &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_dsa_import_cert_file(void **state) +{ + int rc; + ssh_key cert = NULL; + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + rc = ssh_pki_import_cert_file(LIBSSH_DSA_TESTKEY "-cert.pub", &cert); + assert_true(rc == 0); + assert_non_null(cert); + + type = ssh_key_type(cert); + assert_true(type == SSH_KEYTYPE_DSS_CERT01); + + rc = ssh_key_is_public(cert); + assert_true(rc == 1); + + SSH_KEY_FREE(cert); +} + +static void torture_pki_dsa_publickey_base64(void **state) +{ + enum ssh_keytypes_e type; + char *b64_key = NULL, *key_buf = NULL, *p = NULL; + const char *str = NULL; + ssh_key key = NULL; + size_t keylen; + size_t i; + int rc; + + (void) state; /* unused */ + + key_buf = strdup(torture_get_testkey_pub(SSH_KEYTYPE_DSS)); + assert_non_null(key_buf); + + keylen = strlen(key_buf); + + str = p = key_buf; + for (i = 0; i < keylen; i++) { + if (isspace((int)p[i])) { + p[i] = '\0'; + break; + } + + } + + type = ssh_key_type_from_name(str); + assert_true(type == SSH_KEYTYPE_DSS); + + str = &p[i + 1]; + + for (; i < keylen; i++) { + if (isspace((int)p[i])) { + p[i] = '\0'; + break; + } + } + + rc = ssh_pki_import_pubkey_base64(str, type, &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + assert_true(rc == 0); + assert_non_null(b64_key); + + assert_string_equal(str, b64_key); + + free(b64_key); + free(key_buf); + SSH_KEY_FREE(key); +} + +static void torture_pki_dsa_generate_pubkey_from_privkey(void **state) +{ + char pubkey_generated[4096] = {0}; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + int len; + int rc; + + (void) state; /* unused */ + + /* remove the public key, generate it from the private key and write it. */ + unlink(LIBSSH_DSA_TESTKEY ".pub"); + + rc = ssh_pki_import_privkey_file(LIBSSH_DSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_DSA_TESTKEY ".pub"); + assert_true(rc == 0); + + rc = torture_read_one_line(LIBSSH_DSA_TESTKEY ".pub", + pubkey_generated, + sizeof(pubkey_generated)); + assert_true(rc == 0); + + len = torture_pubkey_len(torture_get_testkey_pub(SSH_KEYTYPE_DSS)); + assert_memory_equal(torture_get_testkey_pub(SSH_KEYTYPE_DSS), + pubkey_generated, + len); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_dsa_duplicate_key(void **state) +{ + int rc; + char *b64_key = NULL; + char *b64_key_gen = NULL; + ssh_key pubkey = NULL; + ssh_key pubkey_dup = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + + (void) state; + + rc = ssh_pki_import_pubkey_file(LIBSSH_DSA_TESTKEY ".pub", &pubkey); + assert_true(rc == 0); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_true(rc == 0); + assert_non_null(b64_key); + rc = ssh_pki_import_privkey_file(LIBSSH_DSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey_dup); + assert_true(rc == SSH_OK); + assert_non_null(pubkey_dup); + + rc = ssh_pki_export_pubkey_base64(pubkey_dup, &b64_key_gen); + assert_true(rc == 0); + assert_non_null(b64_key_gen); + + assert_string_equal(b64_key, b64_key_gen); + + rc = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); + assert_true(rc == 0); + + rc = ssh_key_cmp(pubkey, pubkey_dup, SSH_KEY_CMP_PUBLIC); + assert_true(rc == 0); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(pubkey_dup); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); + SSH_STRING_FREE_CHAR(b64_key); + SSH_STRING_FREE_CHAR(b64_key_gen); +} + +static void torture_pki_dsa_generate_key(void **state) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_signature sign = NULL; + ssh_session session=ssh_new(); + (void) state; + + rc = ssh_pki_generate(SSH_KEYTYPE_DSS, 1024, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA1); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + rc = ssh_pki_generate(SSH_KEYTYPE_DSS, 2048, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA1); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + rc = ssh_pki_generate(SSH_KEYTYPE_DSS, 3072, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA1); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + ssh_free(session); +} + +static void torture_pki_dsa_cert_verify(void **state) +{ + int rc; + ssh_key privkey = NULL, cert = NULL; + ssh_signature sign = NULL; + ssh_session session=ssh_new(); + (void) state; + + rc = ssh_pki_import_privkey_file(LIBSSH_DSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_pki_import_cert_file(LIBSSH_DSA_TESTKEY "-cert.pub", &cert); + assert_true(rc == 0); + assert_non_null(cert); + + sign = pki_do_sign(privkey, INPUT, sizeof(INPUT), SSH_DIGEST_SHA1); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, cert, INPUT, sizeof(INPUT)); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(cert); + + ssh_free(session); +} + +static void torture_pki_dsa_skip(UNUSED_PARAM(void **state)) +{ + skip(); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_dsa_import_pubkey_file, + setup_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_import_pubkey_from_openssh_privkey, + setup_openssh_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_import_privkey_base64, + setup_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_import_privkey_base64_comment, + setup_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_import_privkey_base64_whitespace, + setup_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_import_privkey_base64, + setup_openssh_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_publickey_from_privatekey, + setup_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_import_cert_file, + setup_dsa_key, + teardown), +#ifdef HAVE_LIBCRYPTO + cmocka_unit_test_setup_teardown(torture_pki_dsa_write_privkey, + setup_dsa_key, + teardown), +#endif + cmocka_unit_test(torture_pki_sign_data_dsa), + cmocka_unit_test(torture_pki_fail_sign_with_incompatible_hash), + cmocka_unit_test(torture_pki_dsa_import_privkey_base64_passphrase), + cmocka_unit_test(torture_pki_dsa_import_openssh_privkey_base64_passphrase), + + /* public key */ + cmocka_unit_test_setup_teardown(torture_pki_dsa_publickey_base64, + setup_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_generate_pubkey_from_privkey, + setup_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_duplicate_key, + setup_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_duplicate_key, + setup_dsa_key, + teardown), + cmocka_unit_test(torture_pki_dsa_generate_key), + cmocka_unit_test_setup_teardown(torture_pki_dsa_cert_verify, + setup_dsa_key, + teardown), + }; + struct CMUnitTest skip_tests[] = { + cmocka_unit_test(torture_pki_dsa_skip) + }; + + ssh_init(); + if (ssh_fips_mode()) { + rc = cmocka_run_group_tests(skip_tests, NULL, NULL); + } else { + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + } + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_pki_ecdsa.c b/tests/unittests/torture_pki_ecdsa.c new file mode 100644 index 0000000..22f06a4 --- /dev/null +++ b/tests/unittests/torture_pki_ecdsa.c @@ -0,0 +1,1090 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" +#include "pki.c" + +#define LIBSSH_ECDSA_TESTKEY "libssh_testkey.id_ecdsa" +#define LIBSSH_ECDSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_ecdsa" + +const char template[] = "temp_dir_XXXXXX"; +const unsigned char INPUT[] = "12345678901234567890"; + +struct pki_st { + char *cwd; + char *temp_dir; + enum ssh_keytypes_e type; +}; + +static int setup_ecdsa_key(void **state, int ecdsa_bits) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + switch (ecdsa_bits) { + case 521: + test_state->type = SSH_KEYTYPE_ECDSA_P521; + break; + case 384: + test_state->type = SSH_KEYTYPE_ECDSA_P384; + break; + default: + test_state->type = SSH_KEYTYPE_ECDSA_P256; + break; + } + + torture_write_file(LIBSSH_ECDSA_TESTKEY, + torture_get_testkey(test_state->type, 0)); + torture_write_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, + torture_get_testkey(test_state->type, 1)); + torture_write_file(LIBSSH_ECDSA_TESTKEY ".pub", + torture_get_testkey_pub(test_state->type)); + torture_write_file(LIBSSH_ECDSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(test_state->type+3)); + return 0; +} + +static int setup_openssh_ecdsa_key(void **state, int ecdsa_bits) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + const char *keystring = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + switch (ecdsa_bits) { + case 521: + test_state->type = SSH_KEYTYPE_ECDSA_P521; + break; + case 384: + test_state->type = SSH_KEYTYPE_ECDSA_P384; + break; + default: + test_state->type = SSH_KEYTYPE_ECDSA_P256; + break; + } + + keystring = torture_get_openssh_testkey(test_state->type, 0); + torture_write_file(LIBSSH_ECDSA_TESTKEY, keystring); + + keystring = torture_get_openssh_testkey(test_state->type, 1); + torture_write_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, keystring); + torture_write_file(LIBSSH_ECDSA_TESTKEY ".pub", + torture_get_testkey_pub(test_state->type)); + torture_write_file(LIBSSH_ECDSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(test_state->type+3)); + return 0; +} + +static int setup_ecdsa_key_521(void **state) +{ + setup_ecdsa_key(state, 521); + + return 0; +} + +static int setup_ecdsa_key_384(void **state) +{ + setup_ecdsa_key(state, 384); + + return 0; +} + +static int setup_ecdsa_key_256(void **state) +{ + setup_ecdsa_key(state, 256); + + return 0; +} + +static int setup_openssh_ecdsa_key_521(void **state) +{ + setup_openssh_ecdsa_key(state, 521); + + return 0; +} + +static int setup_openssh_ecdsa_key_384(void **state) +{ + setup_openssh_ecdsa_key(state, 384); + + return 0; +} + +static int setup_openssh_ecdsa_key_256(void **state) +{ + setup_openssh_ecdsa_key(state, 256); + + return 0; +} + +static int teardown(void **state) { + + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_ecdsa_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_ECDSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_import_privkey_base64(void **state) +{ + int rc; + char *key_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_ECDSA_TESTKEY); + assert_non_null(key_str); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + free(key_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_ecdsa_import_privkey_base64_comment(void **state) +{ + int rc, file_str_len; + const char *comment_str = "#this is line-comment\n#this is another\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_ECDSA_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(comment_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", comment_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_ecdsa_import_privkey_base64_whitespace(void **state) +{ + int rc, file_str_len; + const char *whitespace_str = " \n\t\t\t\t\t\n\n\n\n\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_ECDSA_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(whitespace_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", whitespace_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); +} + + +static void torture_pki_ecdsa_publickey_from_privatekey(void **state) +{ + int rc; + char *key_str = NULL; + ssh_key key = NULL; + ssh_key pubkey = NULL; + const char *passphrase = NULL; + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_ECDSA_TESTKEY); + assert_non_null(key_str); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + free(key_str); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_import_cert_file(void **state) +{ + int rc; + ssh_key cert = NULL; + enum ssh_keytypes_e type; + struct pki_st *test_state = *((struct pki_st **)state); + + rc = ssh_pki_import_cert_file(LIBSSH_ECDSA_TESTKEY "-cert.pub", &cert); + assert_true(rc == 0); + assert_non_null(cert); + + type = ssh_key_type(cert); + assert_true(type == test_state->type+3); + + rc = ssh_key_is_public(cert); + assert_true(rc == 1); + + SSH_KEY_FREE(cert); +} + +static void torture_pki_ecdsa_publickey_base64(void **state) +{ + enum ssh_keytypes_e type; + char *b64_key = NULL, *key_buf = NULL, *p = NULL; + const char *q = NULL; + ssh_key key = NULL; + int rc; + struct pki_st *test_state = *((struct pki_st **)state); + + key_buf = torture_pki_read_file(LIBSSH_ECDSA_TESTKEY ".pub"); + assert_non_null(key_buf); + + q = p = key_buf; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + type = ssh_key_type_from_name(q); + assert_true(type == test_state->type); + + q = ++p; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + assert_true(rc == 0); + assert_non_null(b64_key); + + assert_string_equal(q, b64_key); + + free(b64_key); + free(key_buf); + SSH_KEY_FREE(key); +} + +static void torture_pki_ecdsa_generate_pubkey_from_privkey(void **state) +{ + char pubkey_original[4096] = {0}; + char pubkey_generated[4096] = {0}; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + int rc; + int len; + + (void) state; /* unused */ + + rc = torture_read_one_line(LIBSSH_ECDSA_TESTKEY ".pub", + pubkey_original, + sizeof(pubkey_original)); + assert_true(rc == 0); + + /* remove the public key, generate it from the private key and write it. */ + unlink(LIBSSH_ECDSA_TESTKEY ".pub"); + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_ECDSA_TESTKEY ".pub"); + assert_true(rc == 0); + + rc = torture_read_one_line(LIBSSH_ECDSA_TESTKEY ".pub", + pubkey_generated, + sizeof(pubkey_generated)); + assert_true(rc == 0); + len = torture_pubkey_len(pubkey_original); + assert_int_equal(strncmp(pubkey_original, pubkey_generated, len), 0); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_duplicate_key(void **state) +{ + int rc; + char *b64_key = NULL; + char *b64_key_gen = NULL; + ssh_key pubkey = NULL; + ssh_key pubkey_dup = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + + (void) state; + + rc = ssh_pki_import_pubkey_file(LIBSSH_ECDSA_TESTKEY ".pub", &pubkey); + assert_true(rc == 0); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_true(rc == 0); + assert_non_null(b64_key); + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey_dup); + assert_true(rc == SSH_OK); + assert_non_null(pubkey_dup); + + rc = ssh_pki_export_pubkey_base64(pubkey_dup, &b64_key_gen); + assert_true(rc == 0); + assert_non_null(b64_key_gen); + + assert_string_equal(b64_key, b64_key_gen); + + rc = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); + assert_true(rc == 0); + + rc = ssh_key_cmp(pubkey, pubkey_dup, SSH_KEY_CMP_PUBLIC); + assert_true(rc == 0); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(pubkey_dup); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); + SSH_STRING_FREE_CHAR(b64_key); + SSH_STRING_FREE_CHAR(b64_key_gen); +} + +/* Test case for bug #147: Private ECDSA key duplication did not carry + * over parts of the key that then caused subsequent key demotion to + * fail. + */ +static void torture_pki_ecdsa_duplicate_then_demote(void **state) +{ + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + int rc; + + (void) state; + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + assert_int_equal(privkey->ecdsa_nid, privkey_dup->ecdsa_nid); + + rc = ssh_pki_export_privkey_to_pubkey(privkey_dup, &pubkey); + assert_int_equal(rc, 0); + assert_non_null(pubkey); + assert_int_equal(pubkey->ecdsa_nid, privkey->ecdsa_nid); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); +} + +static void torture_pki_generate_key_ecdsa(void **state) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_signature sign = NULL; + enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; + const char *type_char = NULL; + const char *etype_char = NULL; + ssh_session session=ssh_new(); + (void) state; + + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P256, 0, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_true(rc == SSH_OK); + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ECDSA_P256); + type_char = ssh_key_type_to_char(type); + assert_true(strcmp(type_char, "ecdsa-sha2-nistp256") == 0); + etype_char = ssh_pki_key_ecdsa_name(key); + assert_true(strcmp(etype_char, "ecdsa-sha2-nistp256") == 0); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + /* deprecated */ + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 256, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_true(rc == SSH_OK); + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ECDSA_P256); + type_char = ssh_key_type_to_char(type); + assert_true(strcmp(type_char, "ecdsa-sha2-nistp256") == 0); + etype_char = ssh_pki_key_ecdsa_name(key); + assert_true(strcmp(etype_char, "ecdsa-sha2-nistp256") == 0); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P384, 0, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA384); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_true(rc == SSH_OK); + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ECDSA_P384); + type_char = ssh_key_type_to_char(type); + assert_true(strcmp(type_char, "ecdsa-sha2-nistp384") == 0); + etype_char =ssh_pki_key_ecdsa_name(key); + assert_true(strcmp(etype_char, "ecdsa-sha2-nistp384") == 0); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + /* deprecated */ + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 384, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA384); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_true(rc == SSH_OK); + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ECDSA_P384); + type_char = ssh_key_type_to_char(type); + assert_true(strcmp(type_char, "ecdsa-sha2-nistp384") == 0); + etype_char =ssh_pki_key_ecdsa_name(key); + assert_true(strcmp(etype_char, "ecdsa-sha2-nistp384") == 0); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P521, 0, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA512); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_true(rc == SSH_OK); + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ECDSA_P521); + type_char = ssh_key_type_to_char(type); + assert_true(strcmp(type_char, "ecdsa-sha2-nistp521") == 0); + etype_char =ssh_pki_key_ecdsa_name(key); + assert_true(strcmp(etype_char, "ecdsa-sha2-nistp521") == 0); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + /* deprecated */ + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 521, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA512); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_true(rc == SSH_OK); + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ECDSA_P521); + type_char = ssh_key_type_to_char(type); + assert_true(strcmp(type_char, "ecdsa-sha2-nistp521") == 0); + etype_char =ssh_pki_key_ecdsa_name(key); + assert_true(strcmp(etype_char, "ecdsa-sha2-nistp521") == 0); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + ssh_free(session); +} + +static void torture_pki_ecdsa_cert_verify(void **state) +{ + int rc; + ssh_key privkey = NULL, cert = NULL; + ssh_signature sign = NULL; + ssh_session session=ssh_new(); + enum ssh_digest_e hash_type; + (void) state; + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_pki_import_cert_file(LIBSSH_ECDSA_TESTKEY "-cert.pub", &cert); + assert_true(rc == 0); + assert_non_null(cert); + + /* Get the hash type to be used in the signature based on the key type */ + hash_type = ssh_key_type_to_hash(session, privkey->type); + + sign = pki_do_sign(privkey, INPUT, sizeof(INPUT), hash_type); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, cert, INPUT, sizeof(INPUT)); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(cert); + + ssh_free(session); +} + +static int test_sign_verify_data(ssh_key key, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len) +{ + ssh_signature sig; + ssh_key pubkey = NULL; + int rc; + + /* Get the public key to verify signature */ + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Sign the buffer */ + sig = pki_sign_data(key, hash_type, input, input_len); + assert_non_null(sig); + + /* Verify signature */ + rc = pki_verify_data_signature(sig, pubkey, input, input_len); + assert_int_equal(rc, SSH_OK); + + ssh_signature_free(sig); + SSH_KEY_FREE(pubkey); + + return rc; +} + +static void torture_pki_sign_data_ecdsa(void **state) +{ + int rc; + ssh_key key = NULL; + + (void) state; + + /* Setup */ + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 256, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + /* Test using SHA256 */ + rc = test_sign_verify_data(key, SSH_DIGEST_SHA256, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + + /* Cleanup */ + SSH_KEY_FREE(key); +} + +static void torture_pki_fail_sign_with_incompatible_hash(void **state) +{ + int rc; + ssh_key key = NULL; + ssh_key pubkey = NULL; + ssh_signature sig, bad_sig; + + (void) state; + + /* Setup */ + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P256, 256, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + /* Get the public key to verify signature */ + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Sign the buffer */ + sig = pki_sign_data(key, SSH_DIGEST_SHA256, INPUT, sizeof(INPUT)); + assert_non_null(sig); + + /* Verify signature */ + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_AUTO */ + bad_sig = pki_sign_data(key, SSH_DIGEST_AUTO, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_AUTO */ + sig->hash_type = SSH_DIGEST_AUTO; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_SHA1 */ + bad_sig = pki_sign_data(key, SSH_DIGEST_SHA1, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_SHA1 */ + sig->hash_type = SSH_DIGEST_SHA1; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_SHA384 */ + bad_sig = pki_sign_data(key, SSH_DIGEST_SHA384, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_SHA384 */ + sig->hash_type = SSH_DIGEST_SHA384; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_SHA512 */ + bad_sig = pki_sign_data(key, SSH_DIGEST_SHA512, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_SHA512 */ + sig->hash_type = SSH_DIGEST_SHA512; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Cleanup */ + ssh_signature_free(sig); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(key); +} + +#ifdef HAVE_LIBCRYPTO +static void torture_pki_ecdsa_write_privkey(void **state) +{ + ssh_key origkey = NULL; + ssh_key privkey = NULL; + int rc; + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &origkey); + assert_true(rc == 0); + assert_non_null(origkey); + + unlink(LIBSSH_ECDSA_TESTKEY); + + rc = ssh_pki_export_privkey_file(origkey, + NULL, + NULL, + NULL, + LIBSSH_ECDSA_TESTKEY); + assert_true(rc == 0); + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_true(rc == 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); + + /* Test with passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &origkey); + assert_true(rc == 0); + assert_non_null(origkey); + + unlink(LIBSSH_ECDSA_TESTKEY_PASSPHRASE); + rc = ssh_pki_export_privkey_file(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_ECDSA_TESTKEY_PASSPHRASE); + assert_true(rc == 0); + + /* Test with invalid passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, + "invalid secret", + NULL, + NULL, + &privkey); + assert_true(rc == SSH_ERROR); + assert_null(privkey); + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_true(rc == 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); +} +#endif /* HAVE_LIBCRYPTO */ + +static void torture_pki_ecdsa_name(void **state, const char *expected_name) +{ + int rc; + ssh_key key = NULL; + const char *etype_char = NULL; + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, NULL, NULL, NULL, &key); + assert_true(rc == 0); + assert_non_null(key); + + etype_char =ssh_pki_key_ecdsa_name(key); + assert_true(strcmp(etype_char, expected_name) == 0); + + SSH_KEY_FREE(key); +} + +static void torture_pki_ecdsa_name256(void **state) +{ + torture_pki_ecdsa_name(state, "ecdsa-sha2-nistp256"); +} + +static void torture_pki_ecdsa_name384(void **state) +{ + torture_pki_ecdsa_name(state, "ecdsa-sha2-nistp384"); +} + +static void torture_pki_ecdsa_name521(void **state) +{ + torture_pki_ecdsa_name(state, "ecdsa-sha2-nistp521"); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_file, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_file, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_file, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_from_openssh_privkey, + setup_openssh_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_from_openssh_privkey, + setup_openssh_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_file, + setup_openssh_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64_comment, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64_comment, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64_comment, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64_whitespace, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64_whitespace, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64_whitespace, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, + setup_openssh_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, + setup_openssh_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, + setup_openssh_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_from_privatekey, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_from_privatekey, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_from_privatekey, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_cert_file, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_cert_file, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_cert_file, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_then_demote, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_then_demote, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_then_demote, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_base64, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_base64, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_base64, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_generate_pubkey_from_privkey, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_generate_pubkey_from_privkey, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_generate_pubkey_from_privkey, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_key, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_key, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_key, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test(torture_pki_generate_key_ecdsa), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_cert_verify, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_cert_verify, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_cert_verify, + setup_ecdsa_key_521, + teardown), +#ifdef HAVE_LIBCRYPTO + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey, + setup_ecdsa_key_521, + teardown), +#endif /* HAVE_LIBCRYPTO */ + cmocka_unit_test(torture_pki_sign_data_ecdsa), + cmocka_unit_test(torture_pki_fail_sign_with_incompatible_hash), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_name256, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_name384, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_name521, + setup_ecdsa_key_521, + teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_pki_ed25519.c b/tests/unittests/torture_pki_ed25519.c new file mode 100644 index 0000000..07ccfd6 --- /dev/null +++ b/tests/unittests/torture_pki_ed25519.c @@ -0,0 +1,1031 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" +#include "pki.c" +#include +#include + +#define LIBSSH_ED25519_TESTKEY "libssh_testkey.id_ed25519" +#define LIBSSH_ED25519_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_ed25519" + +const char template[] = "temp_dir_XXXXXX"; +const unsigned char HASH[] = "12345678901234567890"; +const uint8_t ref_signature[ED25519_SIG_LEN]= + "\xbb\x8d\x55\x9f\x06\x14\x39\x24\xb4\xe1\x5a\x57\x3d\x9d\xbe\x22" + "\x1b\xc1\x32\xd5\x55\x16\x00\x64\xce\xb4\xc3\xd2\xe3\x6f\x5e\x8d" + "\x10\xa3\x18\x93\xdf\xa4\x96\x81\x11\x8e\x1e\x26\x14\x8a\x08\x1b" + "\x01\x6a\x60\x59\x9c\x4a\x55\xa3\x16\x56\xf6\xc4\x50\x42\x7f\x03"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_ed25519_key(void **state) +{ + const char *keystring = NULL; + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); + torture_write_file(LIBSSH_ED25519_TESTKEY, keystring); + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 1); + torture_write_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, keystring); + + torture_write_file(LIBSSH_ED25519_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_ED25519)); + torture_write_file(LIBSSH_ED25519_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_ED25519_CERT01)); + + return 0; +} + +static int teardown(void **state) { + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_ed25519_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_ED25519_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_import_privkey_base64(void **state) +{ + int rc; + char *key_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_ED25519_TESTKEY); + assert_non_null(key_str); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + assert_non_null(key); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ED25519); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_key_is_public(key); + assert_true(rc == 1); + + free(key_str); + SSH_KEY_FREE(key); + +} + +static void torture_pki_ed25519_import_privkey_base64_comment(void **state) +{ + int rc, file_str_len; + const char *comment_str = "#this is line-comment\n#this is another\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_ED25519_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(comment_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", comment_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + assert_non_null(key); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ED25519); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_key_is_public(key); + assert_true(rc == 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); + +} + +static void torture_pki_ed25519_import_privkey_base64_whitespace(void **state) +{ + int rc, file_str_len; + const char *whitespace_str = " \n\t\t\t\t\t\n\n\n\n\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_ED25519_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(whitespace_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", whitespace_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + assert_non_null(key); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ED25519); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_key_is_public(key); + assert_true(rc == 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); + +} + +static void torture_pki_ed25519_import_export_privkey_base64(void **state) +{ + char *b64_key = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + int rc; + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_base64(torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, + false), + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ED25519); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_pki_export_privkey_base64(key, + passphrase, + NULL, + NULL, + &b64_key); + assert_return_code(rc, errno); + assert_non_null(b64_key); + SSH_KEY_FREE(key); + + rc = ssh_pki_import_privkey_base64(b64_key, + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ED25519); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + SSH_STRING_FREE_CHAR(b64_key); + SSH_KEY_FREE(key); +} + +static void torture_pki_ed25519_publickey_from_privatekey(void **state) +{ + int rc; + ssh_key key = NULL; + ssh_key pubkey = NULL; + const char *passphrase = NULL; + const char *keystring = NULL; + + (void) state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); + rc = ssh_pki_import_privkey_base64(keystring, + passphrase, + NULL, + NULL, + &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_import_cert_file(void **state) +{ + int rc; + ssh_key cert = NULL; + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + rc = ssh_pki_import_cert_file(LIBSSH_ED25519_TESTKEY "-cert.pub", &cert); + assert_true(rc == 0); + assert_non_null(cert); + + type = ssh_key_type(cert); + assert_true(type == SSH_KEYTYPE_ED25519_CERT01); + + rc = ssh_key_is_public(cert); + assert_true(rc == 1); + + SSH_KEY_FREE(cert); +} + +static void torture_pki_ed25519_publickey_base64(void **state) +{ + enum ssh_keytypes_e type; + char *b64_key = NULL, *key_buf = NULL, *p = NULL; + const char *q = NULL; + ssh_key key = NULL; + int rc; + + (void) state; /* unused */ + + key_buf = strdup(torture_get_testkey_pub(SSH_KEYTYPE_ED25519)); + assert_non_null(key_buf); + + q = p = key_buf; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + type = ssh_key_type_from_name(q); + assert_true(type == SSH_KEYTYPE_ED25519); + + q = ++p; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + assert_true(rc == 0); + assert_non_null(b64_key); + + assert_string_equal(q, b64_key); + + free(b64_key); + free(key_buf); + SSH_KEY_FREE(key); +} + +static void torture_pki_ed25519_generate_pubkey_from_privkey(void **state) +{ + char pubkey_generated[4096] = {0}; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + int rc; + int len; + + (void) state; /* unused */ + + /* remove the public key, generate it from the private key and write it. */ + unlink(LIBSSH_ED25519_TESTKEY ".pub"); + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_ED25519_TESTKEY ".pub"); + assert_true(rc == 0); + + rc = torture_read_one_line(LIBSSH_ED25519_TESTKEY ".pub", + pubkey_generated, + sizeof(pubkey_generated)); + assert_true(rc == 0); + + len = torture_pubkey_len(torture_get_testkey_pub(SSH_KEYTYPE_ED25519)); + assert_memory_equal(torture_get_testkey_pub(SSH_KEYTYPE_ED25519), + pubkey_generated, + len); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_generate_key(void **state) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_signature sign = NULL; + enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; + const char *type_char = NULL; + ssh_session session=ssh_new(); + uint8_t *raw_sig_data = NULL; + (void) state; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + assert_non_null(session); + + rc = ssh_pki_generate(SSH_KEYTYPE_ED25519, 256, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, HASH, 20, SSH_DIGEST_AUTO); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, HASH, 20); + assert_true(rc == SSH_OK); + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ED25519); + type_char = ssh_key_type_to_char(type); + assert_true(strcmp(type_char, "ssh-ed25519") == 0); + + /* try an invalid signature */ +#ifdef HAVE_OPENSSL_ED25519 + raw_sig_data = ssh_string_data(sign->raw_sig); +#else + raw_sig_data = (uint8_t *)sign->ed25519_sig; +#endif + assert_non_null(raw_sig_data); + (raw_sig_data)[3]^= 0xff; + rc = ssh_pki_signature_verify(session, sign, pubkey, HASH, 20); + assert_true(rc == SSH_ERROR); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + ssh_free(session); +} + +static void torture_pki_ed25519_cert_verify(void **state) +{ + int rc; + ssh_key privkey = NULL, cert = NULL; + ssh_signature sign = NULL; + ssh_session session=ssh_new(); + (void) state; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + assert_non_null(session); + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_pki_import_cert_file(LIBSSH_ED25519_TESTKEY "-cert.pub", &cert); + assert_true(rc == 0); + assert_non_null(cert); + + sign = pki_do_sign(privkey, HASH, 20, SSH_DIGEST_AUTO); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, cert, HASH, 20); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(cert); + + ssh_free(session); +} + +static void torture_pki_ed25519_write_privkey(void **state) +{ + ssh_key origkey = NULL; + ssh_key privkey = NULL; + int rc; + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &origkey); + assert_true(rc == 0); + assert_non_null(origkey); + + unlink(LIBSSH_ED25519_TESTKEY); + + rc = ssh_pki_export_privkey_file(origkey, + NULL, + NULL, + NULL, + LIBSSH_ED25519_TESTKEY); + assert_true(rc == 0); + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_true(rc == 0); + + unlink(LIBSSH_ED25519_TESTKEY); + SSH_KEY_FREE(privkey); + /* do the same with passphrase */ + rc = ssh_pki_export_privkey_file(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_ED25519_TESTKEY); + assert_true(rc == 0); + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + /* opening without passphrase should fail */ + assert_true(rc == SSH_ERROR); + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_true(rc == 0); + unlink(LIBSSH_ED25519_TESTKEY); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); + + /* Test with passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &origkey); + assert_true(rc == 0); + assert_non_null(origkey); + + unlink(LIBSSH_ED25519_TESTKEY_PASSPHRASE); + rc = ssh_pki_export_privkey_file(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_ED25519_TESTKEY_PASSPHRASE); + assert_true(rc == 0); + + /* Test with invalid passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, + "invalid secret", + NULL, + NULL, + &privkey); + assert_true(rc == SSH_ERROR); + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_true(rc == 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); +} + +static void torture_pki_ed25519_sign(void **state) +{ + ssh_key privkey = NULL; + ssh_signature sig = NULL; + ssh_string blob = NULL; + const char *keystring = NULL; + int rc; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + (void)state; + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); + rc = ssh_pki_import_privkey_base64(keystring, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + sig = pki_do_sign(privkey, HASH, sizeof(HASH), SSH_DIGEST_AUTO); + assert_non_null(sig); + + blob = pki_signature_to_blob(sig); + assert_non_null(blob); + + assert_int_equal(ssh_string_len(blob), sizeof(ref_signature)); + assert_memory_equal(ssh_string_data(blob), ref_signature, + sizeof(ref_signature)); + + ssh_signature_free(sig); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE(blob); + +} + +static void torture_pki_ed25519_sign_openssh_privkey_passphrase(void **state) +{ + ssh_key privkey = NULL; + ssh_signature sig = NULL; + ssh_string blob = NULL; + const char *keystring = NULL; + int rc; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + (void)state; + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 1); + rc = ssh_pki_import_privkey_base64(keystring, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + sig = pki_do_sign(privkey, HASH, sizeof(HASH), SSH_DIGEST_AUTO); + assert_non_null(sig); + + blob = pki_signature_to_blob(sig); + assert_non_null(blob); + assert_int_equal(ssh_string_len(blob), sizeof(ref_signature)); + assert_memory_equal(ssh_string_data(blob), ref_signature, + sizeof(ref_signature)); + + ssh_signature_free(sig); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE(blob); +} + +#ifdef HAVE_OPENSSL_ED25519 +static void torture_pki_ed25519_sign_pkcs8_privkey(void **state) +{ + ssh_key privkey = NULL; + ssh_signature sig = NULL; + ssh_string blob = NULL; + const char *keystring = NULL; + int rc; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + (void)state; + + keystring = torture_get_testkey(SSH_KEYTYPE_ED25519, 0); + rc = ssh_pki_import_privkey_base64(keystring, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + sig = pki_do_sign(privkey, HASH, sizeof(HASH), SSH_DIGEST_AUTO); + assert_non_null(sig); + + blob = pki_signature_to_blob(sig); + assert_non_null(blob); + assert_int_equal(ssh_string_len(blob), sizeof(ref_signature)); + assert_memory_equal(ssh_string_data(blob), ref_signature, + sizeof(ref_signature)); + + ssh_signature_free(sig); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE(blob); +} + +static void torture_pki_ed25519_sign_pkcs8_privkey_passphrase(void **state) +{ + ssh_key privkey = NULL; + ssh_signature sig = NULL; + ssh_string blob = NULL; + const char *keystring = NULL; + int rc; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + (void)state; + + keystring = torture_get_testkey(SSH_KEYTYPE_ED25519, 1); + rc = ssh_pki_import_privkey_base64(keystring, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + sig = pki_do_sign(privkey, HASH, sizeof(HASH), SSH_DIGEST_AUTO); + assert_non_null(sig); + + blob = pki_signature_to_blob(sig); + assert_non_null(blob); + assert_int_equal(ssh_string_len(blob), sizeof(ref_signature)); + assert_memory_equal(ssh_string_data(blob), ref_signature, + sizeof(ref_signature)); + + ssh_signature_free(sig); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE(blob); +} +#endif /* HAVE_OPENSSL_ED25519 */ + +static void torture_pki_ed25519_verify(void **state){ + ssh_key pubkey = NULL; + ssh_signature sig = NULL; + ssh_session session = NULL; + ssh_string blob = ssh_string_new(ED25519_SIG_LEN); + char *pkey_ptr = strdup(strchr(torture_get_testkey_pub(SSH_KEYTYPE_ED25519), ' ') + 1); + char *ptr = NULL; + uint8_t *raw_sig_data = NULL; + int rc; + (void) state; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + session = ssh_new(); + assert_non_null(session); + + /* remove trailing comment */ + ptr = strchr(pkey_ptr, ' '); + if(ptr != NULL){ + *ptr = '\0'; + } + rc = ssh_pki_import_pubkey_base64(pkey_ptr, SSH_KEYTYPE_ED25519, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + ssh_string_fill(blob, ref_signature, ED25519_SIG_LEN); + sig = pki_signature_from_blob(pubkey, blob, SSH_KEYTYPE_ED25519, SSH_DIGEST_AUTO); + assert_non_null(sig); + + rc = ssh_pki_signature_verify(session, sig, pubkey, HASH, sizeof(HASH)); + assert_true(rc == SSH_OK); + + /* Alter signature and expect verification error */ +#if defined(HAVE_OPENSSL_ED25519) + raw_sig_data = ssh_string_data(sig->raw_sig); +#else + raw_sig_data = (uint8_t *)sig->ed25519_sig; +#endif + assert_non_null(raw_sig_data); + (raw_sig_data)[3]^= 0xff; + rc = ssh_pki_signature_verify(session, sig, pubkey, HASH, sizeof(HASH)); + assert_true(rc == SSH_ERROR); + + ssh_signature_free(sig); + + SSH_KEY_FREE(pubkey); + SSH_STRING_FREE(blob); + free(pkey_ptr); + ssh_free(session); +} + +static void torture_pki_ed25519_verify_bad(void **state){ + ssh_key pubkey = NULL; + ssh_signature sig = NULL; + ssh_session session = NULL; + ssh_string blob = ssh_string_new(ED25519_SIG_LEN); + char *pkey_ptr = strdup(strchr(torture_get_testkey_pub(SSH_KEYTYPE_ED25519), ' ') + 1); + char *ptr = NULL; + int rc; + int i; + (void) state; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + session = ssh_new(); + assert_non_null(session); + + /* remove trailing comment */ + ptr = strchr(pkey_ptr, ' '); + if(ptr != NULL){ + *ptr = '\0'; + } + rc = ssh_pki_import_pubkey_base64(pkey_ptr, SSH_KEYTYPE_ED25519, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + /* alter signature and expect false result */ + + for (i=0; i < ED25519_SIG_LEN; ++i){ + ssh_string_fill(blob, ref_signature, ED25519_SIG_LEN); + ((uint8_t *)ssh_string_data(blob))[i] ^= 0xff; + sig = pki_signature_from_blob(pubkey, blob, SSH_KEYTYPE_ED25519, SSH_DIGEST_AUTO); + assert_non_null(sig); + + rc = ssh_pki_signature_verify(session, sig, pubkey, HASH, sizeof(HASH)); + assert_true(rc == SSH_ERROR); + ssh_signature_free(sig); + + } + SSH_KEY_FREE(pubkey); + SSH_STRING_FREE(blob); + free(pkey_ptr); + ssh_free(session); +} + +static void torture_pki_ed25519_import_privkey_base64_passphrase(void **state) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + const char *testkey = NULL; + + (void) state; /* unused */ + + /* same for ED25519 */ + testkey = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 1); + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(testkey, + "wrong passphrase !!", + NULL, + NULL, + &key); + assert_true(rc == -1); + SSH_KEY_FREE(key); +} + +static void torture_pki_ed25519_privkey_dup(void **state) +{ + const char *passphrase = torture_get_testkey_passphrase(); + ssh_key key = NULL; + ssh_key dup = NULL; + const char *testkey = NULL; + int rc; + + (void) state; /* unused */ + + testkey = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 1); + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + dup = ssh_key_dup(key); + assert_non_null(dup); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(dup); +} + +static void torture_pki_ed25519_pubkey_dup(void **state) +{ + ssh_key pubkey = NULL; + ssh_key dup = NULL; + const char *p = strchr(torture_get_testkey_pub(SSH_KEYTYPE_ED25519), ' '); + char *pub_str = NULL; + char *q = NULL; + int rc; + + (void) state; /* unused */ + + pub_str = strdup(p + 1); + assert_non_null(pub_str); + + q = strchr(pub_str, ' '); + assert_non_null(q); + *q = '\0'; + + rc = ssh_pki_import_pubkey_base64(pub_str, + SSH_KEYTYPE_ED25519, + &pubkey); + assert_true(rc == 0); + assert_non_null(pubkey); + + rc = ssh_key_is_public(pubkey); + assert_true(rc == 1); + + dup = ssh_key_dup(pubkey); + assert_non_null(dup); + + rc = ssh_key_is_public(dup); + assert_true(rc == 1); + + SAFE_FREE(pub_str); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(dup); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_pubkey_file, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_pubkey_from_openssh_privkey, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_privkey_base64, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_privkey_base64_comment, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_privkey_base64_whitespace, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_export_privkey_base64, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_publickey_from_privatekey, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_cert_file, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_publickey_base64, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_generate_pubkey_from_privkey, + setup_ed25519_key, + teardown), + cmocka_unit_test(torture_pki_ed25519_generate_key), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_cert_verify, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_write_privkey, + setup_ed25519_key, + teardown), + cmocka_unit_test(torture_pki_ed25519_import_privkey_base64_passphrase), + cmocka_unit_test(torture_pki_ed25519_sign), + cmocka_unit_test(torture_pki_ed25519_sign_openssh_privkey_passphrase), +#ifdef HAVE_OPENSSL_ED25519 + cmocka_unit_test(torture_pki_ed25519_sign_pkcs8_privkey), + cmocka_unit_test(torture_pki_ed25519_sign_pkcs8_privkey_passphrase), +#endif + cmocka_unit_test(torture_pki_ed25519_verify), + cmocka_unit_test(torture_pki_ed25519_verify_bad), + cmocka_unit_test(torture_pki_ed25519_privkey_dup), + cmocka_unit_test(torture_pki_ed25519_pubkey_dup), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_pki_rsa.c b/tests/unittests/torture_pki_rsa.c new file mode 100644 index 0000000..a986706 --- /dev/null +++ b/tests/unittests/torture_pki_rsa.c @@ -0,0 +1,1005 @@ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "torture.h" +#include "torture_pki.h" +#include "torture_key.h" +#include "pki.c" + +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_RSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_rsa" + +const char template[] = "temp_dir_XXXXXX"; +const unsigned char INPUT[] = "1234567890123456789012345678901234567890" + "123456789012345678901234"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_rsa_key(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + torture_get_testkey(SSH_KEYTYPE_RSA, 1)); + torture_write_file(LIBSSH_RSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + torture_write_file(LIBSSH_RSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA_CERT01)); + + return 0; +} + +static int setup_openssh_rsa_key(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 1)); + torture_write_file(LIBSSH_RSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + torture_write_file(LIBSSH_RSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA_CERT01)); + + return 0; +} + +static int teardown(void **state) { + + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_rsa_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_import_privkey_base64_NULL_key(void **state) +{ + int rc; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + /* test if it returns -1 if key is NULL */ + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 0), + passphrase, + NULL, + NULL, + NULL); + assert_true(rc == -1); + +} + +static void torture_pki_rsa_import_privkey_base64_NULL_str(void **state) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + /* test if it returns -1 if key_str is NULL */ + rc = ssh_pki_import_privkey_base64(NULL, passphrase, NULL, NULL, &key); + assert_true(rc == -1); + + SSH_KEY_FREE(key); +} + +static void torture_pki_rsa_import_privkey_base64(void **state) +{ + int rc; + char *key_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_RSA_TESTKEY); + assert_non_null(key_str); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + assert_non_null(key); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_RSA); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_key_is_public(key); + assert_true(rc == 1); + + free(key_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_rsa_import_privkey_base64_comment(void **state) +{ + int rc, file_str_len; + const char *comment_str = "#this is line-comment\n#this is another\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_RSA_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(comment_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", comment_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + assert_non_null(key); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_RSA); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_key_is_public(key); + assert_true(rc == 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_rsa_import_privkey_base64_whitespace(void **state) +{ + int rc, file_str_len; + const char *whitespace_str = " \n\t\t\t\t\t\n\n\n\n\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_RSA_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(whitespace_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", whitespace_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + assert_non_null(key); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_RSA); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_key_is_public(key); + assert_true(rc == 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_rsa_publickey_from_privatekey(void **state) +{ + int rc; + ssh_key key = NULL; + ssh_key pubkey = NULL; + const char *passphrase = NULL; + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 0), + passphrase, + NULL, + NULL, + &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_copy_cert_to_privkey(void **state) +{ + /* + * Tests copying a cert loaded into a public key to a private key. + * The function is encryption type agnostic, no need to run this against + * all supported key types. + */ + int rc; + const char *passphrase = torture_get_testkey_passphrase(); + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key cert = NULL; + + (void) state; /* unused */ + + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); + assert_true(rc == SSH_OK); + assert_non_null(cert); + + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 0), + passphrase, + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + /* Basic sanity. */ + rc = ssh_pki_copy_cert_to_privkey(NULL, privkey); + assert_true(rc == SSH_ERROR); + + rc = ssh_pki_copy_cert_to_privkey(pubkey, NULL); + assert_true(rc == SSH_ERROR); + + /* A public key doesn't have a cert, copy should fail. */ + assert_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(pubkey, privkey); + assert_true(rc == SSH_ERROR); + + /* Copying the cert to non-cert keys should work fine. */ + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey->cert); + + /* The private key's cert is already set, another copy should fail. */ + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_true(rc == SSH_ERROR); + + SSH_KEY_FREE(cert); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_import_cert_file(void **state) { + int rc; + ssh_key cert = NULL; + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); + assert_true(rc == 0); + assert_non_null(cert); + + type = ssh_key_type(cert); + assert_true(type == SSH_KEYTYPE_RSA_CERT01); + + rc = ssh_key_is_public(cert); + assert_true(rc == 1); + + SSH_KEY_FREE(cert); +} + +static void torture_pki_rsa_publickey_base64(void **state) +{ + enum ssh_keytypes_e type; + char *b64_key = NULL, *key_buf = NULL, *p = NULL; + const char *q = NULL; + ssh_key key = NULL; + int rc; + + (void) state; /* unused */ + + key_buf = strdup(torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + assert_non_null(key_buf); + + q = p = key_buf; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + type = ssh_key_type_from_name(q); + assert_true(type == SSH_KEYTYPE_RSA); + + q = ++p; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + assert_true(rc == 0); + assert_non_null(b64_key); + + assert_string_equal(q, b64_key); + + free(b64_key); + free(key_buf); + SSH_KEY_FREE(key); +} + +static void torture_pki_rsa_generate_pubkey_from_privkey(void **state) { + char pubkey_generated[4096] = {0}; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + int rc; + int len; + + (void) state; /* unused */ + + /* remove the public key, generate it from the private key and write it. */ + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_RSA_TESTKEY ".pub"); + assert_true(rc == 0); + + rc = torture_read_one_line(LIBSSH_RSA_TESTKEY ".pub", + pubkey_generated, + sizeof(pubkey_generated)); + assert_true(rc == 0); + + len = torture_pubkey_len(torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + assert_memory_equal(torture_get_testkey_pub(SSH_KEYTYPE_RSA), + pubkey_generated, + len); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_duplicate_key(void **state) +{ + int rc; + char *b64_key = NULL; + char *b64_key_gen = NULL; + ssh_key pubkey = NULL; + ssh_key pubkey_dup = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + + (void) state; + + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_true(rc == 0); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_true(rc == 0); + assert_non_null(b64_key); + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey_dup); + assert_true(rc == SSH_OK); + assert_non_null(pubkey_dup); + + rc = ssh_pki_export_pubkey_base64(pubkey_dup, &b64_key_gen); + assert_true(rc == 0); + assert_non_null(b64_key_gen); + + assert_string_equal(b64_key, b64_key_gen); + + rc = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); + assert_true(rc == 0); + + rc = ssh_key_cmp(pubkey, pubkey_dup, SSH_KEY_CMP_PUBLIC); + assert_true(rc == 0); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(pubkey_dup); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); + SSH_STRING_FREE_CHAR(b64_key); + SSH_STRING_FREE_CHAR(b64_key_gen); +} + +static void torture_pki_rsa_generate_key(void **state) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_signature sign = NULL; + ssh_session session=ssh_new(); + (void) state; + + if (!ssh_fips_mode()) { + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 1024, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + key = NULL; + pubkey = NULL; + } + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + key = NULL; + pubkey = NULL; + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 4096, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_true(rc == SSH_OK); + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + key = NULL; + pubkey = NULL; + + ssh_free(session); +} + +static void torture_pki_rsa_sha2(void **state) +{ + int rc; + ssh_key key = NULL, cert = NULL, pubkey = NULL; + ssh_signature sign; + ssh_session session=ssh_new(); + (void) state; + + assert_non_null(session); + + /* Setup */ + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, NULL, NULL, NULL, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); + assert_true(rc == SSH_OK); + assert_non_null(cert); + + /* Get the public key to verify signature */ + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + if (!ssh_fips_mode()) { + /* Sign using old SHA1 digest */ + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA1); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + rc = ssh_pki_signature_verify(session, sign, cert, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + ssh_signature_free(sign); + } + + /* Sign using new SHA256 digest */ + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + rc = ssh_pki_signature_verify(session, sign, cert, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + ssh_signature_free(sign); + + /* Sign using rsa-sha2-512 algorithm */ + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA512); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + rc = ssh_pki_signature_verify(session, sign, cert, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + ssh_signature_free(sign); + + /* Test that it fails when using DIGEST_AUTO */ + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_AUTO); + assert_null(sign); + + /* Test that it fails when using SHA384 */ + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA384); + assert_null(sign); + + /* Cleanup */ + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(cert); + ssh_free(session); +} + +static int test_sign_verify_data(ssh_key key, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len) +{ + ssh_signature sig; + ssh_key pubkey = NULL; + int rc; + + /* Get the public key to verify signature */ + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Sign the buffer */ + sig = pki_sign_data(key, hash_type, input, input_len); + assert_non_null(sig); + + /* Verify signature */ + rc = pki_verify_data_signature(sig, pubkey, input, input_len); + assert_int_equal(rc, SSH_OK); + + ssh_signature_free(sig); + SSH_KEY_FREE(pubkey); + + return rc; +} + +static void torture_pki_sign_data_rsa(void **state) +{ + int rc; + ssh_key key = NULL; + + (void) state; + + /* Setup */ + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + if (!ssh_fips_mode()) { + /* Test using SHA1 */ + rc = test_sign_verify_data(key, SSH_DIGEST_SHA1, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + } + + /* Test using SHA256 */ + rc = test_sign_verify_data(key, SSH_DIGEST_SHA256, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + + /* Test using SHA512 */ + rc = test_sign_verify_data(key, SSH_DIGEST_SHA512, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + + /* Cleanup */ + SSH_KEY_FREE(key); +} + +static void torture_pki_fail_sign_with_incompatible_hash(void **state) +{ + int rc; + ssh_key key = NULL; + ssh_key pubkey = NULL; + ssh_signature sig, bad_sig; + + (void) state; + + /* Setup */ + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + /* Get the public key to verify signature */ + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Sign the buffer */ + sig = pki_sign_data(key, SSH_DIGEST_SHA256, INPUT, sizeof(INPUT)); + assert_non_null(sig); + + /* Verify signature */ + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_AUTO */ + bad_sig = pki_sign_data(key, SSH_DIGEST_AUTO, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_AUTO */ + sig->hash_type = SSH_DIGEST_AUTO; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Cleanup */ + ssh_signature_free(sig); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(key); +} + +#ifdef HAVE_LIBCRYPTO +static void torture_pki_rsa_write_privkey(void **state) +{ + ssh_key origkey = NULL; + ssh_key privkey = NULL; + int rc; + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, + NULL, + NULL, + NULL, + &origkey); + assert_true(rc == 0); + assert_non_null(origkey); + + unlink(LIBSSH_RSA_TESTKEY); + + rc = ssh_pki_export_privkey_file(origkey, + NULL, + NULL, + NULL, + LIBSSH_RSA_TESTKEY); + assert_true(rc == 0); + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_true(rc == 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); + + /* Test with passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &origkey); + assert_true(rc == 0); + assert_non_null(origkey); + + unlink(LIBSSH_RSA_TESTKEY_PASSPHRASE); + rc = ssh_pki_export_privkey_file(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_RSA_TESTKEY_PASSPHRASE); + assert_true(rc == 0); + + /* Test with invalid passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + "invalid secret", + NULL, + NULL, + &privkey); + assert_true(rc == SSH_ERROR); + assert_null(privkey); + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_true(rc == 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); +} +#endif /* HAVE_LIBCRYPTO */ + +static void torture_pki_rsa_import_privkey_base64_passphrase(void **state) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 1), + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 1), + "wrong passphrase !!", + NULL, + NULL, + &key); + assert_true(rc == -1); + SSH_KEY_FREE(key); + +#ifndef HAVE_LIBCRYPTO + /* test if it returns -1 if passphrase is NULL */ + /* libcrypto asks for a passphrase, so skip this test */ + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 1), + NULL, + NULL, + NULL, + &key); + assert_true(rc == -1); + SSH_KEY_FREE(key); +#endif +} + +static void +torture_pki_rsa_import_openssh_privkey_base64_passphrase(void **state) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + const char *keystring = NULL; + + (void) state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 1); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(keystring, + "wrong passphrase !!", + NULL, + NULL, + &key); + assert_true(rc == -1); + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is NULL */ + /* libcrypto asks for a passphrase, so skip this test */ + rc = ssh_pki_import_privkey_base64(keystring, + NULL, + NULL, + NULL, + &key); + assert_true(rc == -1); + SSH_KEY_FREE(key); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_pubkey_file, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_pubkey_from_openssh_privkey, + setup_openssh_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64_NULL_key, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64_NULL_str, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64_comment, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64_whitespace, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64, + setup_openssh_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_publickey_from_privatekey, + setup_rsa_key, + teardown), + cmocka_unit_test(torture_pki_rsa_import_privkey_base64_passphrase), + cmocka_unit_test(torture_pki_rsa_import_openssh_privkey_base64_passphrase), + cmocka_unit_test_setup_teardown(torture_pki_rsa_copy_cert_to_privkey, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_cert_file, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_publickey_base64, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_generate_pubkey_from_privkey, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_duplicate_key, + setup_rsa_key, + teardown), + cmocka_unit_test(torture_pki_rsa_generate_key), +#if defined(HAVE_LIBCRYPTO) + cmocka_unit_test_setup_teardown(torture_pki_rsa_write_privkey, + setup_rsa_key, + teardown), +#endif /* HAVE_LIBCRYPTO */ + cmocka_unit_test(torture_pki_sign_data_rsa), + cmocka_unit_test(torture_pki_fail_sign_with_incompatible_hash), + cmocka_unit_test_setup_teardown(torture_pki_rsa_sha2, + setup_rsa_key, + teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_push_pop_dir.c b/tests/unittests/torture_push_pop_dir.c new file mode 100644 index 0000000..faf1086 --- /dev/null +++ b/tests/unittests/torture_push_pop_dir.c @@ -0,0 +1,78 @@ +#include "config.h" + +#include "torture.h" +#define LIBSSH_STATIC + +const char template[] = "temp_dir_XXXXXX"; + +static int setup(void **state) +{ + char *temp_dir = NULL; + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + *state = (void *)temp_dir; + + return 0; +} + +static int teardown(void **state) +{ + char *temp_dir = *((char **)state); + + torture_rmdirs((const char *)temp_dir); + + free(temp_dir); + + return 0; +} + +static void torture_back_and_forth(void **state) +{ + char *temp_dir = *((char **)state); + char *cwd = NULL; + char *after_change = NULL; + char *after_changing_back = NULL; + int rc = 0; + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + printf("Current dir: %s\n", cwd); + + rc = torture_change_dir(temp_dir); + assert_int_equal(rc, 0); + + after_change = torture_get_current_working_dir(); + assert_non_null(after_change); + + printf("Current dir after change: %s\n", after_change); + + rc = torture_change_dir(cwd); + assert_int_equal(rc, 0); + + after_changing_back = torture_get_current_working_dir(); + assert_non_null(after_changing_back); + + printf("Back to dir: %s\n", after_changing_back); + + SAFE_FREE(cwd); + SAFE_FREE(after_change); + SAFE_FREE(after_changing_back); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_back_and_forth, + setup, teardown), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} + diff --git a/tests/unittests/torture_rand.c b/tests/unittests/torture_rand.c new file mode 100644 index 0000000..58abc7c --- /dev/null +++ b/tests/unittests/torture_rand.c @@ -0,0 +1,83 @@ +#include "config.h" + +#define LIBSSH_STATIC +#include +#include +#include +#include +#include "torture.h" + +#ifdef HAVE_LIBGCRYPT +#define NUM_LOOPS 1000 +#else +/* openssl is much faster */ +#define NUM_LOOPS 20000 +#endif +#define NUM_THREADS 100 + +static int setup(void **state) { + int rc; + + (void) state; + + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + rc = ssh_init(); + if (rc != SSH_OK) { + return -1; + } + + return 0; +} + +static int teardown(void **state) { + (void) state; + + ssh_finalize(); + + return 0; +} + +static void *torture_rand_thread(void *threadid) { + char buffer[12]; + int i; + int ok; + + (void) threadid; + + buffer[0] = buffer[1] = buffer[10] = buffer[11] = 'X'; + for(i = 0; i < NUM_LOOPS; ++i) { + ok = ssh_get_random(&buffer[2], i % 8 + 1, 0); + assert_true(ok); + } + + pthread_exit(NULL); +} + +static void torture_rand_threading(void **state) { + pthread_t threads[NUM_THREADS]; + int i; + int err; + + (void) state; + + for(i = 0; i < NUM_THREADS; ++i) { + err = pthread_create(&threads[i], NULL, torture_rand_thread, NULL); + assert_int_equal(err, 0); + } + for(i = 0; i < NUM_THREADS; ++i) { + err=pthread_join(threads[i], NULL); + assert_int_equal(err, 0); + } +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_rand_threading, setup, teardown), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} diff --git a/tests/unittests/torture_server_x11.c b/tests/unittests/torture_server_x11.c new file mode 100644 index 0000000..72314c5 --- /dev/null +++ b/tests/unittests/torture_server_x11.c @@ -0,0 +1,234 @@ +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include + +#include +#include "torture.h" + +#define TEST_SERVER_PORT 2222 + +struct hostkey_state { + const char *hostkey; + char *hostkey_path; + enum ssh_keytypes_e key_type; + int fd; +}; + +static int setup(void **state) { + struct hostkey_state *h; + mode_t mask; + int rc; + + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + rc = ssh_init(); + if (rc != SSH_OK) { + return -1; + } + + h = malloc(sizeof(struct hostkey_state)); + assert_non_null(h); + + h->hostkey_path = strdup("/tmp/libssh_hostkey_XXXXXX"); + + mask = umask(S_IRWXO | S_IRWXG); + h->fd = mkstemp(h->hostkey_path); + umask(mask); + assert_return_code(h->fd, errno); + close(h->fd); + + h->key_type = SSH_KEYTYPE_ECDSA_P256; + h->hostkey = torture_get_testkey(h->key_type, 0); + + torture_write_file(h->hostkey_path, h->hostkey); + + *state = h; + + return 0; +} + +static int teardown(void **state) { + struct hostkey_state *h = (struct hostkey_state *)*state; + + unlink(h->hostkey); + free(h->hostkey_path); + free(h); + + ssh_finalize(); + + return 0; +} + +/* For x11_screen_number, need something that is not equal to htonl + itself */ +static const uint32_t x11_screen_number = 1; + +static void *client_thread(void *arg) { + unsigned int test_port = TEST_SERVER_PORT; + int rc; + ssh_session session; + ssh_channel channel; + + /* unused */ + (void)arg; + + usleep(200); + session = torture_ssh_session(NULL, "localhost", + &test_port, + "foo", "bar"); + assert_non_null(session); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_int_equal(rc, SSH_OK); + + rc = ssh_channel_request_x11(channel, 0, NULL, NULL, + (uint32_t)x11_screen_number); + assert_int_equal(rc, SSH_OK); + + ssh_free(session); + return NULL; +} + +static int auth_password_accept(ssh_session session, + const char *user, + const char *password, + void *userdata) { + /* unused */ + (void)session; + (void)user; + (void)password; + (void)userdata; + + return SSH_AUTH_SUCCESS; +} + +struct channel_data { + int req_seen; + uint32_t screen_number; +}; + +static void ssh_channel_x11_req(ssh_session session, + ssh_channel channel, + int single_connection, + const char *auth_protocol, + const char *auth_cookie, + uint32_t screen_number, + void *userdata) { + struct channel_data *channel_data = userdata; + + /* unused */ + (void)session; + (void)channel; + (void)single_connection; + (void)auth_protocol; + (void)auth_cookie; + + /* We've seen an x11 request. Record the screen number */ + channel_data->req_seen = 1; + channel_data->screen_number = screen_number; +} + +static ssh_channel channel_open(ssh_session session, void *userdata) { + ssh_channel channel = NULL; + ssh_channel_callbacks channel_cb = userdata; + + /* unused */ + (void)userdata; + + channel = ssh_channel_new(session); + if (channel == NULL) { + goto out; + } + ssh_set_channel_callbacks(channel, channel_cb); + + out: + return channel; +} + +static void test_ssh_channel_request_x11(void **state) { + struct hostkey_state *h = (struct hostkey_state *)*state; + int rc, event_rc; + pthread_t client_pthread; + ssh_bind sshbind; + ssh_session server; + ssh_event event; + + struct channel_data channel_data; + struct ssh_channel_callbacks_struct channel_cb = { + .userdata = &channel_data, + .channel_x11_req_function = ssh_channel_x11_req + }; + struct ssh_server_callbacks_struct server_cb = { + .userdata = &channel_cb, + .auth_password_function = auth_password_accept, + .channel_open_request_session_function = channel_open + }; + + memset(&channel_data, 0, sizeof(channel_data)); + ssh_callbacks_init(&channel_cb); + ssh_callbacks_init(&server_cb); + + /* Create server */ + sshbind = torture_ssh_bind("localhost", + TEST_SERVER_PORT, + h->key_type, + h->hostkey_path); + assert_non_null(sshbind); + + /* Get client to connect */ + rc = pthread_create(&client_pthread, NULL, client_thread, NULL); + assert_return_code(rc, errno); + + server = ssh_new(); + assert_non_null(server); + + rc = ssh_bind_accept(sshbind, server); + assert_int_equal(rc, SSH_OK); + + /* Handle client connection */ + ssh_set_server_callbacks(server, &server_cb); + + rc = ssh_handle_key_exchange(server); + assert_int_equal(rc, SSH_OK); + + event = ssh_event_new(); + assert_non_null(event); + + ssh_event_add_session(event, server); + + event_rc = SSH_OK; + while (!channel_data.req_seen && event_rc == SSH_OK) { + event_rc = ssh_event_dopoll(event, -1); + } + + /* Cleanup */ + ssh_event_free(event); + ssh_free(server); + ssh_bind_free(sshbind); + + rc = pthread_join(client_pthread, NULL); + assert_int_equal(rc, 0); + + assert_true(channel_data.req_seen); + assert_int_equal(channel_data.screen_number, + x11_screen_number); +} + +int torture_run_tests(void) { + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_ssh_channel_request_x11, + setup, + teardown) + }; + + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} + diff --git a/tests/unittests/torture_session_keys.c b/tests/unittests/torture_session_keys.c new file mode 100644 index 0000000..f220e01 --- /dev/null +++ b/tests/unittests/torture_session_keys.c @@ -0,0 +1,97 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/bignum.h" +#include "libssh/crypto.h" +#include "libssh/dh.h" + +uint8_t key[32] = + "\xf7\xa0\xe6\xdf\x1f\x87\x7d\x22\x68\xd2\xc4\xb0\xc5\x93\xa4" + "\x8e\x30\x17\xc6\xab\xca\xf3\x9a\xa4\x9f\x7b\xed\x51\xb1\xe8" + "\x8a\x42"; +uint8_t secret[32] = + "\x33\x64\x8e\x7f\xea\xd9\xd7\xee\x89\x4f\xd8\xd0\xe5\x83\x00" + "\x3d\x53\x17\xbc\xa8\x8b\x6b\x2a\x31\x50\xcc\x08\xe9\xea\x87" + "\xb4\x23"; + +uint8_t eIV[32] = + "\x9a\x2b\x40\x9d\x29\x8e\x22\x70\x86\xdf\x0e\x72\x9b\x91\x31" + "\x90\x5d\x69\xc5\x87\x79\x83\x72\x63\x4e\x67\xf5\x9e\x00\x77" + "\x8c\x7f"; +uint8_t dIV[32] = + "\x10\xdd\x7f\x31\x6d\xe3\x49\x28\xbf\x99\x80\x08\x16\xb3\x99" + "\xff\x8c\x61\x9b\xb9\xc2\xdd\x40\xfb\x36\xf9\x97\xd8\x8c\x55" + "\xbf\xa0"; +uint8_t eK[24] = + "\xe1\x99\x36\xb8\xe6\x1f\x3d\x54\xc3\xa2\xdd\x79\xf0\xfe\x78" + "\x9e\x87\xd5\x05\x54\x26\x34\x21\xd0"; +uint8_t dK[24] = + "\xf8\xdd\xc3\xea\x5a\x59\x98\xb9\x86\xaa\x77\x29\x67\x51\x46" + "\x21\x73\xc2\x6a\x6b\xed\xf2\x49\x98"; +uint8_t eMAC[32] = + "\x0f\xbd\x1f\xe9\x2a\xaa\x84\xdc\xb5\xfc\xfb\x68\x2c\xa5\xe0" + "\xba\xf2\x6f\xe5\x80\xee\x8f\x5c\x5b\x30\x55\x25\xb3\x7b\x21" + "\xdc\xe5"; +uint8_t dMAC[32] = + "\xa3\x52\x6e\x72\xa8\x8b\xde\xc5\x68\x66\x89\xae\x0a\xd2\x83" + "\x23\x21\x4b\x3f\x04\x2e\x7f\x86\x04\x0f\xa8\x04\x3c\x62\xad" + "\x74\x91"; + +struct ssh_cipher_struct fake_in_cipher = { + .keysize = 192 +}; + +struct ssh_cipher_struct fake_out_cipher = { + .keysize = 192 +}; + +struct ssh_crypto_struct test_crypto = { + .digest_len = 32, + .session_id = secret, + .secret_hash = secret, + .in_cipher = &fake_in_cipher, + .out_cipher = &fake_out_cipher, + .in_hmac = SSH_HMAC_SHA256, + .out_hmac = SSH_HMAC_SHA256, + .digest_type = SSH_KDF_SHA256, +}; + +struct ssh_session_struct session = { + .next_crypto = &test_crypto +}; + +static void torture_session_keys(UNUSED_PARAM(void **state)) +{ + ssh_string k_string; + int rc; + + k_string = ssh_string_new(32); + ssh_string_fill(k_string, key, 32); + test_crypto.shared_secret = ssh_make_string_bn(k_string); + + rc = ssh_generate_session_keys(&session); + assert_int_equal(rc, 0); + + assert_memory_equal(test_crypto.encryptIV, eIV, 32); + assert_memory_equal(test_crypto.decryptIV, dIV, 32); + assert_memory_equal(test_crypto.encryptkey, eK, 24); + assert_memory_equal(test_crypto.decryptkey, dK, 24); + assert_memory_equal(test_crypto.encryptMAC, eMAC, 32); + assert_memory_equal(test_crypto.decryptMAC, dMAC, 32); + + SSH_STRING_FREE(k_string); +} + +int torture_run_tests(void) { + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(torture_session_keys), + }; + + ssh_init(); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_temp_dir.c b/tests/unittests/torture_temp_dir.c new file mode 100644 index 0000000..feff23c --- /dev/null +++ b/tests/unittests/torture_temp_dir.c @@ -0,0 +1,51 @@ +#include "config.h" + +#include "torture.h" +#define LIBSSH_STATIC + +const char template[] = "temp_dir_XXXXXX"; + +static int setup(void **state) +{ + char *temp_dir = NULL; + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + *state = (void *)temp_dir; + + return 0; +} + +static int teardown(void **state) +{ + char *temp_dir = *((char **)state); + + torture_rmdirs((const char *)temp_dir); + + free(temp_dir); + + return 0; +} + + +static void torture_create_temp_dir(void **state) +{ + char *temp_dir = *((char **)state); + + printf("Created temp dir: %s\n", temp_dir); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_create_temp_dir, setup, teardown), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} + diff --git a/tests/unittests/torture_temp_file.c b/tests/unittests/torture_temp_file.c new file mode 100644 index 0000000..793d6c1 --- /dev/null +++ b/tests/unittests/torture_temp_file.c @@ -0,0 +1,63 @@ +#include "config.h" + +#include "torture.h" +#define LIBSSH_STATIC + +const char template[] = "temp_file_XXXXXX"; + +static int setup(void **state) +{ + char *file_name = NULL; + + file_name = torture_create_temp_file(template); + assert_non_null(file_name); + + *state = (void *)file_name; + + return 0; +} + +static int teardown(void **state) +{ + int rc; + char *file_name = *((char **)state); + + assert_non_null(file_name); + + rc = unlink(file_name); + assert_int_equal(rc, 0); + + SAFE_FREE(file_name); + + return 0; +} + + +static void torture_temp_file(void **state) +{ + char *file_name = *((char **)state); + FILE *fp = NULL; + + assert_non_null(file_name); + + fp = fopen(file_name, "r"); + assert_non_null(fp); + + fclose(fp); + + printf("Created temp file: %s\n", file_name); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_temp_file, setup, teardown), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} + diff --git a/tests/unittests/torture_threads_buffer.c b/tests/unittests/torture_threads_buffer.c new file mode 100644 index 0000000..de9728f --- /dev/null +++ b/tests/unittests/torture_threads_buffer.c @@ -0,0 +1,600 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#define DEBUG_BUFFER +#include "buffer.c" + +#include + +#define NUM_THREADS 20 + +#define BUFFER_LIMIT (8 * 1024 * 1024) + +static int run_on_threads(void *(*func)(void *)) +{ + pthread_t threads[NUM_THREADS]; + int rc; + int i; + + for (i = 0; i < NUM_THREADS; ++i) { + rc = pthread_create(&threads[i], NULL, func, NULL); + assert_int_equal(rc, 0); + } + + for (i = 0; i < NUM_THREADS; ++i) { + void *p = NULL; + uint64_t *result = NULL; + + rc = pthread_join(threads[i], &p); + assert_int_equal(rc, 0); + + result = (uint64_t *)p; + assert_null(result); + } + + return rc; +} + +/* + * Test if the continuously growing buffer size never exceeds 2 time its + * real capacity + */ +static void *thread_growing_buffer(void *threadid) +{ + ssh_buffer buffer = NULL; + int i; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + for (i = 0; i < BUFFER_LIMIT; ++i) { + ssh_buffer_add_data(buffer,"A",1); + if (buffer->used >= 128) { + if (ssh_buffer_get_len(buffer) * 2 < buffer->allocated) { + assert_true(ssh_buffer_get_len(buffer) * 2 >= buffer->allocated); + } + } + } + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_growing_buffer(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_growing_buffer); + assert_int_equal(rc, 0); +} + +/* + * Test if the continuously growing buffer size never exceeds 2 time its + * real capacity, when we remove 1 byte after each call (sliding window) + */ +static void *thread_growing_buffer_shifting(void *threadid) +{ + ssh_buffer buffer; + int i; + unsigned char c; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + + for (i = 0; i < 1024; ++i) { + ssh_buffer_add_data(buffer,"S",1); + } + + for (i = 0; i < BUFFER_LIMIT; ++i) { + ssh_buffer_get_u8(buffer,&c); + ssh_buffer_add_data(buffer,"A",1); + if (buffer->used >= 128) { + if (ssh_buffer_get_len(buffer) * 4 < buffer->allocated) { + assert_true(ssh_buffer_get_len(buffer) * 4 >= buffer->allocated); + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); + } + } + } + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_growing_buffer_shifting(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_growing_buffer_shifting); + assert_int_equal(rc, 0); +} + +/* + * Test the behavior of ssh_buffer_prepend_data + */ +static void *thread_buffer_prepend(void *threadid) +{ + ssh_buffer buffer = NULL; + uint32_t v; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + ssh_buffer_add_data(buffer, "abcdef", 6); + ssh_buffer_prepend_data(buffer, "xyz", 3); + assert_int_equal(ssh_buffer_get_len(buffer), 9); + assert_memory_equal(ssh_buffer_get(buffer), "xyzabcdef", 9); + + /* Now remove 4 bytes and see if we can replace them */ + ssh_buffer_get_u32(buffer, &v); + assert_int_equal(ssh_buffer_get_len(buffer), 5); + assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5); + + ssh_buffer_prepend_data(buffer, "aris", 4); + assert_int_equal(ssh_buffer_get_len(buffer), 9); + assert_memory_equal(ssh_buffer_get(buffer), "arisbcdef", 9); + + /* same thing but we add 5 bytes now */ + ssh_buffer_get_u32(buffer, &v); + assert_int_equal(ssh_buffer_get_len(buffer), 5); + assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5); + + ssh_buffer_prepend_data(buffer, "12345", 5); + assert_int_equal(ssh_buffer_get_len(buffer), 10); + assert_memory_equal(ssh_buffer_get(buffer), "12345bcdef", 10); + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_buffer_prepend(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_buffer_prepend); + assert_int_equal(rc, 0); +} + +/* + * Test the behavior of ssh_buffer_get_ssh_string with invalid data + */ +static void *thread_ssh_buffer_get_ssh_string(void *threadid) +{ + ssh_buffer buffer = NULL; + size_t i, j, k, l; + int rc; + /* some values that can go wrong */ + uint32_t values[] = { + 0xffffffff, 0xfffffffe, 0xfffffffc, 0xffffff00, + 0x80000000, 0x80000004, 0x7fffffff}; + char data[128] = {0}; + + /* Unused */ + (void)threadid; + + memset(data, 'X', sizeof(data)); + + for (i = 0; i < ARRAY_SIZE(values); ++i) { + for (j = 0; j < (int)sizeof(data); ++j) { + for (k = 1; k < 5; ++k) { + buffer = ssh_buffer_new(); + assert_non_null(buffer); + + for (l = 0; l < k; ++l) { + rc = ssh_buffer_add_u32(buffer, htonl(values[i])); + assert_int_equal(rc, 0); + } + rc = ssh_buffer_add_data(buffer,data,j); + assert_int_equal(rc, 0); + for (l = 0; l < k; ++l) { + ssh_string str = ssh_buffer_get_ssh_string(buffer); + assert_null(str); + SSH_STRING_FREE(str); + } + SSH_BUFFER_FREE(buffer); + } + } + } + + pthread_exit(NULL); +} + +static void torture_ssh_buffer_get_ssh_string(void **state){ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_ssh_buffer_get_ssh_string); + assert_int_equal(rc, 0); +} + +static void *thread_ssh_buffer_add_format(void *threadid) +{ + ssh_buffer buffer = NULL; + uint8_t b; + uint16_t w; + uint32_t d; + uint64_t q; + ssh_string s = NULL; + int rc; + size_t len; + uint8_t verif[] = "\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46" + "\xac\xbd\xce\xdf" + "\x00\x00\x00\x06" "libssh" + "\x00\x00\x00\x05" "rocks" + "So much" + "Fun!"; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + b = 0x42; + w = 0x1337; + d = 0xbadc0de; + q = 0x13243546acbdcedf; + s = ssh_string_from_char("libssh"); + rc = ssh_buffer_pack(buffer, + "bwdqSsPt", + b, + w, + d, + q, + s, + "rocks", + 7, + "So much", + "Fun!"); + assert_int_equal(rc, SSH_OK); + + len = ssh_buffer_get_len(buffer); + assert_int_equal(len, sizeof(verif) - 1); + assert_memory_equal(ssh_buffer_get(buffer), verif, sizeof(verif) -1); + + SSH_STRING_FREE(s); + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_ssh_buffer_add_format(void **state){ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_ssh_buffer_add_format); + assert_int_equal(rc, 0); +} + +static void *thread_ssh_buffer_get_format(void *threadid) { + ssh_buffer buffer; + uint8_t b = 0; + uint16_t w = 0; + uint32_t d = 0; + uint64_t q = 0; + ssh_string s = NULL; + char *s1 = NULL, *s2 = NULL; + int rc; + size_t len; + uint8_t verif[] = "\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46" + "\xac\xbd\xce\xdf" + "\x00\x00\x00\x06" "libssh" + "\x00\x00\x00\x05" "rocks" + "So much"; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + rc = ssh_buffer_add_data(buffer, verif, sizeof(verif) - 1); + assert_int_equal(rc, SSH_OK); + + rc = ssh_buffer_unpack(buffer, + "bwdqSsP", + &b, + &w, + &d, + &q, + &s, + &s1, + (size_t)7, + &s2); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(b, 0x42); + assert_int_equal(w, 0x1337); + + assert_true(d == 0xbadc0de); + assert_true(q == 0x13243546acbdcedf); + + assert_non_null(s); + assert_int_equal(ssh_string_len(s), 6); + assert_memory_equal(ssh_string_data(s), "libssh", 6); + + assert_non_null(s1); + assert_string_equal(s1, "rocks"); + + assert_non_null(s2); + assert_memory_equal(s2, "So much", 7); + + len = ssh_buffer_get_len(buffer); + assert_int_equal(len, 0); + SAFE_FREE(s); + SAFE_FREE(s1); + SAFE_FREE(s2); + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_ssh_buffer_get_format(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_ssh_buffer_get_format); + assert_int_equal(rc, 0); +} + +static void *thread_ssh_buffer_get_format_error(void *threadid) +{ + ssh_buffer buffer = NULL; + uint8_t b = 0; + uint16_t w = 0; + uint32_t d = 0; + uint64_t q = 0; + ssh_string s = NULL; + char *s1 = NULL, *s2 = NULL; + int rc; + uint8_t verif[] = "\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46" + "\xac\xbd\xce\xdf" + "\x00\x00\x00\x06" "libssh" + "\x00\x00\x00\x05" "rocks" + "So much"; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + rc = ssh_buffer_add_data(buffer, verif, sizeof(verif) - 1); + assert_int_equal(rc, SSH_OK); + rc = ssh_buffer_unpack(buffer, + "bwdqSsPb", + &b, + &w, + &d, + &q, + &s, + &s1, + (size_t)7, + &s2, + &b); + assert_int_equal(rc, SSH_ERROR); + + assert_null(s); + assert_null(s1); + assert_null(s2); + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_ssh_buffer_get_format_error(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_ssh_buffer_get_format_error); + assert_int_equal(rc, 0); +} + +static void *thread_buffer_pack_badformat(void *threadid) +{ + ssh_buffer buffer = NULL; + uint8_t b = 42; + int rc; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + /* first with missing format */ + rc = ssh_buffer_pack(buffer, "b", b, b); + assert_int_equal(rc, SSH_ERROR); + ssh_buffer_reinit(buffer); + + /* with additional format */ + rc = ssh_buffer_pack(buffer, "bb", b); + /* check that we detect the missing parameter */ + assert_int_equal(rc, SSH_ERROR); + + /* unpack with missing format */ + ssh_buffer_reinit(buffer); + + rc = ssh_buffer_pack(buffer, "bb", 42, 43); + assert_int_equal(rc, SSH_OK); + + rc = ssh_buffer_unpack(buffer, "b", &b, &b); + assert_int_equal(rc, SSH_ERROR); + + /* not doing the test with additional format as + * it could crash the process */ + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_buffer_pack_badformat(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_buffer_pack_badformat); + assert_int_equal(rc, 0); +} + +#define NUM_TESTS 8 + +static void torture_mixed(void **state) +{ + pthread_t threads[NUM_TESTS][NUM_THREADS]; + int i; + int f; + int rc; + + /* Array of functions to run on threads */ + static void *(*funcs[NUM_TESTS])(void *) = { + thread_growing_buffer, + thread_growing_buffer_shifting, + thread_buffer_prepend, + thread_ssh_buffer_get_ssh_string, + thread_ssh_buffer_add_format, + thread_ssh_buffer_get_format, + thread_ssh_buffer_get_format_error, + thread_buffer_pack_badformat + }; + + (void) state; + + /* Call tests in a round-robin fashion */ + for (i = 0; i < NUM_THREADS; ++i) { + for (f = 0; f < NUM_TESTS; f++) { + rc = pthread_create(&threads[f][i], NULL, funcs[f], NULL); + assert_int_equal(rc, 0); + } + } + + for (f = 0; f < NUM_TESTS; f++) { + for (i = 0; i < NUM_THREADS; ++i) { + void *p = NULL; + uint64_t *result = NULL; + + rc = pthread_join(threads[f][i], &p); + assert_int_equal(rc, 0); + + result = (uint64_t *)p; + assert_null(result); + } + } +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_growing_buffer), + cmocka_unit_test(torture_growing_buffer_shifting), + cmocka_unit_test(torture_buffer_prepend), + cmocka_unit_test(torture_ssh_buffer_get_ssh_string), + cmocka_unit_test(torture_ssh_buffer_add_format), + cmocka_unit_test(torture_ssh_buffer_get_format), + cmocka_unit_test(torture_ssh_buffer_get_format_error), + cmocka_unit_test(torture_buffer_pack_badformat), + cmocka_unit_test(torture_mixed), + }; + + /* + * If the library is statically linked, ssh_init() is not called + * automatically + */ + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/tests/unittests/torture_threads_crypto.c b/tests/unittests/torture_threads_crypto.c new file mode 100644 index 0000000..871f292 --- /dev/null +++ b/tests/unittests/torture_threads_crypto.c @@ -0,0 +1,205 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/crypto.h" + +#include + +#define NUM_THREADS 100 + +static int8_t key[32] = + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e" + "\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d" + "\x1e\x1f"; + +static uint8_t IV[16] = + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e" + "\x1f"; + +static uint8_t cleartext[144] = + "\xb4\xfc\x5d\xc2\x49\x8d\x2c\x29\x4a\xc9\x9a\xb0\x1b\xf8\x29" + "\xee\x85\x6d\x8c\x04\x34\x7c\x65\xf4\x89\x97\xc5\x71\x70\x41" + "\x91\x40\x19\x60\xe1\xf1\x8f\x4d\x8c\x17\x51\xd6\xbc\x69\x6e" + "\xf2\x21\x87\x18\x6c\xef\xc4\xf4\xd9\xe6\x1b\x94\xf7\xd8\xb2" + "\xe9\x24\xb9\xe7\xe6\x19\xf5\xec\x55\x80\x9a\xc8\x7d\x70\xa3" + "\x50\xf8\x03\x10\x35\x49\x9b\x53\x58\xd7\x4c\xfc\x5f\x02\xd6" + "\x28\xea\xcc\x43\xee\x5e\x2b\x8a\x7a\x66\xf7\x00\xee\x09\x18" + "\x30\x1b\x47\xa2\x16\x69\xc4\x6e\x44\x3f\xbd\xec\x52\xce\xe5" + "\x41\xf2\xe0\x04\x4f\x5a\x55\x58\x37\xba\x45\x8d\x15\x53\xf6" + "\x31\x91\x13\x8c\x51\xed\x08\x07\xdb"; + +static uint8_t aes256_cbc_encrypted[144] = + "\x7f\x1b\x92\xac\xc5\x16\x05\x55\x74\xac\xb4\xe0\x91\x8c\xf8" + "\x0d\xa9\x72\xa5\x09\xb8\x44\xee\x55\x02\x13\xb7\x52\x0a\xf0" + "\xac\xd0\x21\x0e\x58\x7b\x34\xfe\xdb\x36\x01\x60\x7d\x18\x3a" + "\xa9\x15\x18\x5b\x13\xca\xdd\x77\x7d\xdf\x64\xc6\xd5\x75\x4b" + "\x02\x02\x37\xb1\xf4\x33\xff\x93\xe6\x32\x08\xda\xcb\x5d\xa2" + "\x8f\x17\x1f\x99\x92\x60\x22\x9d\x6b\xe6\xb2\x5e\xb0\x5d\x26" + "\x3f\xde\xb8\xc1\xb0\x70\x80\x1c\x00\xd0\x93\x2b\xeb\x0f\xd7" + "\x70\x7a\x9a\x7a\xa6\x21\x23\x2c\x02\xb7\xcd\x88\x10\x9c\x2d" + "\x0c\xd3\xfa\xc1\x33\x5b\xe1\xa1\xd4\x3d\x8f\xb8\x50\xc5\xb5" + "\x72\xdd\x6d\x32\x1f\x58\x00\x48\xbe"; + +static int run_on_threads(void *(*func)(void *)) +{ + pthread_t threads[NUM_THREADS]; + int rc; + int i; + + for (i = 0; i < NUM_THREADS; ++i) { + rc = pthread_create(&threads[i], NULL, func, NULL); + assert_int_equal(rc, 0); + } + + for (i = 0; i < NUM_THREADS; ++i) { + void *p = NULL; + uint64_t *result; + + rc = pthread_join(threads[i], &p); + assert_int_equal(rc, 0); + + result = (uint64_t *)p; + assert_null(result); + } + + return rc; +} + +static int get_cipher(struct ssh_cipher_struct *cipher, const char *ciphername) +{ + struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); + int i, cmp; + + for (i = 0; ciphers[i].name != NULL; i++) { + cmp = strcmp(ciphername, ciphers[i].name); + if (cmp == 0) { + memcpy(cipher, &ciphers[i], sizeof(*cipher)); + return SSH_OK; + } + } + + return SSH_ERROR; +} + +static void *thread_crypto_aes256_cbc(void *threadid) +{ + uint8_t output[sizeof(cleartext)] = {0}; + uint8_t iv[16] = {0}; + struct ssh_cipher_struct cipher = { + .name = NULL, + }; + int rc; + + /* Unused */ + (void) threadid; + + rc = get_cipher(&cipher, "aes256-cbc"); + assert_int_equal(rc, SSH_OK); + assert_non_null(cipher.set_encrypt_key); + assert_non_null(cipher.encrypt); + + /* This is for dump static analizyer without modelling support */ + if (cipher.set_encrypt_key == NULL || + cipher.encrypt == NULL) { + return NULL; + } + + memcpy(iv, IV, sizeof(IV)); + cipher.set_encrypt_key(&cipher, + key, + iv + ); + + cipher.encrypt(&cipher, + cleartext, + output, + sizeof(cleartext) + ); + + assert_memory_equal(output, + aes256_cbc_encrypted, + sizeof(aes256_cbc_encrypted)); + ssh_cipher_clear(&cipher); + + rc = get_cipher(&cipher, "aes256-cbc"); + assert_int_equal(rc, SSH_OK); + assert_non_null(cipher.set_encrypt_key); + assert_non_null(cipher.encrypt); + + /* This is for dump static analizyer without modelling support */ + if (cipher.set_encrypt_key == NULL || + cipher.encrypt == NULL) { + return NULL; + } + + memcpy(iv, IV, sizeof(IV)); + cipher.set_decrypt_key(&cipher, + key, + iv + ); + + memset(output, '\0', sizeof(output)); + cipher.decrypt(&cipher, + aes256_cbc_encrypted, + output, + sizeof(aes256_cbc_encrypted) + ); + + assert_memory_equal(output, cleartext, sizeof(cleartext)); + + ssh_cipher_clear(&cipher); + + pthread_exit(NULL); +} + +static void torture_crypto_aes256_cbc(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_crypto_aes256_cbc); + assert_int_equal(rc, 0); +} + +int torture_run_tests(void) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(torture_crypto_aes256_cbc), + }; + + /* + * If the library is statically linked, ssh_init() is not called + * automatically + */ + ssh_init(); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/tests/unittests/torture_threads_init.c b/tests/unittests/torture_threads_init.c new file mode 100644 index 0000000..e49bde1 --- /dev/null +++ b/tests/unittests/torture_threads_init.c @@ -0,0 +1,98 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" + +#include + +#define NUM_THREADS 20 + +static int run_on_threads(void *(*func)(void *)) +{ + pthread_t threads[NUM_THREADS]; + int rc; + int i; + + for (i = 0; i < NUM_THREADS; ++i) { + rc = pthread_create(&threads[i], NULL, func, NULL); + assert_int_equal(rc, 0); + } + + for (i = 0; i < NUM_THREADS; ++i) { + void *p = NULL; + uint64_t *result; + + rc = pthread_join(threads[i], &p); + assert_int_equal(rc, 0); + + result = (uint64_t *)p; + assert_null(result); + } + + return rc; +} + +static void *thread_ssh_init(UNUSED_PARAM(void *threadid)) +{ + int rc; + + (void) threadid; + + rc = ssh_init(); + assert_int_equal(rc, SSH_OK); + + rc = ssh_finalize(); + assert_int_equal(rc, SSH_OK); + + pthread_exit(NULL); +} + +static void torture_ssh_init(UNUSED_PARAM(void **state)) +{ + int rc; + + rc = run_on_threads(thread_ssh_init); + assert_int_equal(rc, 0); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_ssh_init), + }; + + /* + * If the library is statically linked, ssh_init() is not called + * automatically + */ + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/tests/unittests/torture_threads_pki_rsa.c b/tests/unittests/torture_threads_pki_rsa.c new file mode 100644 index 0000000..a04a746 --- /dev/null +++ b/tests/unittests/torture_threads_pki_rsa.c @@ -0,0 +1,810 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "torture.h" +#include "torture_pki.h" +#include "torture_key.h" +#include "pki.c" + +#include + +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_RSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_rsa" + +#define NUM_THREADS 10 + +const char template[] = "temp_dir_XXXXXX"; +const unsigned char RSA_HASH[] = "12345678901234567890"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int run_on_threads(void *(*func)(void *)) +{ + pthread_t threads[NUM_THREADS]; + int rc; + int i; + + for (i = 0; i < NUM_THREADS; ++i) { + rc = pthread_create(&threads[i], NULL, func, NULL); + assert_int_equal(rc, 0); + } + + for (i = 0; i < NUM_THREADS; ++i) { + void *p = NULL; + uint64_t *result; + + rc = pthread_join(threads[i], &p); + assert_int_equal(rc, 0); + + result = (uint64_t *)p; + assert_null(result); + } + + return rc; +} + +static int setup_rsa_key(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + torture_get_testkey(SSH_KEYTYPE_RSA, 1)); + torture_write_file(LIBSSH_RSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + torture_write_file(LIBSSH_RSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA_CERT01)); + + return 0; +} + +static int teardown(void **state) { + + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static int disable_secmem(void **state) +{ + (void) state; /*unused*/ + +#if defined(HAVE_LIBGCRYPT) + /* gcrypt currently is configured to use only 4kB of locked secmem + * (see ssh_crypto_init() in src/libcrypt.c) + * + * This is insufficient to run the RSA key generation in many threads. + * To avoid the expected warning, disable the secure memory. + * */ + + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + gcry_control(GCRYCTL_DISABLE_SECMEM); +#endif + + return 0; +} + +static int enable_secmem(void **state) +{ + (void) state; /*unused*/ + +#if defined(HAVE_LIBGCRYPT) + /* Re-enable secmem */ + gcry_control(GCRYCTL_INIT_SECMEM, 4096); + gcry_control(GCRYCTL_RESUME_SECMEM_WARN); +#endif + return 0; +} + +static void *thread_pki_rsa_import_pubkey_file(void *threadid) +{ + ssh_key pubkey = NULL; + int rc; + + (void) threadid; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); + + pthread_exit(NULL); +} + +static void torture_pki_rsa_import_pubkey_file(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_pubkey_file); + assert_int_equal(rc, 0); +} + + +static void *thread_pki_rsa_import_privkey_base64_NULL_key(void *threadid) +{ + int rc; + const char *passphrase = torture_get_testkey_passphrase(); + const char *testkey; + + (void) threadid; /* unused */ + + testkey = torture_get_testkey(SSH_KEYTYPE_RSA, 0); + assert_non_null(testkey); + + /* test if it returns -1 if key is NULL */ + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + NULL); + assert_true(rc == -1); + + pthread_exit(NULL); +} + +static void torture_pki_rsa_import_privkey_base64_NULL_key(void **state){ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_privkey_base64_NULL_key); + assert_int_equal(rc, 0); +} + + +static void *thread_pki_rsa_import_privkey_base64_NULL_str(void *threadid) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) threadid; /* unused */ + + /* test if it returns -1 if key_str is NULL */ + rc = ssh_pki_import_privkey_base64(NULL, passphrase, NULL, NULL, &key); + assert_true(rc == -1); + + SSH_KEY_FREE(key); + pthread_exit(NULL); +} + +static void torture_pki_rsa_import_privkey_base64_NULL_str(void **state){ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_privkey_base64_NULL_str); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_import_privkey_base64(void *threadid) +{ + const char *passphrase = torture_get_testkey_passphrase(); + char *key_str = NULL; + ssh_key key = NULL; + enum ssh_keytypes_e type; + int ok; + int rc; + + (void) threadid; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_RSA_TESTKEY); + assert_non_null(key_str); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_RSA); + + ok = ssh_key_is_private(key); + assert_true(ok); + + ok = ssh_key_is_public(key); + assert_true(ok); + + free(key_str); + SSH_KEY_FREE(key); + + pthread_exit(NULL); +} + +static void torture_pki_rsa_import_privkey_base64(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_privkey_base64); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_publickey_from_privatekey(void *threadid) +{ + const char *passphrase = NULL; + const char *testkey; + ssh_key pubkey = NULL; + ssh_key key = NULL; + int rc; + int ok; + + (void) threadid; /* unused */ + + testkey = torture_get_testkey(SSH_KEYTYPE_RSA, 0); + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + &key); + assert_true(rc == 0); + assert_non_null(key); + + ok = ssh_key_is_private(key); + assert_true(ok); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + pthread_exit(NULL); +} + +static void torture_pki_rsa_publickey_from_privatekey(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_publickey_from_privatekey); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_copy_cert_to_privkey(void *threadid) +{ + /* + * Tests copying a cert loaded into a public key to a private key. + * The function is encryption type agnostic, no need to run this against + * all supported key types. + */ + const char *passphrase = torture_get_testkey_passphrase(); + const char *testkey = NULL; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key cert = NULL; + int rc; + + (void) threadid; /* unused */ + + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); + assert_true(rc == SSH_OK); + assert_non_null(cert); + + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + testkey = torture_get_testkey(SSH_KEYTYPE_RSA, 0); + assert_non_null(testkey); + + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + /* Basic sanity. */ + rc = ssh_pki_copy_cert_to_privkey(NULL, privkey); + assert_true(rc == SSH_ERROR); + + rc = ssh_pki_copy_cert_to_privkey(pubkey, NULL); + assert_true(rc == SSH_ERROR); + + /* A public key doesn't have a cert, copy should fail. */ + rc = ssh_pki_copy_cert_to_privkey(pubkey, privkey); + assert_true(rc == SSH_ERROR); + + /* Copying the cert to non-cert keys should work fine. */ + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_true(rc == SSH_OK); + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_true(rc == SSH_OK); + + /* The private key's cert is already set, another copy should fail. */ + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_true(rc == SSH_ERROR); + + SSH_KEY_FREE(cert); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + pthread_exit(NULL); +} + +static void torture_pki_rsa_copy_cert_to_privkey(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_copy_cert_to_privkey); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_import_cert_file(void *threadid) +{ + int rc; + ssh_key cert = NULL; + enum ssh_keytypes_e type; + + (void) threadid; /* unused */ + + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); + assert_true(rc == 0); + assert_non_null(cert); + + type = ssh_key_type(cert); + assert_true(type == SSH_KEYTYPE_RSA_CERT01); + + rc = ssh_key_is_public(cert); + assert_true(rc == 1); + + SSH_KEY_FREE(cert); + pthread_exit(NULL); +} + +static void torture_pki_rsa_import_cert_file(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_cert_file); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_publickey_base64(void *threadid) +{ + enum ssh_keytypes_e type; + char *b64_key = NULL, *key_buf = NULL, *p = NULL; + const char *q = NULL; + ssh_key key; + int rc; + + (void) threadid; /* unused */ + + key_buf = strdup(torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + assert_non_null(key_buf); + + q = p = key_buf; + while (*p != ' ') p++; + *p = '\0'; + + type = ssh_key_type_from_name(q); + assert_true(type == SSH_KEYTYPE_RSA); + + q = ++p; + while (*p != ' ') p++; + *p = '\0'; + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + assert_true(rc == 0); + assert_non_null(b64_key); + + assert_string_equal(q, b64_key); + + free(b64_key); + free(key_buf); + SSH_KEY_FREE(key); + pthread_exit(NULL); +} + +static void torture_pki_rsa_publickey_base64(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_publickey_base64); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_duplicate_key(void *threadid) +{ + char *b64_key = NULL; + char *b64_key_gen = NULL; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + int cmp; + int rc; + + (void) threadid; + + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_true(rc == 0); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_true(rc == 0); + SSH_KEY_FREE(pubkey); + assert_non_null(b64_key); + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key_gen); + assert_true(rc == 0); + assert_non_null(b64_key_gen); + + assert_string_equal(b64_key, b64_key_gen); + + cmp = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); + assert_true(cmp == 0); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); + SSH_STRING_FREE_CHAR(b64_key); + SSH_STRING_FREE_CHAR(b64_key_gen); + pthread_exit(NULL); +} + +static void torture_pki_rsa_duplicate_key(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_duplicate_key); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_generate_key(void *threadid) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_signature sign = NULL; + ssh_session session = NULL; + + (void) threadid; + + session = ssh_new(); + assert_non_null(session); + + if (!ssh_fips_mode()) { + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 1024, &key); + assert_ssh_return_code(session, rc); + assert_non_null(key); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + sign = pki_do_sign(key, RSA_HASH, 20, SSH_DIGEST_SHA256); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, RSA_HASH, 20); + assert_ssh_return_code(session, rc); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + } + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_ssh_return_code(session, rc); + assert_non_null(key); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + sign = pki_do_sign(key, RSA_HASH, 20, SSH_DIGEST_SHA256); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, RSA_HASH, 20); + assert_ssh_return_code(session, rc); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 4096, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + sign = pki_do_sign(key, RSA_HASH, 20, SSH_DIGEST_SHA256); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, RSA_HASH, 20); + assert_true(rc == SSH_OK); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + ssh_free(session); + pthread_exit(NULL); +} + +static void torture_pki_rsa_generate_key(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_generate_key); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_import_privkey_base64_passphrase(void *threadid) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + const char *testkey; + + (void) threadid; /* unused */ + + testkey = torture_get_testkey(SSH_KEYTYPE_RSA, 1); + assert_non_null(testkey); + + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(testkey, + "wrong passphrase !!", + NULL, + NULL, + &key); + assert_true(rc == -1); + SSH_KEY_FREE(key); + +#ifndef HAVE_LIBCRYPTO + /* test if it returns -1 if passphrase is NULL */ + /* libcrypto asks for a passphrase, so skip this test */ + rc = ssh_pki_import_privkey_base64(testkey, + NULL, + NULL, + NULL, + &key); + assert_true(rc == -1); + SSH_KEY_FREE(key); +#endif + pthread_exit(NULL); +} + +static void torture_pki_rsa_import_privkey_base64_passphrase(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_privkey_base64_passphrase); + assert_int_equal(rc, 0); +} + +#define NUM_TESTS 11 + +static void torture_mixed(void **state) +{ + pthread_t threads[NUM_TESTS][NUM_THREADS]; + + int i; + int f; + int rc; + + /* Array of functions to run on threads */ + static void *(*funcs[NUM_TESTS])(void *) = { + thread_pki_rsa_import_pubkey_file, + thread_pki_rsa_import_privkey_base64_NULL_key, + thread_pki_rsa_import_privkey_base64_NULL_str, + thread_pki_rsa_import_privkey_base64, + thread_pki_rsa_publickey_from_privatekey, + thread_pki_rsa_import_privkey_base64_passphrase, + thread_pki_rsa_copy_cert_to_privkey, + thread_pki_rsa_import_cert_file, + thread_pki_rsa_publickey_base64, + thread_pki_rsa_duplicate_key, + thread_pki_rsa_generate_key, + }; + + (void) state; + + /* Call tests in a round-robin fashion */ + for (i = 0; i < NUM_THREADS; ++i) { + for (f = 0; f < NUM_TESTS; f++) { + rc = pthread_create(&threads[f][i], NULL, funcs[f], NULL); + assert_int_equal(rc, 0); + } + } + + for (f = 0; f < NUM_TESTS; f++) { + for (i = 0; i < NUM_THREADS; ++i) { + void *p = NULL; + uint64_t *result = NULL; + + rc = pthread_join(threads[f][i], &p); + assert_int_equal(rc, 0); + + result = (uint64_t *)p; + assert_null(result); + } + } +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_pubkey_file, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64_NULL_key, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64_NULL_str, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_publickey_from_privatekey, + setup_rsa_key, + teardown), + cmocka_unit_test(torture_pki_rsa_import_privkey_base64_passphrase), + cmocka_unit_test_setup_teardown(torture_pki_rsa_copy_cert_to_privkey, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_cert_file, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_publickey_base64, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_duplicate_key, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_generate_key, + disable_secmem, + enable_secmem), + cmocka_unit_test_setup_teardown(torture_mixed, + setup_rsa_key, + teardown), + }; + + /* + * Not testing: + * - pki_rsa_generate_pubkey_from_privkey + * - pki_rsa_write_privkey + * + * The original tests in torture_pki_rsa.c require files to be erased + */ + + /* + * If the library is statically linked, ssh_init() is not called + * automatically + */ + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/tests/unittests/torture_tokens.c b/tests/unittests/torture_tokens.c new file mode 100644 index 0000000..6b52b84 --- /dev/null +++ b/tests/unittests/torture_tokens.c @@ -0,0 +1,285 @@ +/* + * torture_tokens.c - Tests for tokens list handling + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/token.h" +#include "libssh/priv.h" + +static void torture_find_matching(UNUSED_PARAM(void **state)) +{ + char *matching; + + /* Match with single token */ + matching = ssh_find_matching("a,b,c", "b"); + assert_non_null(matching); + assert_string_equal(matching, "b"); + SAFE_FREE(matching); + + /* Match sequence, get first preferred */ + matching = ssh_find_matching("a,b,c", "b,c"); + assert_non_null(matching); + assert_string_equal(matching, "b"); + SAFE_FREE(matching); + + /* Only one token allowed */ + matching = ssh_find_matching("c", "a,b,c"); + assert_non_null(matching); + assert_string_equal(matching, "c"); + SAFE_FREE(matching); + + /* Different order in allowed and preferred; gets preferred */ + matching = ssh_find_matching("c,b,a", "a,b,c"); + assert_non_null(matching); + assert_string_equal(matching, "a"); + SAFE_FREE(matching); + + /* No matching returns NULL */ + matching = ssh_find_matching("c,b,a", "d,e,f"); + assert_null(matching); +} + +static void torture_find_all_matching(UNUSED_PARAM(void **state)) +{ + char *matching; + + /* Match with single token */ + matching = ssh_find_all_matching("a,b,c", "b"); + assert_non_null(matching); + assert_string_equal(matching, "b"); + SAFE_FREE(matching); + + /* Match sequence, get first preferred */ + matching = ssh_find_all_matching("a,b,c", "b,c"); + assert_non_null(matching); + assert_string_equal(matching, "b,c"); + SAFE_FREE(matching); + + /* Only one token allowed */ + matching = ssh_find_all_matching("c", "a,b,c"); + assert_non_null(matching); + assert_string_equal(matching, "c"); + SAFE_FREE(matching); + + /* Different order in allowed and preferred; gets preferred */ + matching = ssh_find_all_matching("c,b,a", "a,c,b"); + assert_non_null(matching); + assert_string_equal(matching, "a,c,b"); + SAFE_FREE(matching); + + /* No matching returns NULL */ + matching = ssh_find_all_matching("c,b,a", "d,e,f"); + assert_null(matching); +} + +static void tokenize_compare_expected(const char *chain, const char **expected, + size_t num_expected) +{ + struct ssh_tokens_st *tokens; + size_t i; + + tokens = ssh_tokenize(chain, ','); + assert_non_null(tokens); + + if (expected != NULL) { + assert_non_null(tokens->tokens); + for (i = 0; i < num_expected; i++) { + assert_non_null(tokens->tokens[i]); + assert_non_null(expected[i]); + assert_string_equal(tokens->tokens[i], expected[i]); + } + + assert_null(tokens->tokens[i]); + + i = 0; + printf("Tokenizing \"%s\" resulted in: ", chain); + while (tokens->tokens[i]) { + printf("\"%s\" ", tokens->tokens[i++]); + } + printf("\n"); + } + + ssh_tokens_free(tokens); +} + +static void torture_tokens_sanity(UNUSED_PARAM(void **state)) +{ + const char *simple[] = {"a", "b", "c"}; + const char *colon_first[] = {"", "a", "b", "c"}; + const char *colon_end[] = {"a", "b", "c"}; + const char *colon_both[] = {"", "a", "b", "c"}; + const char *single[] = {"abc"}; + const char *empty[] = {""}; + const char *single_colon[] = {""}; + + tokenize_compare_expected("a,b,c", simple, 3); + tokenize_compare_expected(",a,b,c", colon_first, 4); + tokenize_compare_expected("a,b,c,", colon_end, 3); + tokenize_compare_expected(",a,b,c,", colon_both, 4); + tokenize_compare_expected("abc", single, 1); + tokenize_compare_expected("", empty, 1); + tokenize_compare_expected(",", single_colon, 1); +} + +static void torture_remove_duplicate(UNUSED_PARAM(void **state)) +{ + + const char *simple[] = {"a,a,b,b,c,c", + "a,b,c,a,b,c", + "a,b,c,c,b,a", + "a,a,,b,b,,c,c", + ",a,a,b,b,c,c", + "a,a,b,b,c,c,"}; + const char *empty[] = {"", + ",,,,,,,,,", + NULL}; + char *ret = NULL; + int i; + + for (i = 0; i < 6; i++) { + ret = ssh_remove_duplicates(simple[i]); + assert_non_null(ret); + assert_string_equal("a,b,c", ret); + printf("simple[%d] resulted in '%s'\n", i, ret); + SAFE_FREE(ret); + } + + for (i = 0; i < 3; i++) { + ret = ssh_remove_duplicates(empty[i]); + if (ret != NULL) { + printf("empty[%d] resulted in '%s'\n", i, ret); + } + assert_null(ret); + } + + ret = ssh_remove_duplicates("a"); + assert_non_null(ret); + assert_string_equal("a", ret); + SAFE_FREE(ret); +} + +static void torture_append_without_duplicate(UNUSED_PARAM(void **state)) +{ + const char *s1[] = {"a,a,b,b,c,c", + "a,b,c,a,b,c", + "a,b,c,c,b,a", + "a,a,,b,b,,c,c", + ",a,a,b,b,c,c", + "a,a,b,b,c,c,"}; + const char *s2[] = {"a,a,b,b,c,c,d,d", + "a,b,c,d,a,b,c,d", + "a,b,c,d,d,c,b,a", + "a,a,,b,b,,c,c,,d,d", + ",a,a,b,b,c,c,d,d", + "a,a,b,b,c,c,d,d,", + "d"}; + const char *empty[] = {"", + ",,,,,,,,,", + NULL, + NULL}; + char *ret = NULL; + int i, j; + + ret = ssh_append_without_duplicates("a", "a"); + assert_non_null(ret); + assert_string_equal("a", ret); + SAFE_FREE(ret); + + ret = ssh_append_without_duplicates("a", "b"); + assert_non_null(ret); + assert_string_equal("a,b", ret); + SAFE_FREE(ret); + + ret = ssh_append_without_duplicates("a", NULL); + assert_non_null(ret); + assert_string_equal("a", ret); + SAFE_FREE(ret); + + ret = ssh_append_without_duplicates(NULL, "b"); + assert_non_null(ret); + assert_string_equal("b", ret); + SAFE_FREE(ret); + + for (i = 0; i < 6; i++) { + for (j = 0; j < 7; j++) { + ret = ssh_append_without_duplicates(s1[i], s2[j]); + assert_non_null(ret); + printf("s1[%d] + s2[%d] resulted in '%s'\n", i, j, ret); + assert_string_equal("a,b,c,d", ret); + SAFE_FREE(ret); + } + } + + for (i = 0; i < 6; i++) { + for (j = 0; j < 3; j++) { + ret = ssh_append_without_duplicates(s1[i], empty[j]); + assert_non_null(ret); + printf("s1[%d] + empty[%d] resulted in '%s'\n", i, j, ret); + assert_string_equal("a,b,c", ret); + SAFE_FREE(ret); + } + } + + for (i = 0; i < 3; i++) { + for (j = 0; j < 6; j++) { + ret = ssh_append_without_duplicates(empty[i], s1[j]); + assert_non_null(ret); + printf("empty[%d] + s1[%d] resulted in '%s'\n", i, j, ret); + assert_string_equal("a,b,c", ret); + SAFE_FREE(ret); + } + } + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + ret = ssh_append_without_duplicates(empty[i], empty[j]); + if (ret != NULL) { + printf("empty[%d] + empty[%d] resulted in '%s'\n", i, j, ret); + } + assert_null(ret); + } + } +} + + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_tokens_sanity), + cmocka_unit_test(torture_find_matching), + cmocka_unit_test(torture_find_all_matching), + cmocka_unit_test(torture_remove_duplicate), + cmocka_unit_test(torture_append_without_duplicate), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/valgrind.supp b/tests/valgrind.supp new file mode 100644 index 0000000..e74d2c2 --- /dev/null +++ b/tests/valgrind.supp @@ -0,0 +1,149 @@ +### GLIBC +{ + glibc_regcomp + Memcheck:Leak + fun:*alloc + ... + fun:regcomp +} +{ + glibc_getaddrinfo_leak + Memcheck:Leak + fun:malloc + fun:make_request + fun:__check_pf + fun:getaddrinfo + fun:getai + fun:ssh_connect_host_nonblocking +} + +{ + glibc_dlopen_getdelim_selinux + Memcheck:Leak + fun:malloc + fun:getdelim + obj:/lib64/libselinux.so.1 + fun:call_init + fun:_dl_init + obj:/lib64/ld-2.15.so +} + +{ + glibc_dlopen_alloc + Memcheck:Leak + fun:calloc + fun:_dlerror_run + fun:dlopen@@GLIBC_2.2.5 +} + +### VALGRIND +{ + valgrind_exit_free_bug + Memcheck:Free + fun:free + fun:__libc_freeres + fun:_vgnU_freeres + fun:__run_exit_handlers + fun:exit +} + + +### OPENSSL +{ + openssl_crypto_value8 + Memcheck:Value8 + fun:* + obj:/lib*/libcrypto.so* +} + +{ + openssl_crypto_value4 + Memcheck:Value4 + fun:* + obj:/lib*/libcrypto.so* +} + +{ + openssl_crypto_cond + Memcheck:Cond + fun:* + obj:/lib*/libcrypto.so* +} + +{ + openssl_BN_cond + Memcheck:Cond + fun:BN_* +} + +{ + openssl_bn_value8 + Memcheck:Value8 + fun:bn_* +} + +{ + openssl_bn_value4 + Memcheck:Value4 + fun:bn_* +} + +{ + openssl_AES_cond + Memcheck:Cond + fun:AES_* +} + +{ + openssl_DES_cond + Memcheck:Cond + fun:DES_* +} + +{ + openssl_DES_value8 + Memcheck:Value8 + fun:DES_* +} + +{ + openssl_DES_value4 + Memcheck:Value4 + fun:DES_* +} + +{ + openssl_BF_cond + Memcheck:Cond + fun:BF_* +} + +{ + openssl_SHA1_cond + Memcheck:Cond + fun:SHA1_* +} + +{ + openssl_CRYPTO_leak + Memcheck:Leak + fun:*alloc + fun:CRYPTO_* +} +{ + openssl_CRYPTO_leak + Memcheck:Cond + fun:OPENSSL_cleanse +} +{ + openssl_FIPS_dlopen_leak + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + fun:_dlerror_run + fun:dlopen* + obj:/lib64/libcrypto.so* + fun:FIPS_module_mode_set + fun:FIPS_mode_set + fun:OPENSSL_init_library +}