diff options
296 files changed, 33633 insertions, 12866 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..2880be88 --- /dev/null +++ b/.clang-format @@ -0,0 +1,29 @@ +--- +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +BasedOnStyle: LLVM +IndentWidth: 4 +UseTab: Never +AllowShortIfStatementsOnASingleLine: false +BreakBeforeBraces: Custom +BraceWrapping: + AfterEnum: false + AfterFunction: true + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeElse: false + BeforeWhile: false +IndentCaseLabels: false +IndentCaseBlocks: false +ColumnLimit: 80 +AlignAfterOpenBracket: Align +AllowAllParametersOfDeclarationOnNextLine: false +BinPackArguments: false +BinPackParameters: false +AllowAllArgumentsOnNextLine: false +AllowShortFunctionsOnASingleLine: Empty +# TODO with Clang 19, replace the below with +# BreakAfterReturnType: ExceptShortType +AlwaysBreakAfterReturnType: AllDefinitions +AlignEscapedNewlines: Left +ForEachMacros: ['ssh_callbacks_iterate'] diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..59eb92bc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +max_line_length = 80 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{c,h}] +indent_style = space +indent_size = 4 +tab_width = 4 + +[{CMakeLists.txt,*.cmake}] +indent_style = space +indent_size = 4 +tab_width = 4
\ No newline at end of file @@ -1,11 +1,12 @@ *.a *.o -.* *.swp *~$ cscope.* compile_commands.json +/.cache /.clangd tags /build /obj* +doc/tags.xml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 509fb1d4..c47e6d45 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,24 +1,39 @@ +--- variables: BUILD_IMAGES_PROJECT: libssh/build-images + CENTOS8_BUILD: buildenv-c8s + CENTOS9_BUILD: buildenv-c9s FEDORA_BUILD: buildenv-fedora - UBUNTU_BUILD: buildenv-ubuntu - CENTOS7_BUILD: buildenv-centos7 - TUMBLEWEED_BUILD: buildenv-tumbleweed MINGW_BUILD: buildenv-mingw + TUMBLEWEED_BUILD: buildenv-tumbleweed + UBUNTU_BUILD: buildenv-ubuntu + ALPINE_BUILD: buildenv-alpine stages: + - review - build - test - analysis +# This is some black magic to select between branch pipelines and +# merge request pipelines to avoid running same pipelines in twice +workflow: + rules: + - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push"' + when: never + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS' + when: never + - if: '$CI_COMMIT_BRANCH' + .build: stage: build variables: - CMAKE_DEFAULT_OPTIONS: "-DCMAKE_BUILD_TYPE=RelWithDebInfo -DPICKY_DEVELOPER=ON" - CMAKE_BUILD_OPTIONS: "-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 -DWITH_DSA=ON" - CMAKE_TEST_OPTIONS: "-DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON" - CMAKE_OPTIONS: $CMAKE_DEFAULT_OPTIONS $CMAKE_BUILD_OPTIONS $CMAKE_TEST_OPTIONS - before_script: + CMAKE_DEFAULT_OPTIONS: "-DCMAKE_BUILD_TYPE=RelWithDebInfo -DPICKY_DEVELOPER=ON" + CMAKE_BUILD_OPTIONS: "-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" + CMAKE_TEST_OPTIONS: "-DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON -DWITH_BENCHMARKS=ON -DFUZZ_TESTING=ON" + CMAKE_OPTIONS: $CMAKE_DEFAULT_OPTIONS $CMAKE_BUILD_OPTIONS $CMAKE_TEST_OPTIONS + before_script: &build - uname -a - cat /etc/os-release - mount @@ -33,6 +48,10 @@ stages: # Do not use after_script as it does not make the targets fail tags: - shared + only: + - merge_requests + - branches + except: - tags artifacts: @@ -50,32 +69,97 @@ stages: - cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. && make -j$(nproc) && ctest --output-on-failure - # Do not use after_script as it does not make the targets fail + # Do not use after_script as it does not make the targets fail .fedora: extends: .tests image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD variables: - CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON + CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON .tumbleweed: extends: .tests image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD +.fips: + extends: .tests + variables: + CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON + before_script: + - *build + - echo "# userspace fips" > /etc/system-fips + # We do not need the kernel part, but in case we ever do: + # mkdir -p /var/tmp/userspace-fips + # echo 1 > /var/tmp/userspace-fips/fips_enabled + # mount --bind /var/tmp/userspace-fips/fips_enabled \ + # /proc/sys/crypto/fips_enabled + - update-crypto-policies --show + - update-crypto-policies --set FIPS + - update-crypto-policies --show +############################################################################### +# Review # +############################################################################### +review: + variables: + GIT_DEPTH: 100 + stage: review + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + script: + - ERROR=0 + codespell --ignore-words-list=keypair,sorce,ned,nd,ue || ERROR=1; + ./.gitlab-ci/clang-format-check.sh || ERROR=1; + ./.gitlab-ci/git-check-signoff-trailer.sh ${CI_MERGE_REQUEST_DIFF_BASE_SHA} || ERROR=1; + ./.gitlab-ci/git-check-signoff-trailer.sh ${CI_MERGE_REQUEST_DIFF_BASE_SHA} || ERROR=1; + ./.gitlab-ci/shellcheck.sh || ERROR=1; + exit $ERROR + # the format is not always matching our intentions + allow_failure: true + tags: + - shared + only: + - merge_requests ############################################################################### # CentOS builds # ############################################################################### -# 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 +centos9s/openssl_3.0.x/x86_64: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS9_BUILD extends: .tests + variables: + CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON script: - - cmake3 $CMAKE_OPTIONS .. && + - export OPENSSL_ENABLE_SHA1_SIGNATURES=1 + - cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. && make -j$(nproc) && ctest --output-on-failure +centos9s/openssl_3.0.x/x86_64/fips: + extends: .fips + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS9_BUILD + script: + - export OPENSSL_ENABLE_SHA1_SIGNATURES=1 + - cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. && + make -j$(nproc) && + OPENSSL_FORCE_FIPS_MODE=1 ctest --output-on-failure + +centos8s/openssl_1.1.1/x86_64: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS8_BUILD + extends: .tests + variables: + CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON + script: + - cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. && + make -j$(nproc) && + ctest --output-on-failure + +centos8s/openssl_1.1.1/x86_64/fips: + extends: .fips + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS8_BUILD + script: + - cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. && + make -j$(nproc) && + OPENSSL_FORCE_FIPS_MODE=1 ctest --output-on-failure ############################################################################### # Fedora builds # @@ -88,41 +172,42 @@ fedora/docs: extends: .build image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD script: - - cmake .. && make docs + - cmake .. && make docs_coverage && make docs + coverage: '/^Documentation coverage is \d+.\d+%/' fedora/ninja: extends: .fedora image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD script: - - cmake -G Ninja $CMAKE_OPTIONS ../ && ninja && ninja test - -fedora/openssl_1.1.x/x86_64: - extends: .fedora + - cmake -G Ninja $CMAKE_OPTIONS ../ && ninja && CTEST_OUTPUT_ON_FAILURE=1 ninja test -fedora/openssl_1.1.x/x86_64/fips: +fedora/coverage: extends: .fedora - before_script: - - echo "# userspace fips" > /etc/system-fips - # We do not need the kernel part, but in case we ever do: - # mkdir -p /var/tmp/userspace-fips - # echo 1 > /var/tmp/userspace-fips/fips_enabled - # mount --bind /var/tmp/userspace-fips/fips_enabled /proc/sys/crypto/fips_enabled - - update-crypto-policies --show - - update-crypto-policies --set FIPS - - update-crypto-policies --show - - 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 -DWITH_DSA=ON - -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON .. + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + variables: + CMAKE_ADDITIONAL_OPTIONS: "-DCMAKE_BUILD_TYPE=Debug -DWITH_COVERAGE=ON" script: - - cmake $CMAKE_OPTIONS .. && + - cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. && make -j$(nproc) && - OPENSSL_FORCE_FIPS_MODE=1 ctest --output-on-failure + make coverage_xml + coverage: /^\s*lines:\s*\d+.\d+\%/ + artifacts: + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + expire_in: 1 week + reports: + coverage_report: + coverage_format: cobertura + path: obj/coverage_xml.xml + +fedora/openssl_3.0.x/x86_64: + extends: .fedora + +fedora/openssl_3.0.x/x86_64/pkcs11-provider: + variables: + CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON -DWITH_PKCS11_PROVIDER=ON + extends: .fedora -fedora/openssl_1.1.x/x86_64/minimal: +fedora/openssl_3.0.x/x86_64/minimal: extends: .fedora variables: script: @@ -131,12 +216,24 @@ fedora/openssl_1.1.x/x86_64/minimal: -DWITH_SERVER=OFF -DWITH_ZLIB=OFF -DWITH_PCAP=OFF - -DWITH_DSA=OFF -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DWITH_GEX=OFF .. && make -j$(nproc) +# The PKCS#11 support is turned off as it brings dozens of memory issues from +# engine_pkcs11 or openssl itself +fedora/valgrind: + variables: + CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=OFF + extends: .fedora + stage: analysis + script: + - cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. && + make -j$(nproc) && + make test_memcheck + - cat Testing/Temporary/MemoryChecker.*.log | wc -l | grep "^0$" + # 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 @@ -190,12 +287,12 @@ fedora/undefined-sanitizer: fedora/libgcrypt/x86_64: extends: .fedora variables: - CMAKE_ADDITIONAL_OPTIONS: "-DWITH_GCRYPT=ON -DWITH_DEBUG_CRYPTO=ON" + CMAKE_ADDITIONAL_OPTIONS: "-DWITH_GCRYPT=ON -DWITH_DEBUG_CRYPTO=ON" fedora/mbedtls/x86_64: extends: .fedora variables: - CMAKE_ADDITIONAL_OPTIONS: "-DWITH_MBEDTLS=ON -DWITH_DEBUG_CRYPTO=ON -DWITH_DSA=OFF" + CMAKE_ADDITIONAL_OPTIONS: "-DWITH_MBEDTLS=ON -DWITH_DEBUG_CRYPTO=ON " # Unit testing only, no client and pkd testing, because cwrap is not available # for MinGW @@ -232,87 +329,107 @@ fedora/mingw32: ctest --output-on-failure - ############################################################################### # Fedora csbuild # ############################################################################### .csbuild: stage: analysis variables: - GIT_DEPTH: "100" + GIT_DEPTH: "100" image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD before_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" + - | + # for merge requests + if [[ -n "$CI_MERGE_REQUEST_DIFF_BASE_SHA" ]]; then + export CI_COMMIT_BEFORE_SHA="$CI_MERGE_REQUEST_DIFF_BASE_SHA" + fi + # for branches run + 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" tags: - - shared + - shared except: - - tags + - tags + only: + - merge_requests artifacts: expire_in: 1 week when: on_failure paths: - obj-csbuild/ -fedora/csbuild/openssl_1.1.x: +fedora/csbuild/openssl_3.0.x: extends: .csbuild script: - - 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 -DWITH_DSA=ON @SRCDIR@ && make clean && make -j$(nproc)" - --git-commit-range $CI_COMMIT_RANGE - --color - --print-current --print-fixed + - 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 fedora/csbuild/libgcrypt: extends: .csbuild script: - - 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 -DWITH_GCRYPT=ON -DWITH_DSA=ON @SRCDIR@ && make clean && make -j$(nproc)" - --git-commit-range $CI_COMMIT_RANGE - --color - --print-current --print-fixed + - 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 -DWITH_GCRYPT=ON @SRCDIR@ && make clean && make -j$(nproc)" + --git-commit-range $CI_COMMIT_RANGE + --color + --print-current --print-fixed fedora/csbuild/mbedtls: extends: .csbuild script: - - 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 -DWITH_MBEDTLS=ON @SRCDIR@ && make clean && make -j$(nproc)" - --git-commit-range $CI_COMMIT_RANGE - --color - --print-current --print-fixed - - + - 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 -DWITH_MBEDTLS=ON @SRCDIR@ && make clean && make -j$(nproc)" + --git-commit-range $CI_COMMIT_RANGE + --color + --print-current --print-fixed ############################################################################### # Ubuntu builds # ############################################################################### -ubuntu/openssl_1.1.x/x86_64: +ubuntu/openssl_3.0.x/x86_64: image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$UBUNTU_BUILD extends: .tests +############################################################################### +# Alpine builds # +############################################################################### +alpine/openssl_3.0.x/musl: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$ALPINE_BUILD + extends: .tests + script: + - cmake $CMAKE_DEFAULT_OPTIONS + -DWITH_SFTP=ON + -DWITH_SERVER=ON + -DWITH_ZLIB=ON + -DWITH_PCAP=ON + -DUNIT_TESTING=ON .. && + make -j$(nproc) && + ctest --output-on-failure + ############################################################################### # Tumbleweed builds # ############################################################################### -tumbleweed/openssl_1.1.x/x86_64/gcc: +tumbleweed/openssl_3.0.x/x86_64/gcc: extends: .tumbleweed variables: - CMAKE_ADDITIONAL_OPTIONS: "-DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config" + CMAKE_ADDITIONAL_OPTIONS: "-DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config" -tumbleweed/openssl_1.1.x/x86/gcc: +tumbleweed/openssl_3.0.x/x86/gcc: extends: .tumbleweed script: - cmake @@ -322,30 +439,30 @@ tumbleweed/openssl_1.1.x/x86/gcc: -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DWITH_DSA=ON - -DUNIT_TESTING=ON .. + -DUNIT_TESTING=ON .. && + make -j$(nproc) -tumbleweed/openssl_1.1.x/x86_64/gcc7: +tumbleweed/openssl_3.0.x/x86_64/gcc7: extends: .tumbleweed variables: - CMAKE_ADDITIONAL_OPTIONS: "-DCMAKE_C_COMPILER=gcc-7 -DCMAKE_CXX_COMPILER=g++-7 -DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config" + CMAKE_ADDITIONAL_OPTIONS: "-DCMAKE_C_COMPILER=gcc-7 -DCMAKE_CXX_COMPILER=g++-7 -DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config" -tumbleweed/openssl_1.1.x/x86/gcc7: +tumbleweed/openssl_3.0.x/x86/gcc7: extends: .tumbleweed script: - cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-cross-m32.cmake -DCMAKE_C_COMPILER=gcc-7 -DCMAKE_CXX_COMPILER=g++-7 $CMAKE_DEFAULT_OPTIONS - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON -DWITH_DSA=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON -DUNIT_TESTING=ON .. && make -j$(nproc) && ctest --output-on-failure -tumbleweed/openssl_1.1.x/x86_64/clang: +tumbleweed/openssl_3.0.x/x86_64/clang: extends: .tumbleweed variables: - CMAKE_ADDITIONAL_OPTIONS: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config" + CMAKE_ADDITIONAL_OPTIONS: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config" tumbleweed/static-analysis: extends: .tests @@ -369,21 +486,20 @@ tumbleweed/static-analysis: - obj/scan - ############################################################################### # FreeBSD builds # ############################################################################### # That is a specific runner that we cannot enable universally. # We restrict it to builds under the $BUILD_IMAGES_PROJECT project. -freebsd/x86_64: +freebsd/openssl_1.1.1/x86_64: image: extends: .tests before_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 .. + - 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 .. script: - cmake $CMAKE_DEFAULT_OPTIONS -DWITH_SFTP=ON @@ -394,12 +510,13 @@ freebsd/x86_64: make && ctest --output-on-failure tags: - - private - - freebsd + - private + - freebsd only: - - branches@libssh/libssh-mirror - - branches@cryptomilk/libssh-mirror - - branches@jjelen/libssh-mirror + - branches@libssh/libssh-mirror + - branches@cryptomilk/libssh-mirror + - branches@jjelen/libssh-mirror + - branches@marco.fortina/libssh-mirror ############################################################################### @@ -414,36 +531,37 @@ freebsd/x86_64: variables: ErrorActionPreference: STOP script: - - cmake --build . - - ctest --output-on-failure + - cmake --build . + - ctest --output-on-failure tags: - - windows - - shared-windows + - windows + - shared-windows + only: + - merge_requests + - branches except: - - tags + - tags artifacts: expire_in: 1 week when: on_failure paths: - obj/ before_script: - - choco install --no-progress -y cmake - - $env:Path += ';C:\Program Files\CMake\bin' - - If (!(test-path .vcpkg\archives)) { mkdir -p .vcpkg\archives } - - $env:VCPKG_DEFAULT_BINARY_CACHE="$PWD\.vcpkg\archives" - - echo $env:VCPKG_DEFAULT_BINARY_CACHE - - $env:VCPKG_DEFAULT_TRIPLET="$TRIPLET-windows" - - vcpkg install cmocka - - vcpkg install openssl - - vcpkg install zlib - - vcpkg integrate install - - mkdir -p obj; if ($?) {cd obj}; if (! $?) {exit 1} - - cmake - -A $PLATFORM - -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DUNIT_TESTING=ON .. + - If (!(test-path .vcpkg\archives)) { mkdir -p .vcpkg\archives } + - $env:VCPKG_DEFAULT_BINARY_CACHE="$PWD\.vcpkg\archives" + - echo $env:VCPKG_DEFAULT_BINARY_CACHE + - $env:VCPKG_DEFAULT_TRIPLET="$TRIPLET-windows" + - vcpkg install cmocka + - vcpkg install openssl + - vcpkg install zlib + - vcpkg integrate install + - mkdir -p obj; if ($?) {cd obj}; if (! $?) {exit 1} + - cmake + -A $PLATFORM + -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON .. visualstudio/x86_64: extends: .vs @@ -468,7 +586,7 @@ visualstudio/x86: coverity: stage: analysis - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS9_BUILD script: - mkdir obj && cd obj - wget https://scan.coverity.com/download/linux64 --post-data "token=$COVERITY_SCAN_TOKEN&project=$COVERITY_SCAN_PROJECT_NAME" -O /tmp/coverity_tool.tgz diff --git a/.gitlab-ci/clang-format-check.sh b/.gitlab-ci/clang-format-check.sh new file mode 100755 index 00000000..261918ae --- /dev/null +++ b/.gitlab-ci/clang-format-check.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# Based on Github Action +# https://github.com/yshui/git-clang-format-lint + +diff=$(git-clang-format --diff --commit "$CI_MERGE_REQUEST_DIFF_BASE_SHA") +[ "$diff" = "no modified files to format" ] && exit 0 +[ "$diff" = "clang-format did not modify any files" ] && exit 0 + +printf "You have introduced coding style breakages, suggested changes:\n\n" + +echo "${diff}" | colordiff +exit 1 diff --git a/.gitlab-ci/git-check-signoff-trailer.sh b/.gitlab-ci/git-check-signoff-trailer.sh new file mode 100755 index 00000000..e3819662 --- /dev/null +++ b/.gitlab-ci/git-check-signoff-trailer.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +if [ $# != 1 ]; then + echo "Usage: $0 UPSTREAM_COMMIT_SHA" + exit 1 +fi + +failed=0 + +if [ -z "$CI_COMMIT_SHA" ]; then + echo "CI_COMMIT_SHA is not set" + exit 1 +fi + +CI_COMMIT_RANGE="$1..$CI_COMMIT_SHA" + +red='\033[0;31m' +blue='\033[0;34m' + +echo -e "${blue}Checking commit range: $CI_COMMIT_RANGE" +echo +echo + +for commit in $(git rev-list "$CI_COMMIT_RANGE"); do + git show -s --format=%B "$commit" | grep "^Signed-off-by: " >/dev/null 2>&1 + ret=$? + if [ $ret -eq 1 ]; then + echo -e "${red} >>> Missing Signed-off-by trailer in commit $commit" + failed=$(("$failed" + 1)) + fi +done + +echo +echo + +exit $failed diff --git a/.gitlab-ci/shellcheck.sh b/.gitlab-ci/shellcheck.sh new file mode 100755 index 00000000..e7db0b63 --- /dev/null +++ b/.gitlab-ci/shellcheck.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# Simplified and de-github-ed version of +# https://github.com/ludeeus/action-shellcheck/blob/master/action.yaml + +statuscode=0 + +declare -a filepaths +shebangregex="^#! */[^ ]*/(env *)?[abk]*sh" +set -f # temporarily disable globbing so that globs in inputs aren't expanded + +while IFS= read -r -d '' file; do + filepaths+=("$file") +done < <(find . \ + -type f \ + '(' \ + -name '*.bash' \ + -o -name '.bashrc' \ + -o -name 'bashrc' \ + -o -name '.bash_aliases' \ + -o -name '.bash_completion' \ + -o -name '.bash_login' \ + -o -name '.bash_logout' \ + -o -name '.bash_profile' \ + -o -name 'bash_profile' \ + -o -name '*.ksh' \ + -o -name 'suid_profile' \ + -o -name '*.zsh' \ + -o -name '.zlogin' \ + -o -name 'zlogin' \ + -o -name '.zlogout' \ + -o -name 'zlogout' \ + -o -name '.zprofile' \ + -o -name 'zprofile' \ + -o -name '.zsenv' \ + -o -name 'zsenv' \ + -o -name '.zshrc' \ + -o -name 'zshrc' \ + -o -name '*.sh' \ + -o -path '*/.profile' \ + -o -path '*/profile' \ + -o -name '*.shlib' \ + ')' \ + -print0) + +while IFS= read -r -d '' file; do + head -n1 "$file" | grep -Eqs "$shebangregex" || continue + filepaths+=("$file") +done < <(find . \ + -type f ! -name '*.*' -perm /111 \ + -print0) + +shellcheck "${filepaths[@]}" || statuscode=$? + +set +f # re-enable globbing + +exit "$statuscode" diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 00000000..a245e6e5 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,10 @@ +# +# GitLeaks Repo Specific Configuration +# +# This allowlist is used to help Red Hat ignore false positives during its code +# scans. + +[allowlist] + paths = [ + '''tests/*''', + ] @@ -1,9 +1,97 @@ -ChangeLog -========== - -version 0.10.0 (released 2020-xx-xx) - * Support for Smart Cards - * Support for chacha20-poly1305@openssh.com with libgcrypt +CHANGELOG +========= + +version 0.10.0 (released 2022-07-xx) + * Added support for OpenSSL 3.0 + * Added support for mbedTLS 3 + * Added support for Smart Cards (through openssl pkcs11 engine) + * Added support for chacha20-poly1305@openssh.com with libgcrypt + * Added support ed25519 keys in PEM files + * Added support for sk-ecdsa and sk-ed25519 (server side) + * Added support for limiting RSA key sizes and not accepting small one by + default + * Added support for ssh-agent on Windows + * Added ssh_userauth_publickey_auto_get_current_identity() API + * Added ssh_vlog() API + * Added ssh_send_issue_banner() API + * Added ssh_session_set_disconnect_message() API + * Added new configuration options: + + IdentityAgent + + ModuliFile + * Provided X11 client example + * Disabled DSA support at build time by default (will be removed in the next + release) + * Deprecated the SCP API! + * Deprecated old pubkey, privatekey API + * Avoided some needless large stack buffers to minimize memory footprint + * Removed support for OpenSSL < 1.0.1 + +version 0.9.6 (released 2021-08-26) + * CVE-2021-3634: Fix possible heap-buffer overflow when rekeying with + different key exchange mechanism + * Fix several memory leaks on error paths + * Reset pending_call_state on disconnect + * Fix handshake bug with AEAD ciphers and no HMAC overlap + * Use OPENSSL_CRYPTO_LIBRARIES in CMake + * Ignore request success and failure message if they are not expected + * Support more identity files in configuration + * Avoid setting compiler flags directly in CMake + * Support build directories with special characters + * Include stdlib.h to avoid crash in Windows + * Fix sftp_new_channel constructs an invalid object + * Fix Ninja multiple rules error + * Several tests fixes + +version 0.9.5 (released 2020-09-10) + * CVE-2020-16135: Avoid null pointer dereference in sftpserver (T232) + * Improve handling of library initialization (T222) + * Fix parsing of subsecond times in SFTP (T219) + * Make the documentation reproducible + * Remove deprecated API usage in OpenSSL + * Fix regression of ssh_channel_poll_timeout() returning SSH_AGAIN + * Define version in one place (T226) + * Prevent invalid free when using different C runtimes than OpenSSL (T229) + * Compatibility improvements to testsuite + +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 several 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-02-xx) * Added support for AES-GCM @@ -120,7 +208,7 @@ version 0.6.1 (released 2014-02-08) * Fixed DSA signature extraction. * Fixed some memory leaks. * Fixed read of non-connected socket. - * Fixed thread dectection. + * Fixed thread detection. version 0.6.0 (released 2014-01-08) * Added new publicy key API. @@ -145,7 +233,7 @@ version 0.6.0 (released 2014-01-08) 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 pollset error return if empty. * Fix NULL pointer checks in channel functions. * Several bugfixes. @@ -161,7 +249,7 @@ version 0.5.3 (released 2012-11-20) * 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. + * BUG #89 - Reset error state to no error on successful SSHv1 authentication. * Fixed a possible use after free in ssh_free(). * Fixed multiple possible NULL pointer dereferences. * Fixed multiple memory leaks in error paths. @@ -222,7 +310,7 @@ 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 timeout 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(). @@ -245,7 +333,7 @@ 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. + * Fixed several build warnings. version 0.4.4 (released 2010-06-01) * Fixed a bug in the expand function for escape sequences. @@ -264,17 +352,17 @@ version 0.4.3 (released 2010-05-18) * 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 config parsing which has overwritten user specified values. * Fixed hashed [host]:port format in knownhosts * Fixed Windows build. - * Fixed doublefree happening after a negociation error. + * Fixed doublefree happening after a negotiation 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. + * Build examples only on the Linux platform. version 0.4.2 (released 2010-03-15) * Added owner and group information in sftp attributes. @@ -296,7 +384,7 @@ version 0.4.1 (released 2010-02-13) * 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 kex bug that client preference should be priority * Fixed known_hosts file set by the user. * Fixed a memleak in channel_accept(). * Fixed underflow when leave_function() are unbalanced @@ -434,7 +522,7 @@ version 0.11-dev * Keyboard-interactive authentication working. version 0.1 (released 2004-03-05) - * Begining of sftp subsystem implementation. + * Beginning 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(). @@ -455,7 +543,7 @@ version 0.0.4 (released 2003-10-10) * 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) + * Support for other crypto libs planned (lighter libs) * Fixed stupid select() bug. * Libssh now compiles and links with openssl 0.9.6 * RSA pubkey authentication code now works ! diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f1925ef..acc1e606 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,6 @@ -cmake_minimum_required(VERSION 3.3.0) -cmake_policy(SET CMP0048 NEW) +cmake_minimum_required(VERSION 3.12.0) -# Specify search path for CMake modules to be loaded by include() +# 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") @@ -10,7 +9,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules") include(DefineCMakeDefaults) include(DefineCompilerFlags) -project(libssh VERSION 0.8.90 LANGUAGES C) +project(libssh VERSION 0.10.90 LANGUAGES C CXX) # global needed variable set(APPLICATION_NAME ${PROJECT_NAME}) @@ -22,7 +21,7 @@ set(APPLICATION_NAME ${PROJECT_NAME}) # 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.1") +set(LIBRARY_VERSION "4.9.0") set(LIBRARY_SOVERSION "4") # where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked @@ -49,32 +48,11 @@ 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 1.0.1) - if (OPENSSL_FOUND) - # On CMake < 3.16, OPENSSL_CRYPTO_LIBRARIES is usually a synonym for OPENSSL_CRYPTO_LIBRARY, but is not defined - # when building on Windows outside of Cygwin. We provide the synonym here, if FindOpenSSL didn't define it already. - if (NOT DEFINED OPENSSL_CRYPTO_LIBRARIES) - set(OPENSSL_CRYPTO_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) - endif (NOT DEFINED OPENSSL_CRYPTO_LIBRARIES) - else (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 (OPENSSL_FOUND) -endif(WITH_GCRYPT) +else() + find_package(OpenSSL 1.1.1 REQUIRED) +endif() if (UNIT_TESTING) find_package(CMocka REQUIRED) @@ -89,13 +67,6 @@ if (WITH_GSSAPI) find_package(GSSAPI) endif (WITH_GSSAPI) -if (WITH_PKCS11_URI) - find_package(softhsm) - if (NOT SOFTHSM_FOUND) - message(SEND_ERROR "Could not find softhsm module!") - endif (NOT SOFTHSM_FOUND) -endif (WITH_PKCS11_URI) - if (WITH_NACL) find_package(NaCl) if (NOT NACL_FOUND) @@ -103,10 +74,6 @@ if (WITH_NACL) 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) @@ -118,14 +85,18 @@ endif (UNIX) include(ConfigureChecks.cmake) configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) +if (NOT HAVE_ARGP_PARSE) + find_package(Argp) +endif (NOT HAVE_ARGP_PARSE) + # 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) +if (UNIX OR MINGW) +configure_file(libssh.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/libssh.pc @ONLY) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/libssh.pc @@ -134,7 +105,7 @@ install( COMPONENT pkgconfig ) -endif (UNIX) +endif (UNIX OR MINGW) # CMake config files include(CMakePackageConfigHelpers) @@ -216,16 +187,35 @@ if (WITH_SYMBOL_VERSIONING AND ABIMAP_FOUND) endif(UPDATE_ABI) endif (WITH_SYMBOL_VERSIONING AND ABIMAP_FOUND) +# Coverage +if (WITH_COVERAGE) + include(CodeCoverage) + setup_target_for_coverage_lcov( + NAME "coverage" + EXECUTABLE make test + DEPENDENCIES ssh tests) + set(GCOVR_ADDITIONAL_ARGS --xml-pretty --exclude-unreachable-branches --print-summary) + setup_target_for_coverage_gcovr_xml( + NAME "coverage_xml" + EXECUTABLE make test + DEPENDENCIES ssh tests) +endif (WITH_COVERAGE) + add_custom_target(dist COMMAND ${CMAKE_MAKE_PROGRAM} package_source DEPENDS ${_SYMBOL_TARGET} VERBATIM) -# Link compile database for clangd -execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink - "${CMAKE_BINARY_DIR}/compile_commands.json" - "${CMAKE_SOURCE_DIR}/compile_commands.json") +get_directory_property(hasParent PARENT_DIRECTORY) +if(NOT(hasParent)) + # Link compile database for clangd if we are the master project + execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink + "${CMAKE_BINARY_DIR}/compile_commands.json" + "${CMAKE_SOURCE_DIR}/compile_commands.json") +endif() message(STATUS "********************************************") message(STATUS "********** ${PROJECT_NAME} build options : **********") +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") +message(STATUS "Coverage: ${WITH_COVERAGE}") message(STATUS "zlib support: ${WITH_ZLIB}") message(STATUS "libgcrypt support: ${WITH_GCRYPT}") message(STATUS "libmbedTLS support: ${WITH_MBEDTLS}") @@ -241,7 +231,7 @@ message(STATUS "Unit testing: ${UNIT_TESTING}") message(STATUS "Client code testing: ${CLIENT_TESTING}") message(STATUS "Blowfish cipher support: ${WITH_BLOWFISH_CIPHER}") message(STATUS "PKCS #11 URI support: ${WITH_PKCS11_URI}") -message(STATUS "DSA support: ${WITH_DSA}") +message(STATUS "With PKCS #11 provider support: ${WITH_PKCS11_PROVIDER}") set(_SERVER_TESTING OFF) if (WITH_SERVER) set(_SERVER_TESTING ${SERVER_TESTING}) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 71c449b0..8e26015c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ For contributions we prefer Merge Requests on Gitlab: https://gitlab.com/libssh/libssh-mirror/ -This way you get contintious integration which runs the complete libssh +This way you get continuous integration which runs the complete libssh testsuite for you. For larger code changes, breaking the changes up into a set of simple @@ -274,7 +274,7 @@ This is bad: * This is a multi line comment, * with some more words...*/ -### Indention & Whitespace & 80 columns +### Indentation & 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 @@ -478,6 +478,45 @@ 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. +### Switch/case indentation + +The `case` should not be indented to avoid wasting too much horizontal space. +When the case block contains local variables that need to be wrapped in braces, +they should not be indented again either. + +Good example: + + switch (x) { + case 0: + do_stuff(); + break; + case 1: { + int y; + do_stuff(); + break; + } + default: + do_other_stuff(); + break; + } + +Bad example: + + switch (x) { + case 0: + do_stuff(); + break; + case 1: + { + int y; + do_stuff(); + break; + } + default: + do_other_stuff(); + break; + } + Have fun and happy libssh hacking! diff --git a/CPackConfig.cmake b/CPackConfig.cmake index c4a3598a..5bd52c56 100644 --- a/CPackConfig.cmake +++ b/CPackConfig.cmake @@ -10,7 +10,7 @@ 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_IGNORE_FILES "~$;[.]swp$;/[.]git/;/[.]clangd/;/[.]cache/;.gitignore;/build*;/obj*;tags;cscope.*;compile_commands.json;.*\.patch") set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}") ### NSIS INSTALLER diff --git a/CompilerChecks.cmake b/CompilerChecks.cmake index 5bdc05c3..7a25b592 100644 --- a/CompilerChecks.cmake +++ b/CompilerChecks.cmake @@ -43,6 +43,12 @@ if (UNIX) 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) + add_c_compiler_flag("-Wold-style-definition" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=old-style-definition" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wimplicit-int" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=implicit-int" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wint-conversion" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=int-conversion" SUPPORTED_COMPILER_FLAGS) check_c_compiler_flag("-Wformat" REQUIRED_FLAGS_WFORMAT) if (REQUIRED_FLAGS_WFORMAT) @@ -70,7 +76,7 @@ if (UNIX) 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 + # This is needed as Solaris has a separate libssp if (SOLARIS) list(APPEND SUPPORTED_LINKER_FLAGS "-fstack-protector-strong") endif() @@ -78,16 +84,18 @@ if (UNIX) 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 + # This is needed as Solaris has a separate 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") + if (NOT WINDOWS AND NOT CYGWIN) + 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() endif() if (PICKY_DEVELOPER) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 384e7856..64aaea07 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -44,6 +44,8 @@ int main(void){ return 0; } endif(CMAKE_COMPILER_IS_GNUCC AND NOT MINGW AND NOT OS2) # HEADER FILES +check_function_exists(argp_parse HAVE_ARGP_PARSE) + set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} ${ARGP_INCLUDE_DIR}) check_include_file(argp.h HAVE_ARGP_H) unset(CMAKE_REQUIRED_INCLUDES) @@ -74,76 +76,32 @@ if (WIN32) endif (WIN32) if (OPENSSL_FOUND) - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES OpenSSL::Crypto) + 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_LIBRARIES}) 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_LIBRARIES}) + check_function_exists(EVP_KDF_CTX_new HAVE_OPENSSL_EVP_KDF_CTX_NEW) check_function_exists(FIPS_mode HAVE_OPENSSL_FIPS_MODE) - - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) check_function_exists(RAND_priv_bytes HAVE_OPENSSL_RAND_PRIV_BYTES) - - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) - check_function_exists(EVP_DigestSign HAVE_OPENSSL_EVP_DIGESTSIGN) - - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) - 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_LIBRARIES}) - check_symbol_exists(EVP_PKEY_ED25519 "openssl/evp.h" FOUND_OPENSSL_ED25519) - - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) check_function_exists(EVP_chacha20 HAVE_OPENSSL_EVP_CHACHA20) - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) - check_symbol_exists(EVP_PKEY_POLY1305 "openssl/evp.h" HAVE_OPENSSL_EVP_POLY1305) - - 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_LIBRARIES}) - check_symbol_exists(EVP_PKEY_X25519 "openssl/evp.h" HAVE_OPENSSL_X25519) - - unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) endif() @@ -159,13 +117,12 @@ if (NOT WITH_GCRYPT AND NOT WITH_MBEDTLS) if (HAVE_OPENSSL_ECC) set(HAVE_ECC 1) endif (HAVE_OPENSSL_ECC) -endif () -if (WITH_DSA) - if (NOT WITH_MBEDTLS) - set(HAVE_DSA 1) - endif (NOT WITH_MBEDTLS) -endif() + if (HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID OR HAVE_OPENSSL_EVP_KDF_CTX_NEW) + set(HAVE_OPENSSL_EVP_KDF_CTX 1) + endif (HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID OR HAVE_OPENSSL_EVP_KDF_CTX_NEW) + +endif () # FUNCTIONS @@ -311,7 +268,7 @@ int main(void) { # For detecting attributes we need to treat warnings as # errors if (UNIX OR MINGW) - # Get warnings for attributs + # Get warnings for attributes check_c_compiler_flag("-Wattributes" REQUIRED_FLAGS_WERROR) if (REQUIRED_FLAGS_WERROR) string(APPEND CMAKE_REQUIRED_FLAGS "-Wattributes ") @@ -366,6 +323,23 @@ int main(void) { return 0; }" HAVE_FALLTHROUGH_ATTRIBUTE) +check_c_source_compiles(" +#define WEAK __attribute__((weak)) + +WEAK int sum(int a, int b) +{ + return a + b; +} + +int main(void) +{ + int i = sum(2, 2); + + (void)i; + + return 0; +}" HAVE_WEAK_ATTRIBUTE) + if (NOT WIN32) check_c_source_compiles(" #define __unused __attribute__((unused)) @@ -463,18 +437,21 @@ if (WITH_PKCS11_URI) if (WITH_GCRYPT) message(FATAL_ERROR "PKCS #11 is not supported for gcrypt.") set(WITH_PKCS11_URI 0) - endif() - if (WITH_MBEDTLS) + elseif (WITH_MBEDTLS) message(FATAL_ERROR "PKCS #11 is not supported for mbedcrypto") set(WITH_PKCS11_URI 0) - endif() -endif() - -if (WITH_MBEDTLS) - if (WITH_DSA) - message(FATAL_ERROR "DSA is not supported with mbedTLS crypto") - set(HAVE_DSA 0) - endif() + elseif (OPENSSL_FOUND AND OPENSSL_VERSION VERSION_GREATER_EQUAL "3.0.0") + find_library(PKCS11_PROVIDER + NAMES + pkcs11.so + PATH_SUFFIXES + ossl-modules + ) + if (NOT PKCS11_PROVIDER) + set(WITH_PKCS11_PROVIDER 0) + message(WARNING "Could not find pkcs11 provider! Falling back to engines") + endif (NOT PKCS11_PROVIDER) + endif () endif() # ENDIAN diff --git a/DefineOptions.cmake b/DefineOptions.cmake index 068db988..7401e86d 100644 --- a/DefineOptions.cmake +++ b/DefineOptions.cmake @@ -2,10 +2,9 @@ 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_CRYPTO "Build with crypto 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_DSA "Build with DSA" OFF) option(WITH_GCRYPT "Compile against libgcrypt" OFF) option(WITH_MBEDTLS "Compile against libmbedtls" OFF) option(WITH_BLOWFISH_CIPHER "Compile with blowfish support" OFF) @@ -13,10 +12,11 @@ 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(WITH_PKCS11_URI "Build with PKCS#11 URI support" OFF) +option(WITH_PKCS11_PROVIDER "Use the PKCS#11 provider for accessing pkcs11 objects" OFF) 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_BENCHMARKS "Build benchmarks tools; enables unit testing and client tests" OFF) option(WITH_EXAMPLES "Build examples" ON) option(WITH_NACL "Build with libnacl (curve25519)" ON) option(WITH_SYMBOL_VERSIONING "Build with symbol versioning" ON) @@ -7,11 +7,13 @@ In order to build libssh, you need to install several components: - A C compiler -- [CMake](https://www.cmake.org) >= 3.3.0 -- [openssl](https://www.openssl.org) >= 1.0.1 -or -- [gcrypt](https://www.gnu.org/directory/Security/libgcrypt.html) >= 1.4 +- [CMake](https://www.cmake.org) >= 3.5.0 - [libz](https://www.zlib.net) >= 1.2 +- [openssl](https://www.openssl.org) >= 1.1.1 +or +- [gcrypt](https://www.gnu.org/directory/Security/libgcrypt.html) >= 1.5 +or +- [Mbed TLS](https://www.trustedfirmware.org/projects/mbed-tls/) optional: - [cmocka](https://cmocka.org/) >= 1.1.0 @@ -19,6 +21,7 @@ optional: - [nss_wrapper](https://cwrap.org/) >= 1.1.2 - [uid_wrapper](https://cwrap.org/) >= 1.2.0 - [pam_wrapper](https://cwrap.org/) >= 1.0.1 +- [priv_wrapper](https://cwrap.org/) >= 1.0.0 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. @@ -39,7 +42,7 @@ 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 +On Windows you should choose a makefile generator with -G or use cmake-gui.exe .. diff --git a/cmake/Modules/AddCMockaTest.cmake b/cmake/Modules/AddCMockaTest.cmake index 4b0c2dad..79178183 100644 --- a/cmake/Modules/AddCMockaTest.cmake +++ b/cmake/Modules/AddCMockaTest.cmake @@ -116,5 +116,9 @@ function(ADD_CMOCKA_TEST _TARGET_NAME) add_test(${_TARGET_NAME} ${TARGET_SYSTEM_EMULATOR} ${_TARGET_NAME} ) + if (WITH_COVERAGE) + include(CodeCoverage) + append_coverage_compiler_flags_to_target(${_TARGET_NAME}) + endif (WITH_COVERAGE) endfunction (ADD_CMOCKA_TEST) diff --git a/cmake/Modules/CodeCoverage.cmake b/cmake/Modules/CodeCoverage.cmake new file mode 100644 index 00000000..0fd70ae2 --- /dev/null +++ b/cmake/Modules/CodeCoverage.cmake @@ -0,0 +1,750 @@ +# Copyright (c) 2012 - 2017, Lars Bilke +# 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. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# CHANGES: +# +# 2012-01-31, Lars Bilke +# - Enable Code Coverage +# +# 2013-09-17, Joakim Söderberg +# - Added support for Clang. +# - Some additional usage instructions. +# +# 2016-02-03, Lars Bilke +# - Refactored functions to use named parameters +# +# 2017-06-02, Lars Bilke +# - Merged with modified version from github.com/ufz/ogs +# +# 2019-05-06, Anatolii Kurotych +# - Remove unnecessary --coverage flag +# +# 2019-12-13, FeRD (Frank Dana) +# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor +# of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments. +# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY +# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list +# - Set lcov basedir with -b argument +# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be +# overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().) +# - Delete output dir, .info file on 'make clean' +# - Remove Python detection, since version mismatches will break gcovr +# - Minor cleanup (lowercase function names, update examples...) +# +# 2019-12-19, FeRD (Frank Dana) +# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets +# +# 2020-01-19, Bob Apthorpe +# - Added gfortran support +# +# 2020-02-17, FeRD (Frank Dana) +# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters +# in EXCLUDEs, and remove manual escaping from gcovr targets +# +# 2021-01-19, Robin Mueller +# - Add CODE_COVERAGE_VERBOSE option which will allow to print out commands which are run +# - Added the option for users to set the GCOVR_ADDITIONAL_ARGS variable to supply additional +# flags to the gcovr command +# +# 2020-05-04, Mihchael Davis +# - Add -fprofile-abs-path to make gcno files contain absolute paths +# - Fix BASE_DIRECTORY not working when defined +# - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines +# +# 2021-05-10, Martin Stump +# - Check if the generator is multi-config before warning about non-Debug builds +# +# 2022-02-22, Marko Wehle +# - Change gcovr output from -o <filename> for --xml <filename> and --html <filename> output respectively. +# This will allow for Multiple Output Formats at the same time by making use of GCOVR_ADDITIONAL_ARGS, e.g. GCOVR_ADDITIONAL_ARGS "--txt". +# +# 2022-09-28, Sebastian Mueller +# - fix append_coverage_compiler_flags_to_target to correctly add flags +# - replace "-fprofile-arcs -ftest-coverage" with "--coverage" (equivalent) +# +# USAGE: +# +# 1. Copy this file into your cmake modules path. +# +# 2. Add the following line to your CMakeLists.txt (best inside an if-condition +# using a CMake option() to enable it just optionally): +# include(CodeCoverage) +# +# 3. Append necessary compiler flags for all supported source files: +# append_coverage_compiler_flags() +# Or for specific target: +# append_coverage_compiler_flags_to_target(YOUR_TARGET_NAME) +# +# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og +# +# 4. If you need to exclude additional directories from the report, specify them +# using full paths in the COVERAGE_EXCLUDES variable before calling +# setup_target_for_coverage_*(). +# Example: +# set(COVERAGE_EXCLUDES +# '${PROJECT_SOURCE_DIR}/src/dir1/*' +# '/path/to/my/src/dir2/*') +# Or, use the EXCLUDE argument to setup_target_for_coverage_*(). +# Example: +# setup_target_for_coverage_lcov( +# NAME coverage +# EXECUTABLE testrunner +# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*") +# +# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set +# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR) +# Example: +# set(COVERAGE_EXCLUDES "dir1/*") +# setup_target_for_coverage_gcovr_html( +# NAME coverage +# EXECUTABLE testrunner +# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src" +# EXCLUDE "dir2/*") +# +# 5. Use the functions described below to create a custom make target which +# runs your test executable and produces a code coverage report. +# +# 6. Build a Debug build: +# cmake -DCMAKE_BUILD_TYPE=Debug .. +# make +# make my_coverage_target +# + +include(CMakeParseArguments) + +option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE) + +# Check prereqs +find_program( GCOV_PATH gcov ) +find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) +find_program( FASTCOV_PATH NAMES fastcov fastcov.py ) +find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) +find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) +find_program( CPPFILT_PATH NAMES c++filt ) + +if(NOT GCOV_PATH) + message(FATAL_ERROR "gcov not found! Aborting...") +endif() # NOT GCOV_PATH + +# Check supported compiler (Clang, GNU and Flang) +get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach(LANG ${LANGUAGES}) + if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) + message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") + endif() + elseif(NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" + AND NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(LLVM)?[Ff]lang") + message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...") + endif() +endforeach() + +set(COVERAGE_COMPILER_FLAGS "-g --coverage -fprofile-update=atomic" + CACHE INTERNAL "") + +if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path) + if(HAVE_cxx_fprofile_abs_path) + set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + endif() +endif() +if(CMAKE_C_COMPILER_ID MATCHES "(GNU|Clang)") + include(CheckCCompilerFlag) + check_c_compiler_flag(-fprofile-abs-path HAVE_c_fprofile_abs_path) + if(HAVE_c_fprofile_abs_path) + set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + endif() +endif() + +set(CMAKE_Fortran_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the Fortran compiler during coverage builds." + FORCE ) +set(CMAKE_CXX_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C++ compiler during coverage builds." + FORCE ) +set(CMAKE_C_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C compiler during coverage builds." + FORCE ) +set(CMAKE_EXE_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used for linking binaries during coverage builds." + FORCE ) +set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used by the shared libraries linker during coverage builds." + FORCE ) +mark_as_advanced( + CMAKE_Fortran_FLAGS_COVERAGE + CMAKE_CXX_FLAGS_COVERAGE + CMAKE_C_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) + +get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) + message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") +endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + link_libraries(gcov) +endif() + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_lcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# ) +function(setup_target_for_coverage_lcov) + + set(options NO_DEMANGLE SONARQUBE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() # NOT LCOV_PATH + + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() # NOT GENHTML_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(LCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND LCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES LCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Setting up commands which will be run to generate coverage data. + # Cleanup lcov + set(LCOV_CLEAN_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory . + -b ${BASEDIR} --zerocounters + ) + # Create baseline to make sure untouched files show up in the report + set(LCOV_BASELINE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b + ${BASEDIR} -o ${Coverage_NAME}.base + ) + # Run tests + set(LCOV_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Capturing lcov counters and generating report + set(LCOV_CAPTURE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b + ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture + ) + # add baseline counters + set(LCOV_BASELINE_COUNT_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base + -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total + ) + # filter collected data to final coverage report + set(LCOV_FILTER_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove + ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info + ) + # Generate HTML output + set(LCOV_GEN_HTML_CMD + ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o + ${Coverage_NAME} ${Coverage_NAME}.info + ) + if(${Coverage_SONARQUBE}) + # Generate SonarQube output + set(GCOVR_XML_CMD + ${GCOVR_PATH} --sonarqube ${Coverage_NAME}_sonarqube.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + set(GCOVR_XML_CMD_COMMAND + COMMAND ${GCOVR_XML_CMD} + ) + set(GCOVR_XML_CMD_BYPRODUCTS ${Coverage_NAME}_sonarqube.xml) + set(GCOVR_XML_CMD_COMMENT COMMENT "SonarQube code coverage info report saved in ${Coverage_NAME}_sonarqube.xml.") + endif() + + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + message(STATUS "Command to clean up lcov: ") + string(REPLACE ";" " " LCOV_CLEAN_CMD_SPACED "${LCOV_CLEAN_CMD}") + message(STATUS "${LCOV_CLEAN_CMD_SPACED}") + + message(STATUS "Command to create baseline: ") + string(REPLACE ";" " " LCOV_BASELINE_CMD_SPACED "${LCOV_BASELINE_CMD}") + message(STATUS "${LCOV_BASELINE_CMD_SPACED}") + + message(STATUS "Command to run the tests: ") + string(REPLACE ";" " " LCOV_EXEC_TESTS_CMD_SPACED "${LCOV_EXEC_TESTS_CMD}") + message(STATUS "${LCOV_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to capture counters and generate report: ") + string(REPLACE ";" " " LCOV_CAPTURE_CMD_SPACED "${LCOV_CAPTURE_CMD}") + message(STATUS "${LCOV_CAPTURE_CMD_SPACED}") + + message(STATUS "Command to add baseline counters: ") + string(REPLACE ";" " " LCOV_BASELINE_COUNT_CMD_SPACED "${LCOV_BASELINE_COUNT_CMD}") + message(STATUS "${LCOV_BASELINE_COUNT_CMD_SPACED}") + + message(STATUS "Command to filter collected data: ") + string(REPLACE ";" " " LCOV_FILTER_CMD_SPACED "${LCOV_FILTER_CMD}") + message(STATUS "${LCOV_FILTER_CMD_SPACED}") + + message(STATUS "Command to generate lcov HTML output: ") + string(REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD}") + message(STATUS "${LCOV_GEN_HTML_CMD_SPACED}") + + if(${Coverage_SONARQUBE}) + message(STATUS "Command to generate SonarQube XML output: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + COMMAND ${LCOV_CLEAN_CMD} + COMMAND ${LCOV_BASELINE_CMD} + COMMAND ${LCOV_EXEC_TESTS_CMD} + COMMAND ${LCOV_CAPTURE_CMD} + COMMAND ${LCOV_BASELINE_COUNT_CMD} + COMMAND ${LCOV_FILTER_CMD} + COMMAND ${LCOV_GEN_HTML_CMD} + ${GCOVR_XML_CMD_COMMAND} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.base + ${Coverage_NAME}.capture + ${Coverage_NAME}.total + ${Coverage_NAME}.info + ${GCOVR_XML_CMD_BYPRODUCTS} + ${Coverage_NAME}/index.html + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." + ) + + # Show where to find the lcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." + ${GCOVR_XML_CMD_COMMENT} + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_lcov + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_xml( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_xml) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_XML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Running gcovr + set(GCOVR_XML_CMD + ${GCOVR_PATH} --xml ${Coverage_NAME}.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_XML_EXEC_TESTS_CMD_SPACED "${GCOVR_XML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_XML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to generate gcovr XML coverage data: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_XML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_XML_CMD} + + BYPRODUCTS ${Coverage_NAME}.xml + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce Cobertura code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." + ) +endfunction() # setup_target_for_coverage_gcovr_xml + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_html( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_html) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_HTML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Create folder + set(GCOVR_HTML_FOLDER_CMD + ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} + ) + # Running gcovr + set(GCOVR_HTML_CMD + ${GCOVR_PATH} --html ${Coverage_NAME}/index.html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_HTML_EXEC_TESTS_CMD_SPACED "${GCOVR_HTML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_HTML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to create a folder: ") + string(REPLACE ";" " " GCOVR_HTML_FOLDER_CMD_SPACED "${GCOVR_HTML_FOLDER_CMD}") + message(STATUS "${GCOVR_HTML_FOLDER_CMD_SPACED}") + + message(STATUS "Command to generate gcovr HTML coverage data: ") + string(REPLACE ";" " " GCOVR_HTML_CMD_SPACED "${GCOVR_HTML_CMD}") + message(STATUS "${GCOVR_HTML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_HTML_FOLDER_CMD} + COMMAND ${GCOVR_HTML_CMD} + + BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html # report directory + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce HTML code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_gcovr_html + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_fastcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/" "src/dir2/" # Patterns to exclude. +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# SKIP_HTML # Don't create html report +# POST_CMD perl -i -pe s!${PROJECT_SOURCE_DIR}/!!g ctest_coverage.json # E.g. for stripping source dir from file paths +# ) +function(setup_target_for_coverage_fastcov) + + set(options NO_DEMANGLE SKIP_HTML) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS POST_CMD) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT FASTCOV_PATH) + message(FATAL_ERROR "fastcov not found! Aborting...") + endif() + + if(NOT Coverage_SKIP_HTML AND NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (Patterns, not paths, for fastcov) + set(FASTCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_FASTCOV_EXCLUDES}) + list(APPEND FASTCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES FASTCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Set up commands which will be run to generate coverage data + set(FASTCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) + + set(FASTCOV_CAPTURE_CMD ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --process-gcno + --output ${Coverage_NAME}.json + --exclude ${FASTCOV_EXCLUDES} + ) + + set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH} + -C ${Coverage_NAME}.json --lcov --output ${Coverage_NAME}.info + ) + + if(Coverage_SKIP_HTML) + set(FASTCOV_HTML_CMD ";") + else() + set(FASTCOV_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} + -o ${Coverage_NAME} ${Coverage_NAME}.info + ) + endif() + + set(FASTCOV_POST_CMD ";") + if(Coverage_POST_CMD) + set(FASTCOV_POST_CMD ${Coverage_POST_CMD}) + endif() + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Code coverage commands for target ${Coverage_NAME} (fastcov):") + + message(" Running tests:") + string(REPLACE ";" " " FASTCOV_EXEC_TESTS_CMD_SPACED "${FASTCOV_EXEC_TESTS_CMD}") + message(" ${FASTCOV_EXEC_TESTS_CMD_SPACED}") + + message(" Capturing fastcov counters and generating report:") + string(REPLACE ";" " " FASTCOV_CAPTURE_CMD_SPACED "${FASTCOV_CAPTURE_CMD}") + message(" ${FASTCOV_CAPTURE_CMD_SPACED}") + + message(" Converting fastcov .json to lcov .info:") + string(REPLACE ";" " " FASTCOV_CONVERT_CMD_SPACED "${FASTCOV_CONVERT_CMD}") + message(" ${FASTCOV_CONVERT_CMD_SPACED}") + + if(NOT Coverage_SKIP_HTML) + message(" Generating HTML report: ") + string(REPLACE ";" " " FASTCOV_HTML_CMD_SPACED "${FASTCOV_HTML_CMD}") + message(" ${FASTCOV_HTML_CMD_SPACED}") + endif() + if(Coverage_POST_CMD) + message(" Running post command: ") + string(REPLACE ";" " " FASTCOV_POST_CMD_SPACED "${FASTCOV_POST_CMD}") + message(" ${FASTCOV_POST_CMD_SPACED}") + endif() + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + + # Cleanup fastcov + COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --zerocounters + + COMMAND ${FASTCOV_EXEC_TESTS_CMD} + COMMAND ${FASTCOV_CAPTURE_CMD} + COMMAND ${FASTCOV_CONVERT_CMD} + COMMAND ${FASTCOV_HTML_CMD} + COMMAND ${FASTCOV_POST_CMD} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.info + ${Coverage_NAME}.json + ${Coverage_NAME}/index.html # report directory + + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report." + ) + + set(INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME}.info and ${Coverage_NAME}.json.") + if(NOT Coverage_SKIP_HTML) + string(APPEND INFO_MSG " Open ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html in your browser to view the coverage report.") + endif() + # Show where to find the fastcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG} + ) + +endfunction() # setup_target_for_coverage_fastcov + +function(append_coverage_compiler_flags) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") +endfunction() # append_coverage_compiler_flags + +# Setup coverage for specific library +function(append_coverage_compiler_flags_to_target name) + separate_arguments(_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS}") + target_compile_options(${name} PRIVATE ${_flag_list}) + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + target_link_libraries(${name} PRIVATE gcov) + endif() +endfunction() diff --git a/cmake/Modules/DefineCMakeDefaults.cmake b/cmake/Modules/DefineCMakeDefaults.cmake index ef4fb337..6f369faa 100644 --- a/cmake/Modules/DefineCMakeDefaults.cmake +++ b/cmake/Modules/DefineCMakeDefaults.cmake @@ -6,7 +6,7 @@ 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 +# are preferred over the already installed ones # since cmake 2.4.1 set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE ON) diff --git a/cmake/Modules/ExtractSymbols.cmake b/cmake/Modules/ExtractSymbols.cmake index f7829334..95c850b6 100644 --- a/cmake/Modules/ExtractSymbols.cmake +++ b/cmake/Modules/ExtractSymbols.cmake @@ -50,15 +50,28 @@ file(READ ${HEADERS_LIST_FILE} HEADERS_LIST) set(symbols) foreach(header ${HEADERS_LIST}) + file(READ ${header} header_content) # Filter only lines containing the FILTER_PATTERN - file(STRINGS ${header} contain_filter - REGEX "^.*${FILTER_PATTERN}.*[(]" + # separated from the function name with one optional newline + string(REGEX MATCHALL + "${FILTER_PATTERN}[^(\n]*\n?[^(\n]*[(]" + contain_filter + "${header_content}" + ) + + # Remove the optional newline now + string(REGEX REPLACE + "(.+)\n?(.*)" + "\\1\\2" + oneline + "${contain_filter}" ) # Remove function-like macros - foreach(line ${contain_filter}) - if (NOT ${line} MATCHES ".*#[ ]*define") + # and anything with two underscores that sounds suspicious + foreach(line ${oneline}) + if (NOT ${line} MATCHES ".*(#[ ]*define|__)") list(APPEND not_macro ${line}) endif() endforeach() diff --git a/cmake/Modules/FindABIMap.cmake b/cmake/Modules/FindABIMap.cmake index 5117b498..e7f725d2 100644 --- a/cmake/Modules/FindABIMap.cmake +++ b/cmake/Modules/FindABIMap.cmake @@ -220,13 +220,12 @@ # Search for python which is required if (ABIMap_FIND_REQURIED) - find_package(PythonInterp REQUIRED) + find_package(Python REQUIRED) else() - find_package(PythonInterp) + find_package(Python) endif() - -if (PYTHONINTERP_FOUND) +if (TARGET Python::Interpreter) # 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) diff --git a/cmake/Modules/FindArgp.cmake b/cmake/Modules/FindArgp.cmake index 454965ac..13d74637 100644 --- a/cmake/Modules/FindArgp.cmake +++ b/cmake/Modules/FindArgp.cmake @@ -1,4 +1,8 @@ # - Try to find ARGP +# +# The argp can be either shipped as part of libc (ex. glibc) or as a separate +# library that requires additional linking (ex. Windows, Mac, musl libc, ...) +# # Once done this will define # # ARGP_ROOT_DIR - Set this variable to the root installation of ARGP @@ -60,7 +64,7 @@ if (ARGP_LIBRARY) endif (ARGP_LIBRARY) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(ARGP DEFAULT_MSG ARGP_LIBRARIES ARGP_INCLUDE_DIR) +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/FindGSSAPI.cmake b/cmake/Modules/FindGSSAPI.cmake index 4c3f44b1..d227d8dd 100644 --- a/cmake/Modules/FindGSSAPI.cmake +++ b/cmake/Modules/FindGSSAPI.cmake @@ -5,7 +5,7 @@ # 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_MIT - set to TRUE 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 diff --git a/config.h.cmake b/config.h.cmake index 34690304..391ee162 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -82,30 +82,18 @@ /* Define to 1 if you have the <pthread.h> header file. */ #cmakedefine HAVE_PTHREAD_H 1 -/* Define to 1 if you have eliptic curve cryptography in openssl */ +/* Define to 1 if you have elliptic curve cryptography in openssl */ #cmakedefine HAVE_OPENSSL_ECC 1 -/* Define to 1 if you have eliptic curve cryptography in gcrypt */ +/* Define to 1 if you have elliptic curve cryptography in gcrypt */ #cmakedefine HAVE_GCRYPT_ECC 1 -/* Define to 1 if you have eliptic curve cryptography */ +/* Define to 1 if you have elliptic 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 */ +/* Define to 1 if you have gl_flags as a glob_t struct 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 - -/* Define to 1 if you have OpenSSL with Poly1305 support */ -#cmakedefine HAVE_OPENSSL_EVP_POLY1305 1 - /* Define to 1 if you have gcrypt with ChaCha20/Poly1305 support */ #cmakedefine HAVE_GCRYPT_CHACHA_POLY 1 @@ -114,21 +102,12 @@ /* Define to 1 if you have the `EVP_chacha20' function. */ #cmakedefine HAVE_OPENSSL_EVP_CHACHA20 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 `EVP_KDF_CTX_new_id' or `EVP_KDF_CTX_new` function. */ +#cmakedefine HAVE_OPENSSL_EVP_KDF_CTX 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 @@ -225,6 +204,7 @@ #cmakedefine HAVE_FALLTHROUGH_ATTRIBUTE 1 #cmakedefine HAVE_UNUSED_ATTRIBUTE 1 +#cmakedefine HAVE_WEAK_ATTRIBUTE 1 #cmakedefine HAVE_CONSTRUCTOR_ATTRIBUTE 1 #cmakedefine HAVE_DESTRUCTOR_ATTRIBUTE 1 @@ -275,6 +255,9 @@ /* Define to 1 if you want to enable PKCS #11 URI support */ #cmakedefine WITH_PKCS11_URI 1 +/* Define to 1 if we want to build a support for PKCS #11 provider. */ +#cmakedefine WITH_PKCS11_PROVIDER 1 + /*************************** ENDIAN *****************************/ /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 259424b4..c8b5a7e5 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -14,11 +14,13 @@ if (DOXYGEN_FOUND) set(DOXYGEN_OPTIMIZE_OUTPUT_FOR_C YES) set(DOXYGEN_MARKDOWN_SUPPORT YES) set(DOXYGEN_FULL_PATH_NAMES NO) + set(DOXYGEN_GENERATE_TAGFILE "tags.xml") set(DOXYGEN_PREDEFINED DOXYGEN WITH_SERVER WITH_SFTP - PRINTF_ATTRIBUTE(x,y)) + PRINTF_ATTRIBUTE\(x,y\)) + set(DOXYGEN_DOT_GRAPH_MAX_NODES 100) set(DOXYGEN_EXCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/that_style) set(DOXYGEN_HTML_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/that_style/header.html) @@ -34,6 +36,44 @@ if (DOXYGEN_FOUND) ${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) + set(DOXYGEN_EXCLUDE_PATTERNS */src/external/* fe25519.h ge25519.h sc25519.h + blf.h) + set(DOXYGEN_EXCLUDE_SYMBOLS_STRUCTS chacha20_poly1305_keysched,dh_ctx,dh_ctx,dh_keypair,error_struct, + packet_struct,pem_get_password_struct,ssh_tokens_st, + sftp_attributes_struct,sftp_client_message_struct, + sftp_dir_struct,sftp_ext_struct,sftp_file_struct,sftp_message_struct, + sftp_packet_struct,sftp_request_queue_struct,sftp_session_struct, + sftp_status_message_struct,ssh_agent_state_struct, + ssh_agent_struct,ssh_auth_auto_state_struct,ssh_auth_request, + ssh_bind_config_keyword_table_s,ssh_bind_config_match_keyword_table_s, + ssh_bind_struct,ssh_buffer_struct,ssh_channel_callbacks_struct, + ssh_channel_read_termination_struct,ssh_channel_request, + ssh_channel_request_open,ssh_channel_struct,ssh_cipher_struct, + ssh_common_struct,ssh_config_keyword_table_s, + ssh_config_match_keyword_table_s,ssh_connector_struct, + ssh_counter_struct,ssh_crypto_struct,ssh_event_fd_wrapper, + ssh_event_struct,ssh_global_request,ssh_gssapi_struct,ssh_hmac_struct, + ssh_iterator,ssh_kbdint_struct,ssh_kex_struct,ssh_key_struct, + ssh_knownhosts_entry,ssh_list,ssh_mac_ctx_struct,ssh_message_struct, + ssh_packet_callbacks_struct,ssh_packet_header,ssh_poll_ctx_struct, + ssh_poll_handle_struct,ssh_pollfd_struct,ssh_private_key_struct, + ssh_public_key_struct,ssh_scp_struct,ssh_service_request, + ssh_session_struct,ssh_signature_struct,ssh_socket_struct, + ssh_string_struct,ssh_threads_callbacks_struct,ssh_timestamp,) + set(DOXYGEN_EXCLUDE_SYMBOLS_MACRO SSH_FXP*,SSH_SOCKET*,SERVERBANNER,SOCKOPT_TYPE_ARG4,SSH_FILEXFER*, + SSH_FXF*,SSH_S_*,SFTP_*,NSS_BUFLEN_PASSWD,CLOCK,MAX_LINE_SIZE, + PKCS11_URI,KNOWNHOSTS_MAXTYPES,) + set(DOXYGEN_EXCLUDE_SYMBOLS_TYPEDEFS sftp_attributes,sftp_client_message,sftp_dir,sftp_ext,sftp_file, + sftp_message,sftp_packet,sftp_request_queue,sftp_session, + sftp_status_message,sftp_statvfs_t,poll_fn,ssh_callback_int, + ssh_callback_data,ssh_callback_int_int,ssh_message_callback, + ssh_channel_callback_int,ssh_channel_callback_data,ssh_callbacks, + ssh_gssapi_select_oid_callback,ssh_gssapi_accept_sec_ctx_callback, + ssh_gssapi_verify_mic_callback,ssh_server_callbacks,ssh_socket_callbacks, + ssh_packet_callbacks,ssh_channel_callbacks,ssh_bind,ssh_bind_callbacks,) + set(DOXYGEN_EXCLUDE_SYMBOLS ${DOXYGEN_EXCLUDE_SYMBOLS_STRUCTS} + ${DOXYGEN_EXCLUDE_SYMBOLS_MACRO} + ${DOXYGEN_EXCLUDE_SYMBOLS_TYPEDEFS}) # This updates the Doxyfile if we do changes here set(_doxyfile_template "${CMAKE_BINARY_DIR}/CMakeDoxyfile.in") @@ -44,6 +84,8 @@ if (DOXYGEN_FOUND) ${CMAKE_SOURCE_DIR}/include/libssh ${CMAKE_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}) + + add_custom_target(docs_coverage COMMAND ${CMAKE_SOURCE_DIR}/doc/doc_coverage.sh ${CMAKE_BINARY_DIR}) endif() # DOXYGEN_FOUND endif() # CMAKE_VERSION diff --git a/doc/authentication.dox b/doc/authentication.dox index 3196f645..7d0ab81d 100644 --- a/doc/authentication.dox +++ b/doc/authentication.dox @@ -33,6 +33,9 @@ The process of authenticating by public key to a server is the following: 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. + - when several identity files are specified, then the order of processing of + these files is from the last-mentioned to the first one + (if specified in the ~/.ssh/config, then starting from the bottom to the top). The function ssh_userauth_autopubkey() does this using the available keys in "~/.ssh/". The return values are the following: diff --git a/doc/curve25519-sha256@libssh.org.txt b/doc/curve25519-sha256@libssh.org.txt index 75541902..04d88575 100644 --- a/doc/curve25519-sha256@libssh.org.txt +++ b/doc/curve25519-sha256@libssh.org.txt @@ -3,13 +3,13 @@ curve25519-sha256@libssh.org.txt Aris Adamantiadis <aris@badcode.be> 1. Introduction -This document describes the key exchange methode curve25519-sha256@libssh.org +This document describes the key exchange method 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 +into software, 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 @@ -42,8 +42,8 @@ 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 +SSH_MSG_KEX_ECDH_INIT --------> + Verify that client public key length is 32 bytes. Generate ephemeral key pair. Compute shared secret. @@ -55,7 +55,7 @@ Compute shared secret. Generate exchange hash. Verify server's signature. -* Optional but strongly recommanded as this protects against MITM attacks. +* Optional but strongly recommended as this protects against MITM attacks. This is implemented using the same messages as described in RFC5656 chapter 4 @@ -109,7 +109,7 @@ This number is calculated using the following procedure: 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 + This conversion follows the network byte order. This step differs from RFC5656. [RFC5656] https://tools.ietf.org/html/rfc5656 diff --git a/doc/doc_coverage.sh b/doc/doc_coverage.sh new file mode 100755 index 00000000..2f653275 --- /dev/null +++ b/doc/doc_coverage.sh @@ -0,0 +1,52 @@ +#!/bin/bash +################################################################################ +# .doc_coverage.sh # +# Script to detect overall documentation coverage of libssh. The script uses # +# doxygen to generate the documentation then parses it's output. # +# # +# maintainer: Norbert Pocs <npocs@redhat.com> # +################################################################################ +BUILD_DIR="$1" +DOXYFILE_PATH="$BUILD_DIR/doc/Doxyfile.docs" +INDEX_XML_PATH="$BUILD_DIR/doc/xml/index.xml" +# filters +F_EXCLUDE_FILES=' wrapper.h legacy.h crypto.h priv.h chacha.h curve25519.h ' +F_UNDOC_FUNC='(function).*is not documented' +F_FUNC='kind="function"' +F_HEADERS='libssh_8h_|group__libssh__' +F_CUT_BEFORE='.*<name>' +F_CUT_AFTER='<\/name><\/member>' +# Doxygen options +O_QUIET='QUIET=YES' +O_GEN_XML='GENERATE_XML=YES' + +# check if build dir given +if [ $# -eq 0 ]; then + echo "Please provide the build directory e.g.: ./build" + exit 255 +fi + +# modify doxyfile to our needs: +# QUIET - less output +# GENERATE_XML - xml needed to inspect all the functions +# (note: the options are needed to be on separate lines) +# We want to exclude irrelevant files +MOD_DOXYFILE=$(cat "$DOXYFILE_PATH"; echo "$O_QUIET"; echo "$O_GEN_XML") +MOD_DOXYFILE=${MOD_DOXYFILE//EXCLUDE_PATTERNS.*=/EXCLUDE_PATTERNS=$F_EXCLUDE_FILES/g} + +# call doxygen to get the warning messages +# and also generate the xml for inspection +DOXY_WARNINGS=$(echo "$MOD_DOXYFILE" | doxygen - 2>&1) + +# get the number of undocumented functions +UNDOC_FUNC=$(echo "$DOXY_WARNINGS" | grep -cE "$F_UNDOC_FUNC") + +# filter out the lines consisting of functions of our interest +FUNC_LINES=$(grep "$F_FUNC" "$INDEX_XML_PATH" | grep -E "$F_HEADERS") +# cut the irrelevant information and leave just the function names +ALL_FUNC=$(echo "$FUNC_LINES" | sed -e "s/$F_CUT_BEFORE//g" -e "s/$F_CUT_AFTER//") +# remove duplicates and get the number of functions +ALL_FUNC=$(echo "$ALL_FUNC" | sort - | uniq | wc -l) + +# percentage of the documented functions +awk "BEGIN {printf \"Documentation coverage is %.2f%\n\", 100 - (${UNDOC_FUNC}/${ALL_FUNC}*100)}" diff --git a/doc/forwarding.dox b/doc/forwarding.dox index ca3b94f8..2b202b4d 100644 --- a/doc/forwarding.dox +++ b/doc/forwarding.dox @@ -165,6 +165,8 @@ int web_server(ssh_session session) char buffer[256]; int nbytes, nwritten; int port = 0; + char *peer_address = NULL; + int peer_port = 0; char *helloworld = "" "HTTP/1.1 200 OK\n" "Content-Type: text/html\n" @@ -187,7 +189,8 @@ int web_server(ssh_session session) return rc; } - channel = ssh_channel_accept_forward(session, 60000, &port); + channel = ssh_channel_open_forward_port(session, 60000, &port, + &peer_address, &peer_port); if (channel == NULL) { fprintf(stderr, "Error waiting for incoming connection: %s\n", @@ -204,6 +207,7 @@ int web_server(ssh_session session) ssh_get_error(session)); ssh_channel_send_eof(channel); ssh_channel_free(channel); + ssh_string_free_char(peer_address); return SSH_ERROR; } if (strncmp(buffer, "GET /", 5)) continue; @@ -216,13 +220,15 @@ int web_server(ssh_session session) ssh_get_error(session)); ssh_channel_send_eof(channel); ssh_channel_free(channel); + ssh_string_free_char(peer_address); return SSH_ERROR; } - printf("Sent answer\n"); + printf("Sent answer to %s:%d\n", peer_address, peer_port); } ssh_channel_send_eof(channel); ssh_channel_free(channel); + ssh_string_free_char(peer_address); return SSH_OK; } @endcode diff --git a/doc/guided_tour.dox b/doc/guided_tour.dox index 69576f18..904a739e 100644 --- a/doc/guided_tour.dox +++ b/doc/guided_tour.dox @@ -5,7 +5,7 @@ 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 + server public key authentication, i.e. RSA, ED25519 or ECDSA. You can choose cryptographic algorithms you trust and compression algorithms if any. You must of course set up the hostname. @@ -15,7 +15,7 @@ A SSH session goes through the following steps: file. - The client must authenticate: the classical ways are password, or - public keys (from dsa and rsa key-pairs generated by openssh). + public keys (from ecdsa, ed25519 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 diff --git a/doc/introduction.dox b/doc/introduction.dox index f2f3d3dd..8d2aa1d5 100644 --- a/doc/introduction.dox +++ b/doc/introduction.dox @@ -44,6 +44,10 @@ Table of contents: @subpage libssh_tutor_threads +@subpage libssh_tutor_pkcs11 + +@subpage libssh_tutor_sftp_aio + @subpage libssh_tutor_todo */ diff --git a/doc/mainpage.dox b/doc/mainpage.dox index e40d8a15..04197603 100644 --- a/doc/mainpage.dox +++ b/doc/mainpage.dox @@ -20,7 +20,7 @@ the interesting functions as you go. The libssh library provides: - <strong>Key Exchange Methods</strong>: <i>curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521</i>, diffie-hellman-group1-sha1, diffie-hellman-group14-sha1 - - <strong>Public Key Algorithms</strong>: ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, ssh-rsa, rsa-sha2-512, rsa-sha2-256,ssh-dss + - <strong>Public Key Algorithms</strong>: ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, ssh-rsa, rsa-sha2-512, rsa-sha2-256 - <strong>Ciphers</strong>: <i>aes256-ctr, aes192-ctr, aes128-ctr</i>, aes256-cbc (rijndael-cbc@lysator.liu.se), aes192-cbc, aes128-cbc, 3des-cbc, blowfish-cbc - <strong>Compression Schemes</strong>: zlib, <i>zlib@openssh.com</i>, none - <strong>MAC hashes</strong>: hmac-sha1, hmac-sha2-256, hmac-sha2-512, hmac-md5 @@ -38,7 +38,7 @@ The libssh library provides: @section main-additional-features Additional Features - Client <b>and</b> server support - - SSHv2 and SSHv1 protocol support + - SSHv2 protocol support - Supports <a href="https://test.libssh.org/" target="_blank">Linux, UNIX, BSD, Solaris, OS/2 and Windows</a> - Automated test cases with nightly <a href="https://test.libssh.org/" target="_blank">tests</a> - Event model based on poll(2), or a poll(2)-emulation. @@ -149,7 +149,7 @@ The libssh Team @subsection main-rfc-secsh Secure Shell (SSH) -The following RFC documents described SSH-2 protcol as an Internet standard. +The following RFC documents described SSH-2 protocol as an Internet standard. - <a href="https://tools.ietf.org/html/rfc4250" target="_blank">RFC 4250</a>, The Secure Shell (SSH) Protocol Assigned Numbers @@ -213,15 +213,15 @@ It was later modified and expanded by the following RFCs. Use of RSA Keys with SHA-256 and SHA-512 in the Secure Shell (SSH) Protocol - <a href="https://tools.ietf.org/html/rfc8709" target="_blank">RFC 8709</a>, Ed25519 and Ed448 Public Key Algorithms for the Secure Shell (SSH) Protocol + - <a href="https://tools.ietf.org/html/rfc8709" target="_blank">RFC 8731</a>, + Secure Shell (SSH) Key Exchange Method Using Curve25519 and Curve448 + - <a href="https://tools.ietf.org/html/rfc9142" target="_blank">RFC 9142</a>, + Key Exchange (KEX) Method Updates and Recommendations for Secure Shell (SSH) There are also drafts that are being currently developed and followed. - - <a href="https://tools.ietf.org/html/draft-ietf-curdle-ssh-kex-sha2-10" target="_blank">draft-ietf-curdle-ssh-kex-sha2-10</a> - Key Exchange (KEX) Method Updates and Recommendations for Secure Shell (SSH) - - <a href="https://tools.ietf.org/html/draft-miller-ssh-agent-03" target="_blank">draft-miller-ssh-agent-03</a> + - <a href="https://tools.ietf.org/html/draft-miller-ssh-agent-03" target="_blank">draft-miller-ssh-agent-08</a> SSH Agent Protocol - - <a href="https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves-12" target="_blank">draft-ietf-curdle-ssh-curves-12</a> - Secure Shell (SSH) Key Exchange Method using Curve25519 and Curve448 Interesting cryptography documents: diff --git a/doc/pkcs11.dox b/doc/pkcs11.dox index 0bdfc6dc..c2732a81 100644 --- a/doc/pkcs11.dox +++ b/doc/pkcs11.dox @@ -9,11 +9,11 @@ objects stored on the tokens can be uniquely identified is called PKCS #11 URI (Uniform Resource Identifier) and is defined in RFC 7512 (https://tools.ietf.org/html/rfc7512). -Pre-requisites: +# Pre-requisites (OpenSSL < 3.0): -OpenSSL defines an abstract layer called the "engine" to achieve cryptographic -acceleration. The engine_pkcs11 module acts like an interface between the PKCS #11 -modules and the OpenSSL engine. +OpenSSL 1.x defines an abstract layer called the "engine" to achieve +cryptographic acceleration. The engine_pkcs11 module acts like an interface +between the PKCS #11 modules and the OpenSSL application. To build and use libssh with PKCS #11 support: 1. Enable the cmake option: $ cmake -DWITH_PKCS11_URI=ON @@ -21,6 +21,20 @@ To build and use libssh with PKCS #11 support: 3. Install and configure engine_pkcs11 (https://github.com/OpenSC/libp11). 4. Plug in a working smart card or configure softhsm (https://www.opendnssec.org/softhsm). +# Pre-requisites (OpenSSL 3.0.8+) + +The OpenSSL 3.0 is deprecating usage of low-level engines in favor of high-level +"providers" to provide alternative implementation of cryptographic operations +or acceleration. + +To build and use libssh with PKCS #11 support using OpenSSL providers: +1. Install and configure pkcs11 provider (https://github.com/latchset/pkcs11-provider). +2. Enable the cmake options: $ cmake -DWITH_PKCS11_URI=ON -DWITH_PKCS11_PROVIDER=ON +3. Build with OpenSSL. +4. Plug in a working smart card or configure softhsm (https://www.opendnssec.org/softhsm). + +# New API functions + The functions ssh_pki_import_pubkey_file() and ssh_pki_import_privkey_file() that import the public and private keys from files respectively are now modified to support PKCS #11 URIs. These functions automatically detect if the provided filename is a file path @@ -31,7 +45,7 @@ corresponding to the PKCS #11 URI are loaded from the PKCS #11 device. If you wish to authenticate using public keys on your own, follow the steps mentioned under "Authentication with public keys" in Chapter 2 - A deeper insight into authentication. -The function pki_uri_import() is used to populate the public/private ssh_key from the +The function pki_uri_import() is used to populate the public/private ssh_key from the engine with PKCS #11 URIs as the look up. Here is a minimalistic example of public key authentication using PKCS #11 URIs: @@ -64,4 +78,10 @@ We recommend the users to provide a specific PKCS #11 URI so that it matches onl If the engine discovers multiple slots that could potentially contain the private keys referenced by the provided PKCS #11 URI, the engine will not try to authenticate. +For testing, the SoftHSM PKCS#11 library is used. But it has some issues with +OpenSSL initialization/cleanup when used with OpenSSL 3.0 so we are using it +indirectly through a p11-kit remoting as described in the following article: + +https://p11-glue.github.io/p11-glue/p11-kit/manual/remoting.html + */ diff --git a/doc/sftp.dox b/doc/sftp.dox index 1f99cfdf..4c176a4b 100644 --- a/doc/sftp.dox +++ b/doc/sftp.dox @@ -139,7 +139,7 @@ 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 +@subsection sftp_write Writing to a file on 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, @@ -203,16 +203,14 @@ int sftp_helloworld(ssh_session session, sftp_session sftp) @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(). +A synchronous read from a remote file is done using sftp_read(). This +section describes how to download a remote file using sftp_read(). The +next section will discuss more about synchronous/asynchronous read/write +operations using libssh sftp API. 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: +request, sftp_read() blocks till the data has been received: @code // Good chunk size @@ -273,87 +271,39 @@ int sftp_read_sync(ssh_session session, sftp_session sftp) } @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. +@subsection sftp_aio Performing an asynchronous read/write on a file on the remote computer -The example below reads a very big file in asynchronous, nonblocking, mode. Each -time the data is not ready yet, a counter is incremented. +sftp_read() performs a "synchronous" read operation on a remote file. +This means that sftp_read() will first request the server to read some +data from the remote file and then would wait until the server response +containing data to read (or an error) arrives at the client side. -@code -// Good chunk size -#define MAX_XFER_BUF_SIZE 16384 +sftp_write() performs a "synchronous" write operation on a remote file. +This means that sftp_write() will first request the server to write some +data to the remote file and then would wait until the server response +containing information about the status of the write operation arrives at the +client side. -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; +If your client program wants to do something other than waiting for the +response after requesting a read/write, the synchronous sftp_read() and +sftp_write() can't be used. In such a case the "asynchronous" sftp aio API +should be used. - 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; - } +Please go through @ref libssh_tutor_sftp_aio for a detailed description +of the sftp aio API. - 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); +The sftp aio API provides two categories of functions : + - sftp_aio_begin_*() : For requesting a read/write from the server. + - sftp_aio_wait_*() : For waiting for the response of a previously + issued read/write request from the server. - 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; - } +Hence, the client program can call sftp_aio_begin_*() to request a read/write +and then can perform any number of operations (other than waiting) before +calling sftp_aio_wait_*() for waiting for the response of the previously +issued request. - return SSH_OK; -} -@endcode +We call read/write operations performed in the manner described above as +"asynchronous" read/write operations on a remote file. @subsection sftp_ls Listing the contents of a directory diff --git a/doc/sftp_aio.dox b/doc/sftp_aio.dox new file mode 100644 index 00000000..9c26f5e1 --- /dev/null +++ b/doc/sftp_aio.dox @@ -0,0 +1,705 @@ +/** + +@page libssh_tutor_sftp_aio Chapter 10: The SFTP asynchronous I/O + +@section sftp_aio_api The SFTP asynchronous I/O + +NOTE : Please read @ref libssh_tutor_sftp before reading this page. The +synchronous sftp_read() and sftp_write() have been described there. + +SFTP AIO stands for "SFTP Asynchronous Input/Output". This API contains +functions which perform async read/write operations on remote files. + +File transfers performed using the asynchronous sftp aio API can be +significantly faster than the file transfers performed using the synchronous +sftp read/write API (see sftp_read() and sftp_write()). + +The sftp aio API functions are divided into two categories : + - sftp_aio_begin_*() [see sftp_aio_begin_read(), sftp_aio_begin_write()]: + These functions send a request for an i/o operation to the server and + provide the caller an sftp aio handle corresponding to the sent request. + + - sftp_aio_wait_*() [see sftp_aio_wait_read(), sftp_aio_wait_write()]: + These functions wait for the server response corresponding to a previously + issued request. Which request ? the request corresponding to the sftp aio + handle supplied by the caller to these functions. + +Conceptually, you can think of the sftp aio handle as a request identifier. + +Technically, the sftp_aio_begin_*() functions dynamically allocate memory to +store information about the i/o request they send and provide the caller a +handle to this memory, we call this handle an sftp aio handle. + +sftp_aio_wait_*() functions use the information stored in that memory (handled +by the caller supplied sftp aio handle) to identify a request, and then they +wait for that request's response. These functions also release the memory +handled by the caller supplied sftp aio handle (except when they return +SSH_AGAIN). + +sftp_aio_free() can also be used to release the memory handled by an sftp aio +handle but unlike the sftp_aio_wait_*() functions, it doesn't wait for a +response. This should be used to release the memory corresponding to an sftp +aio handle when some failure occurs. An example has been provided at the +end of this page to show the usage of sftp_aio_free(). + +To begin with, this tutorial will provide basic examples that describe the +usage of sftp aio API to perform a single read/write operation. + +The later sections describe the usage of the sftp aio API to obtain faster file +transfers as compared to the transfers performed using the synchronous sftp +read/write API. + +On encountering an error, the sftp aio API functions set the sftp and ssh +errors just like any other libssh sftp API function. These errors can be +obtained using sftp_get_error(), ssh_get_error() and ssh_get_error_code(). +The code examples provided on this page ignore error handling for the sake of +brevity. + +@subsection sftp_aio_read Using the sftp aio API for reading (a basic example) + +For performing an async read operation on a sftp file (see sftp_open()), +the first step is to call sftp_aio_begin_read() to send a read request to the +server. The caller is provided an sftp aio handle corresponding to the sent +read request. + +The second step is to pass a pointer to this aio handle to +sftp_aio_wait_read(), this function waits for the server response which +indicates the success/failure of the read request. On success, the response +indicates EOF or contains the data read from the sftp file. + +The following code example shows how a read operation can be performed +on an sftp file using the sftp aio API. + +@code +ssize_t read_chunk(sftp_file file, void *buf, size_t to_read) +{ + ssize_t bytes_requested, bytes_read; + + // Variable to store an sftp aio handle + sftp_aio aio = NULL; + + // Send a read request to the sftp server + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + // Here its possible that (bytes_requested < to_read) as specified in + // the function documentation of sftp_aio_begin_read() + + // Wait for the response of the read request corresponding to the + // sftp aio handle stored in the aio variable. + bytes_read = sftp_aio_wait_read(&aio, buf, to_read); + if (bytes_read == SSH_ERROR) { + // handle error + } + + return bytes_read; +} +@endcode + +@subsection sftp_aio_write Using the sftp aio API for writing (a basic example) + +For performing an async write operation on a sftp file (see sftp_open()), +the first step is to call sftp_aio_begin_write() to send a write request to +the server. The caller is provided an sftp aio handle corresponding to the +sent write request. + +The second step is to pass a pointer to this aio handle to +sftp_aio_wait_write(), this function waits for the server response which +indicates the success/failure of the write request. + +The following code example shows how a write operation can be performed on an +sftp file using the sftp aio API. + +@code +ssize_t write_chunk(sftp_file file, void *buf, size_t to_write) +{ + ssize_t bytes_requested, bytes_written; + + // Variable to store an sftp aio handle + sftp_aio aio = NULL; + + // Send a write request to the sftp server + bytes_requested = sftp_aio_begin_write(file, buf, to_write, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + // Here its possible that (bytes_requested < to_write) as specified in + // the function documentation of sftp_aio_begin_write() + + // Wait for the response of the write request corresponding to + // the sftp aio handle stored in the aio variable. + bytes_written = sftp_aio_wait_write(&aio); + if (bytes_written == SSH_ERROR) { + // handle error + } + + return bytes_written; +} +@endcode + +@subsection sftp_aio_actual_use Using the sftp aio API to speed up a transfer + +The above examples were provided to introduce the sftp aio API. +This is not how the sftp aio API is intended to be used, because the +above usage offers no advantage over the synchronous sftp read/write API +which does the same thing i.e issue a request and then immediately wait for +its response. + +The facility that the sftp aio API provides is that the user can do +anything between issuing a request and getting the corresponding response. +Any number of operations can be performed after calling sftp_aio_begin_*() +[which issues a request] and before calling sftp_aio_wait_*() [which waits +for a response] + +The code can leverage this feature by calling sftp_aio_begin_*() multiple times +to issue multiple requests before calling sftp_aio_wait_*() to wait for the +response of an earlier issued request. This approach will keep a certain number +of requests outstanding at the client side. + +After issuing those requests, while the client code does something else (for +example waiting for an outstanding request's response, processing an obtained +response, issuing another request or any other operation the client wants +to perform), at the same time : + + - Some of those outstanding requests may be travelling over the + network towards the server. + + - Some of the outstanding requests may have reached the server and may + be queued for processing at the server side. + + - Some of the outstanding requests may have been processed and the + corresponding responses may be travelling over the network towards the + client. + + - Some of the responses corresponding to the outstanding requests may + have already reached the client side. + +Clearly in this case, operations that the client performs and operations +involved in transfer/processing of a outstanding request can occur in +parallel. Also, operations involved in transfer/processing of two or more +outstanding requests may also occur in parallel (for example when one request +travels to the server, another request's response may be incoming towards the +client). Such kind of parallelism makes the overall transfer faster as compared +to a transfer performed using the synchronous sftp read/write API. + +When the synchronous sftp read/write API is used to perform a transfer, +a strict sequence is followed: + + - The client issues a single read/write request. + - Then waits for its response. + - On obtaining the response, the client processes it. + - After the processing ends, the client issues the next read/write request. + +A file transfer performed in this manner would be slower than the case where +multiple read/write requests are kept outstanding at the client side. Because +here at any given time, operations related to transfer/processing of only one +request/response pair occurs. This is in contrast to the multiple outstanding +requests scenario where operations related to transfer/processing of multiple +request/response pairs may occur at the same time. + +Although it's true that keeping multiple requests outstanding can speed up a +transfer, those outstanding requests come at a cost of increased memory +consumption both at the client side and the server side. Hence care must be +taken to use a reasonable limit for the number of requests kept outstanding. + +The further sections provide code examples to show how uploads/downloads +can be performed using the sftp aio API and the concept of outstanding requests +discussed in this section. In those code examples, error handling has been +ignored and at some places pseudo code has been used for the sake of brevity. + +The complete code for performing uploads/downloads using the sftp aio API, +can be found at https://gitlab.com/libssh/libssh-mirror/-/tree/master. + + - libssh benchmarks for uploads performed using the sftp aio API [See + tests/benchmarks/bench_sftp.c] + - libssh benchmarks for downloads performed using the sftp aio API. [See + tests/benchmarks/bench_sftp.c] + - libssh sftp ft API code for performing a local to remote transfer (upload). + [See src/sftp_ft.c] + - libssh sftp ft API code for performing a remote to local transfer + (download). [See src/sftp_ft.c] + +@subsection sftp_aio_cap Capping applied by the sftp aio API + +Before the code examples for uploads and downloads, its important +to know about the capping applied by the sftp aio API. + +sftp_aio_begin_read() caps the number of bytes the caller can request +to read from the remote file. That cap is the value of the max_read_length +field of the sftp_limits_t returned by sftp_limits(). Say that cap is LIM +and the caller passes x as the number of bytes to read to +sftp_aio_begin_read(), then (assuming no error occurs) : + + - if x <= LIM, then sftp_aio_begin_read() will request the server + to read x bytes from the remote file, and will return x. + + - if x > LIM, then sftp_aio_begin_read() will request the server + to read LIM bytes from the remote file and will return LIM. + +Hence to request server to read x bytes (> LIM), the caller would have +to call sftp_aio_begin_read() multiple times, typically in a loop and +break out of the loop when the summation of return values of the multiple +sftp_aio_begin_read() calls becomes equal to x. + +For the sake of simplicity, the code example for download in the upcoming +section would always ask sftp_aio_begin_read() to read x <= LIM bytes, +so that its return value is guaranteed to be x, unless an error occurs. + +Similarly, sftp_aio_begin_write() caps the number of bytes the caller +can request to write to the remote file. That cap is the value of +max_write_length field of the sftp_limits_t returned by sftp_limits(). +Say that cap is LIM and the caller passes x as the number of bytes to +write to sftp_aio_begin_write(), then (assuming no error occurs) : + + - if x <= LIM, then sftp_aio_begin_write() will request the server + to write x bytes to the remote file, and will return x. + + - if x > LIM, then sftp_aio_begin_write() will request the server + to write LIM bytes to the remote file and will return LIM. + +Hence to request server to write x bytes (> LIM), the caller would have +to call sftp_aio_begin_write() multiple times, typically in a loop and +break out of the loop when the summation of return values of the multiple +sftp_aio_begin_write() calls becomes equal to x. + +For the sake of simplicity, the code example for upload in the upcoming +section would always ask sftp_aio_begin_write() to write x <= LIM bytes, +so that its return value is guaranteed to be x, unless an error occurs. + +@subsection sftp_aio_download_example Performing a download using the sftp aio API + +Terminologies used in the following code snippets : + + - sftp : The sftp_session opened using sftp_new() and initialised using + sftp_init() + + - file : The sftp file handle of the remote file to download data + from. (See sftp_open()) + + - file_size : the size of the sftp file to download. This size can be obtained + by statting the remote file to download (e.g by using sftp_stat()) + + - We will need to maintain a queue which will be used to store the sftp aio + handles corresponding to the outstanding requests. + +First, we issue the read requests while ensuring that their count +doesn't exceed a particular limit decided by us, and the number of bytes +requested don't exceed the size of the file to download. + +@code +sftp_aio aio = NULL; + +// Chunk size to use for the transfer +size_t chunk_size; + +// For the limits structure that would be used +// by the code to set the chunk size +sftp_limits_t lim = NULL; + +// Max number of requests to keep outstanding at a time +size_t in_flight_requests = 5; + +// Number of bytes for which requests have been sent +size_t total_bytes_requested = 0; + +// Number of bytes which have been downloaded +size_t bytes_downloaded = 0; + +// Buffer to use for the download +char *buffer = NULL; + +// Helper variables +size_t to_read; +ssize_t bytes_requested; + +// Get the sftp limits +lim = sftp_limits(sftp); +if (lim == NULL) { + // handle error +} + +// Set the chunk size for download = the max limit for reading +// The reason for this has been given in the "Capping applied by +// the sftp aio API" section (Its to make the code simpler) +// +// Assigning a size_t type variable a uint64_t type value here, +// theoretically could cause an overflow, but practically +// max_read_length would never exceed SIZE_MAX so its okay. +chunk_size = lim->max_read_length; + +buffer = malloc(chunk_size); +if (buffer == NULL) { + // handle error +} + +... // Code to open the remote file (to download) using sftp_open(). +... // Code to stat the remote file's file size. +... // Code to open the local file in which downloaded data is to be stored. +... // Code to initialize the queue which will be used to store sftp aio + // handles. + +for (i = 0; + i < in_flight_requests && total_bytes_requested < file_size; + ++i) { + to_read = file_size - total_bytes_requested; + if (to_read > chunk_size) { + to_read = chunk_size; + } + + // Issue a read request + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + if ((size_t)bytes_requested < to_read) { + // Should not happen for this code, as the to_read is <= + // max limit for reading (chunk size), so there is no reason + // for sftp_aio_begin_read() to return a lesser value. + } + + total_bytes_requested += (size_t)bytes_requested; + + // Pseudo code + ENQUEUE aio in the queue; +} + +@endcode + +At this point, at max in_flight_requests number of requests may be +outstanding. Now we wait for the response corresponding to the earliest +issued outstanding request. + +On getting that response, we issue another read request if there are +still some bytes in the sftp file (to download) for which we haven't sent the +read request. (This happens when total_bytes_requested < file_size) + +This issuing of another read request (under a condition) is done to +keep the number of outstanding requests equal to the value of the +in_flight_requests variable. + +This process has to be repeated for every remaining outstanding request. + +@code +while (the queue is not empty) { + // Pseudo code + aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles; + + // Wait for the response of the request corresponding to the aio + bytes_read = sftp_aio_wait_read(&aio, buffer, chunk_size); + if (bytes_read == SSH_ERROR) { + //handle error + } + + bytes_downloaded += bytes_read; + if (bytes_read != chunk_size && bytes_downloaded != file_size) { + // A short read encountered on the remote file before reaching EOF, + // short read before reaching EOF should never happen for the sftp aio + // API which respects the max limit for reading. This probably + // indicates a bad server. + } + + // Pseudo code + WRITE bytes_read bytes from the buffer into the local file + in which downloaded data is to be stored ; + + if (total_bytes_requested == file_size) { + // no need to issue more read requests + continue; + } + + // else issue a read request + to_read = file_size - total_bytes_requested; + if (to_read > chunk_size) { + to_read = chunk_size; + } + + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + if ((size_t)bytes_requested < to_read) { + // Should not happen for this code, as the to_read is <= + // max limit for reading (chunk size), so there is no reason + // for sftp_aio_begin_read() to return a lesser value. + } + + total_bytes_requested += bytes_requested; + + // Pseudo code + ENQUEUE aio in the queue; +} + +free(buffer); +sftp_limits_free(lim); + +... // Code to destroy the queue which was used to store the sftp aio + // handles. +@endcode + +After exiting the while (the queue is not empty) loop, the download +would've been complete (assuming no error occurs). + +@subsection sftp_aio_upload_example Performing an upload using the sftp aio API + +Terminologies used in the following code snippets : + + - sftp : The sftp_session opened using sftp_new() and initialised using + sftp_init() + + - file : The sftp file handle of the remote file in which uploaded data + is to be stored. (See sftp_open()) + + - file_size : The size of the local file to upload. This size can be + obtained by statting the local file to upload (e.g by using stat()) + + - We will need maintain a queue which will be used to store the sftp aio + handles corresponding to the outstanding requests. + +First, we issue the write requests while ensuring that their count +doesn't exceed a particular limit decided by us, and the number of bytes +requested to write don't exceed the size of the file to upload. + +@code +sftp_aio aio = NULL; + +// The chunk size to use for the transfer +size_t chunk_size; + +// For the limits structure that would be used by +// the code to set the chunk size +sftp_limits_t lim = NULL; + +// Max number of requests to keep outstanding at a time +size_t in_flight_requests = 5; + +// Total number of bytes for which write requests have been sent +size_t total_bytes_requested = 0; + +// Buffer to use for the upload +char *buffer = NULL; + +// Helper variables +size_t to_write; +ssize_t bytes_requested; + +// Get the sftp limits +lim = sftp_limits(sftp); +if (lim == NULL) { + // handle error +} + +// Set the chunk size for upload = the max limit for writing. +// The reason for this has been given in the "Capping applied by +// the sftp aio API" section (Its to make the code simpler) +// +// Assigning a size_t type variable a uint64_t type value here, +// theoretically could cause an overflow, but practically +// max_write_length would never exceed SIZE_MAX so its okay. +chunk_size = lim->max_write_length; + +buffer = malloc(chunk_size); +if (buffer == NULL) { + // handle error +} + +... // Code to open the local file (to upload) [e.g using open(), fopen()]. +... // Code to stat the local file's file size [e.g using stat()]. +... // Code to open the remote file in which uploaded data will be stored [see + // sftp_open()]. +... // Code to initialize the queue which will be used to store sftp aio + // handles. + +for (i = 0; + i < in_flight_requests && total_bytes_requested < file_size; + ++i) { + to_write = file_size - total_bytes_requested; + if (to_write > chunk_size) { + to_write = chunk_size; + } + + // Pseudo code + READ to_write bytes from the local file (to upload) into the buffer; + + bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + if ((size_t)bytes_requested < to_write) { + // Should not happen for this code, as the to_write is <= + // max limit for writing (chunk size), so there is no reason + // for sftp_aio_begin_write() to return a lesser value. + } + + total_bytes_requested += (size_t)bytes_requested; + + // Pseudo code + ENQUEUE aio in the queue; +} + +@endcode + +At this point, at max in_flight_requests number of requests may be +outstanding. Now we wait for the response corresponding to the earliest +issued outstanding request. + +On getting that response, we issue another write request if there are +still some bytes in the local file (to upload) for which we haven't sent +the write request. (This happens when total_bytes_requested < file_size) + +This issuing of another write request (under a condition) is done to +keep the number of outstanding requests equal to the value of the +in_flight_requests variable. + +This process has to be repeated for every remaining outstanding request. + +@code +while (the queue is not empty) { + // Pseudo code + aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles; + + // Wait for the response of the request corresponding to the aio + bytes_written = sftp_aio_wait_write(&aio); + if (bytes_written == SSH_ERROR) { + // handle error + } + + // sftp_aio_wait_write() won't report a short write, so no need + // to check for a short write here. + + if (total_bytes_requested == file_size) { + // no need to issue more write requests + continue; + } + + // else issue a write request + to_write = file_size - total_bytes_requested; + if (to_write > chunk_size) { + to_write = chunk_size; + } + + // Pseudo code + READ to_write bytes from the local file (to upload) into a buffer; + + bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + if ((size_t)bytes_requested < to_write) { + // Should not happen for this code, as the to_write is <= + // max limit for writing (chunk size), so there is no reason + // for sftp_aio_begin_write() to return a lesser value. + } + + total_bytes_requested += (size_t)bytes_requested; + + // Pseudo code + ENQUEUE aio in the queue; +} + +free(buffer); + +... // Code to destroy the queue which was used to store the sftp aio + // handles. +@endcode + +After exiting the while (the queue is not empty) loop, the upload +would've been complete (assuming no error occurs). + +@subsection sftp_aio_free Example showing the usage of sftp_aio_free() + +The purpose of sftp_aio_free() was discussed at the beginning of this page, +the following code example shows how it can be used during cleanup. + +@code +void print_sftp_error(sftp_session sftp) +{ + if (sftp == NULL) { + return; + } + + fprintf(stderr, "sftp error : %d\n", sftp_get_error(sftp)); + fprintf(stderr, "ssh error : %s\n", ssh_get_error(sftp->session)); +} + +// Returns 0 on success, -1 on error +int write_strings(sftp_file file) +{ + const char * strings[] = { + "This is the first string", + "This is the second string", + "This is the third string", + "This is the fourth string" + }; + + size_t string_count = sizeof(strings) / sizeof(strings[0]); + size_t i; + + sftp_session sftp = NULL; + sftp_aio aio = NULL; + + int rc; + + if (file == NULL) { + return -1; + } + + ... // Code to initialize the queue which will be used to store sftp aio + // handles + + sftp = file->sftp; + for (i = 0; i < string_count; ++i) { + rc = sftp_aio_begin_write(file, + strings[i], + strlen(strings[i]), + &aio); + if (rc == SSH_ERROR) { + print_sftp_error(sftp); + goto err; + } + + // Pseudo code + ENQUEUE aio in the queue of sftp aio handles + } + + for (i = 0; i < string_count; ++i) { + // Pseudo code + aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles; + + rc = sftp_aio_wait_write(&aio); + if (rc == SSH_ERROR) { + print_sftp_error(sftp); + goto err; + } + } + + + ... // Code to destroy the queue in which sftp aio handles were + // stored + + return 0; + +err: + + while (queue is not empty) { + // Pseudo code + aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles; + + sftp_aio_free(aio); + } + + ... // Code to destroy the queue in which sftp aio handles were + // stored. + + return -1; +} + +@endcode + +*/ diff --git a/doc/shell.dox b/doc/shell.dox index 0693bbcc..f51c489c 100644 --- a/doc/shell.dox +++ b/doc/shell.dox @@ -65,8 +65,17 @@ 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(). +If you want define its dimensions (number of rows and columns), +call ssh_channel_request_pty_size() instead. It's also possible to change +the dimensions after creating the pty with ssh_channel_change_pty_size(). + +These two functions configure the pty using the same terminal modes that +stdin has. If stdin isn't a TTY, they use default modes that configure +the pty with in canonical mode and e.g. preserving CR and LF characters. +If you want to change the terminal modes used by the pty (e.g. to change +CRLF handling), use ssh_channel_request_pty_size_modes(). This function +accepts an additional "modes" buffer that is expected to contain encoded +terminal modes according to RFC 4254 section 8. Be your session interactive or not, the next step is to request a shell with ssh_channel_request_shell(). @@ -320,18 +329,36 @@ int interactive_shell_session(ssh_session session, ssh_channel channel) 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(). +To do that, you first declare a callback to manage channel_open_request_x11_function. +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 +#include <libssh/callbacks.h> + +ssh_channel x11channel = NULL; + +ssh_channel x11_open_request_callback(ssh_session session, const char *shost, int sport, void *userdata) +{ + x11channel = ssh_channel_new(session); + return x11channel; +} + int interactive_shell_session(ssh_channel channel) { int rc; - ssh_channel x11channel; + + struct ssh_callbacks_struct cb = + { + .channel_open_request_x11_function = x11_open_request_callback, + .userdata = NULL + }; + + ssh_callbacks_init(&cb); + rc = ssh_set_callbacks(session, &cb); + if (rc != SSH_OK) return rc; rc = ssh_channel_request_pty(channel); if (rc != SSH_OK) return rc; @@ -350,12 +377,15 @@ int interactive_shell_session(ssh_channel channel) } @endcode -Don't forget to set the $DISPLAY environment variable on the remote +Don't forget to check the $DISPLAY environment variable on the remote side, or the remote applications won't try using the X11 tunnel: @code -$ export DISPLAY=:0 +$ echo $DISPLAY +localhost:10.0 $ xclock & @endcode +See an implementation example at https://gitlab.com/libssh/libssh-mirror/-/tree/master/examples/ssh_X11_client.c for details. + */ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1cfefd8b..64458cfc 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -29,40 +29,48 @@ if (UNIX AND NOT WIN32) add_executable(samplesftp samplesftp.c ${examples_SRCS}) target_compile_options(samplesftp PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) target_link_libraries(samplesftp ssh::ssh) + + add_executable(sample_sftpserver sample_sftpserver.c ${examples_SRCS}) + target_compile_options(sample_sftpserver PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(sample_sftpserver ssh::ssh ${ARGP_LIBRARIES}) 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)) + add_executable(ssh-X11-client ssh_X11_client.c ${examples_SRCS}) + target_compile_options(ssh-X11-client PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(ssh-X11-client ssh::ssh) + + if (WITH_SERVER AND (ARGP_LIBRARIES OR HAVE_ARGP_H)) if (HAVE_LIBUTIL) add_executable(ssh_server_fork ssh_server.c) target_compile_options(ssh_server_fork PRIVATE ${DEFAULT_C_COMPILE_FLAGS} -DWITH_FORK) - target_link_libraries(ssh_server_fork ssh::ssh ${ARGP_LIBRARY} util) + target_link_libraries(ssh_server_fork ssh::ssh ${ARGP_LIBRARIES} util) add_executable(ssh_server_pthread ssh_server.c) target_compile_options(ssh_server_pthread PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) - target_link_libraries(ssh_server_pthread ssh::ssh ${ARGP_LIBRARY} pthread util) + target_link_libraries(ssh_server_pthread ssh::ssh ${ARGP_LIBRARIES} pthread util) endif (HAVE_LIBUTIL) if (WITH_GSSAPI AND GSSAPI_FOUND) add_executable(proxy proxy.c) target_compile_options(proxy PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) - target_link_libraries(proxy ssh::ssh ${ARGP_LIBRARY}) + target_link_libraries(proxy ssh::ssh ${ARGP_LIBRARIES}) 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}) + target_link_libraries(sshd_direct-tcpip ssh::ssh ${ARGP_LIBRARIES}) 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}) + target_link_libraries(samplesshd-kbdint ssh::ssh ${ARGP_LIBRARIES}) add_executable(keygen2 keygen2.c ${examples_SRCS}) target_compile_options(keygen2 PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) - target_link_libraries(keygen2 ssh::ssh) + target_link_libraries(keygen2 ssh::ssh ${ARGP_LIBRARIES}) endif() endif (UNIX AND NOT WIN32) @@ -71,9 +79,9 @@ if (WITH_SERVER) add_executable(samplesshd-cb samplesshd-cb.c) target_compile_options(samplesshd-cb PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) target_link_libraries(samplesshd-cb ssh::ssh) - if (ARGP_LIBRARY OR HAVE_ARGP_H) - target_link_libraries(samplesshd-cb ${ARGP_LIBRARY}) - endif(ARGP_LIBRARY OR HAVE_ARGP_H) + if (ARGP_LIBRARIES OR HAVE_ARGP_H) + target_link_libraries(samplesshd-cb ${ARGP_LIBRARIES}) + endif(ARGP_LIBRARIES OR HAVE_ARGP_H) endif() add_executable(exec exec.c ${examples_SRCS}) diff --git a/examples/exec.c b/examples/exec.c index 7200ddef..77d3be47 100644 --- a/examples/exec.c +++ b/examples/exec.c @@ -17,7 +17,7 @@ int main(void) { return 1; } - channel = ssh_channel_new(session);; + channel = ssh_channel_new(session); if (channel == NULL) { ssh_disconnect(session); ssh_free(session); diff --git a/examples/keygen.c b/examples/keygen.c index 2ab00113..99f8c98c 100644 --- a/examples/keygen.c +++ b/examples/keygen.c @@ -27,14 +27,14 @@ int main(void) rv = ssh_pki_generate(SSH_KEYTYPE_ED25519, 0, &key); if (rv != SSH_OK) { fprintf(stderr, "Failed to generate private key"); - return -1; + return -1; } - /* Write it to a file testkey in the current dirrectory */ + /* Write it to a file testkey in the current directory */ 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 -1; } return 0; diff --git a/examples/keygen2.c b/examples/keygen2.c index 8cab1077..466e075b 100644 --- a/examples/keygen2.c +++ b/examples/keygen2.c @@ -38,6 +38,8 @@ struct arguments_st { unsigned long bits; char *file; char *passphrase; + char *format; + int action_list; }; static struct argp_option options[] = { @@ -51,7 +53,6 @@ static struct argp_option options[] = { "Accepted values are: " "1024, 2048, 3072 (default), 4096, and 8192 for TYPE=\"rsa\"; " "256 (default), 384, and 521 for TYPE=\"ecdsa\"; " - "1024 (default) and 2048 for TYPE=\"dsa\"; " "can be omitted for TYPE=\"ed25519\" " "(it will be ignored if provided).\n", .group = 0 @@ -85,7 +86,25 @@ static struct argp_option options[] = { .flags = 0, .doc = "The type of the key to be generated. " "Accepted values are: " - "\"rsa\", \"ecdsa\", \"ed25519\", and \"dsa\".\n", + "\"rsa\", \"ecdsa\", and \"ed25519\".\n", + .group = 0 + }, + { + .name = "list", + .key = 'l', + .arg = NULL, + .flags = 0, + .doc = "List the Fingerprint of the given key\n", + .group = 0 + }, + { + .name = "format", + .key = 'm', + .arg = "FORMAT", + .flags = 0, + .doc = "Write the file in specific format. The supported values are " + "'PEM'and 'OpenSSH' file format. By default Ed25519 " + "keys are exported in OpenSSH format and others in PEM.\n", .group = 0 }, { @@ -144,9 +163,6 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) if (!strcmp(arg, "rsa")) { arguments->type = SSH_KEYTYPE_RSA; } - else if (!strcmp(arg, "dsa")) { - arguments->type = SSH_KEYTYPE_DSS; - } else if (!strcmp(arg, "ecdsa")) { arguments->type = SSH_KEYTYPE_ECDSA; } @@ -160,6 +176,12 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) goto end; } break; + case 'l': + arguments->action_list = 1; + break; + case 'm': + arguments->format = strdup(arg); + break; case ARGP_KEY_ARG: if (state->arg_num > 0) { /* Too many arguments. */ @@ -185,98 +207,80 @@ static int validate_args(struct arguments_st *args) return EINVAL; } - switch(args->type) { - case SSH_KEYTYPE_RSA: - switch(args->bits) { - case 0: - /* If not provided, use default value */ - args->bits = 3072; - break; - case 1024: - case 2048: - case 3072: - case 4096: - case 8192: - break; - default: - fprintf(stderr, "Error: Invalid bits parameter provided\n"); - rc = EINVAL; - break; - } - - if (args->file == NULL) { - args->file = strdup("id_rsa"); - if (args->file == NULL) { - rc = ENOMEM; - break; - } - } + /* no other arguments needed for listing key fingerprints */ + if (args->action_list) { + return 0; + } + switch (args->type) { + case SSH_KEYTYPE_RSA: + switch (args->bits) { + case 0: + /* If not provided, use default value */ + args->bits = 3072; break; - case SSH_KEYTYPE_ECDSA: - switch(args->bits) { - case 0: - /* If not provided, use default value */ - args->bits = 256; - break; - case 256: - case 384: - case 521: - break; - default: - fprintf(stderr, "Error: Invalid bits parameter provided\n"); - rc = EINVAL; - break; - } + case 1024: + case 2048: + case 3072: + case 4096: + case 8192: + break; + default: + fprintf(stderr, "Error: Invalid bits parameter provided\n"); + rc = EINVAL; + break; + } + + if (args->file == NULL) { + args->file = strdup("id_rsa"); if (args->file == NULL) { - args->file = strdup("id_ecdsa"); - if (args->file == NULL) { - rc = ENOMEM; - break; - } + rc = ENOMEM; + break; } + } + break; + case SSH_KEYTYPE_ECDSA: + switch (args->bits) { + case 0: + /* If not provided, use default value */ + args->bits = 256; break; - case SSH_KEYTYPE_DSS: - switch(args->bits) { - case 0: - /* If not provided, use default value */ - args->bits = 1024; - break; - case 1024: - case 2048: - break; - default: - fprintf(stderr, "Error: Invalid bits parameter provided\n"); - rc = EINVAL; - break; - } + case 256: + case 384: + case 521: + break; + default: + fprintf(stderr, "Error: Invalid bits parameter provided\n"); + rc = EINVAL; + break; + } + if (args->file == NULL) { + args->file = strdup("id_ecdsa"); if (args->file == NULL) { - args->file = strdup("id_dsa"); - if (args->file == NULL) { - rc = ENOMEM; - break; - } + rc = ENOMEM; + break; } + } - break; - case SSH_KEYTYPE_ED25519: - /* Ignore value and overwrite with a zero */ - args->bits = 0; + break; + case SSH_KEYTYPE_ED25519: + /* Ignore value and overwrite with a zero */ + args->bits = 0; + if (args->file == NULL) { + args->file = strdup("id_ed25519"); if (args->file == NULL) { - args->file = strdup("id_ed25519"); - if (args->file == NULL) { - rc = ENOMEM; - break; - } + rc = ENOMEM; + break; } + } - break; - default: - fprintf(stderr, "Error: unknown key type\n"); - rc = EINVAL; - break; + break; + default: + fprintf(stderr, "Error: unknown key type\n"); + rc = EINVAL; + break; } return rc; @@ -289,6 +293,31 @@ static char doc[] = "Generate an SSH key pair. " /* Our argp parser */ static struct argp argp = {options, parse_opt, NULL, doc, NULL, NULL, NULL}; +static void +list_fingerprint(char *file) +{ + ssh_key key = NULL; + unsigned char *hash = NULL; + size_t hlen = 0; + int rc; + + rc = ssh_pki_import_privkey_file(file, NULL, NULL, NULL, &key); + if (rc != SSH_OK) { + fprintf(stderr, "Failed to import private key %s\n", file); + return; + } + + rc = ssh_get_publickey_hash(key, SSH_PUBLICKEY_HASH_SHA256, &hash, &hlen); + if (rc != SSH_OK) { + fprintf(stderr, "Failed to get key fingerprint\n"); + return; + } + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); + + ssh_clean_pubkey_hash(&hash); + ssh_key_free(key); +} + int main(int argc, char *argv[]) { ssh_key key = NULL; @@ -302,6 +331,7 @@ int main(int argc, char *argv[]) .bits = 0, .file = NULL, .passphrase = NULL, + .action_list = 0, }; if (argc < 2) { @@ -319,6 +349,11 @@ int main(int argc, char *argv[]) goto end; } + if (arguments.action_list && arguments.file) { + list_fingerprint(arguments.file); + goto end; + } + errno = 0; rc = open(arguments.file, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR); if (rc < 0) { @@ -361,8 +396,36 @@ int main(int argc, char *argv[]) } /* Write the private key */ - rc = ssh_pki_export_privkey_file(key, arguments.passphrase, NULL, NULL, - arguments.file); + if (arguments.format != NULL) { + if (strcasecmp(arguments.format, "PEM") == 0) { + rc = ssh_pki_export_privkey_file_format(key, + arguments.passphrase, + NULL, + NULL, + arguments.file, + SSH_FILE_FORMAT_PEM); + } else if (strcasecmp(arguments.format, "OpenSSH") == 0) { + rc = ssh_pki_export_privkey_file_format(key, + arguments.passphrase, + NULL, + NULL, + arguments.file, + SSH_FILE_FORMAT_OPENSSH); + } else { + rc = ssh_pki_export_privkey_file_format(key, + arguments.passphrase, + NULL, + NULL, + arguments.file, + SSH_FILE_FORMAT_DEFAULT); + } + } else { + rc = ssh_pki_export_privkey_file(key, + arguments.passphrase, + NULL, + NULL, + arguments.file); + } if (rc != SSH_OK) { fprintf(stderr, "Error: Failed to write private key file"); goto end; diff --git a/examples/libssh_scp.c b/examples/libssh_scp.c index b81059b4..adb82ef0 100644 --- a/examples/libssh_scp.c +++ b/examples/libssh_scp.c @@ -229,7 +229,7 @@ static int open_location(struct location *loc, int flag) { return -1; } return 0; - } else { + } else if (loc->path != NULL) { loc->file = fopen(loc->path, flag == READ ? "r":"w"); if (!loc->file) { if (errno == EISDIR) { diff --git a/examples/proxy.c b/examples/proxy.c index 159a37e5..49c39e4c 100644 --- a/examples/proxy.c +++ b/examples/proxy.c @@ -143,19 +143,11 @@ static struct argp_option options[] = { .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.", + .doc = "Set the rsa host key (deprecated alias to 'k').", .group = 0 }, { @@ -180,15 +172,11 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) { 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 'r': + /* deprecated */ 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; @@ -237,7 +225,7 @@ int main(int argc, char **argv){ sshbind=ssh_bind_new(); session=ssh_new(); - ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, "sshd_rsa"); + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, "sshd_rsa"); #ifdef HAVE_ARGP_H /* diff --git a/examples/sample_sftpserver.c b/examples/sample_sftpserver.c new file mode 100644 index 00000000..b96f4ef7 --- /dev/null +++ b/examples/sample_sftpserver.c @@ -0,0 +1,515 @@ +/* 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 <libssh/callbacks.h> +#include <libssh/server.h> +#include <libssh/sftp.h> +#include <libssh/sftpserver.h> + +#include <poll.h> +#ifdef HAVE_ARGP_H +#include <argp.h> +#endif +#include <fcntl.h> +#ifdef HAVE_LIBUTIL_H +#include <libutil.h> +#endif +#ifdef HAVE_PTY_H +#include <pty.h> +#endif +#include <signal.h> +#include <stdlib.h> +#ifdef HAVE_UTMP_H +#include <utmp.h> +#endif +#ifdef HAVE_UTIL_H +#include <util.h> +#endif +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <stdio.h> +#include <stdbool.h> + +/* below are for sftp */ +#include <sys/statvfs.h> +#include <errno.h> +#include <unistd.h> +#include <dirent.h> +#include <time.h> +#include <inttypes.h> + +#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) + +static void set_default_keys(ssh_bind sshbind, + int rsa_already_set, + int ecdsa_already_set) +{ + if (!rsa_already_set) + { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, + KEYS_FOLDER "ssh_host_rsa_key"); + } + if (!ecdsa_already_set) + { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, + 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 sftp server example " SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = "<libssh@libssh.org>"; + +/* Program documentation. */ +static char doc[] = "Sftp server implemented with 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 = "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, 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 '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_HOSTKEY, arg); + rsa_already_set = 1; + break; + case 'e': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, 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, + 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 +{ + /* Event which is used to poll the above descriptors. */ + ssh_event event; + sftp_session sftp; +}; + +/* 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 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)session; + (void)user; + + 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 void handle_session(ssh_event event, ssh_session session) +{ + int n; + + /* Our struct holding information about the channel. */ + struct channel_data_struct cdata = { + .sftp = NULL, + }; + + /* 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.sftp), + .channel_data_function = sftp_channel_default_data_callback, + .channel_subsystem_request_function = sftp_channel_default_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) { + continue; + } + /* FIXME The server keeps hanging in the poll above when the client + * closes the channel */ + } while (ssh_channel_is_open(sdata.channel)); + + 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 = NULL; + ssh_session session = NULL; + ssh_event event = NULL; + 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"); + goto exit; + } + + sshbind = ssh_bind_new(); + if (sshbind == NULL) + { + fprintf(stderr, "ssh_bind_new failed\n"); + goto exit; + } + +#ifdef HAVE_ARGP_H + argp_parse(&argp, argc, argv, 0, 0, sshbind); +#else + (void)argc; + (void)argv; + + set_default_keys(sshbind, 0, 0); +#endif /* HAVE_ARGP_H */ + + if (ssh_bind_listen(sshbind) < 0) + { + fprintf(stderr, "%s\n", ssh_get_error(sshbind)); + goto exit; + } + + 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); + } + +exit: + ssh_bind_free(sshbind); + ssh_finalize(); + return 0; +} diff --git a/examples/samplesftp.c b/examples/samplesftp.c index 9c8cd34a..d82a556a 100644 --- a/examples/samplesftp.c +++ b/examples/samplesftp.c @@ -33,21 +33,18 @@ clients must be made or how a client should react. #define BUF_SIZE 65536 #endif -static int verbosity; -static char *destination; - 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 source; sftp_file to; int len = 1; unsigned int i; char data[BUF_SIZE] = {0}; - char *lnk; + char *lnk = NULL; unsigned int count; @@ -86,6 +83,7 @@ static void do_sftp(ssh_session session) { goto end; } printf("readlink /tmp/sftp_symlink_test: %s\n", lnk); + ssh_string_free_char(lnk); sftp_unlink(sftp, "/tmp/sftp_symlink_test"); @@ -173,7 +171,7 @@ static void do_sftp(ssh_session session) { sftp_attributes_free(file); } - /* when file = NULL, an error has occured OR the directory listing is end of + /* when file = NULL, an error has occurred OR the directory listing is end of * file */ if (!sftp_dir_eof(dir)) { fprintf(stderr, "Error: %s\n", ssh_get_error(session)); @@ -188,8 +186,8 @@ static void do_sftp(ssh_session session) { /* 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) { + source = sftp_open(sftp, "/usr/bin/ssh", O_RDONLY, 0); + if (!source) { fprintf(stderr, "Error opening /usr/bin/ssh: %s\n", ssh_get_error(session)); goto end; @@ -200,16 +198,16 @@ static void do_sftp(ssh_session session) { if (!to) { fprintf(stderr, "Error opening ssh-copy for writing: %s\n", ssh_get_error(session)); - sftp_close(fichier); + sftp_close(source); goto end; } - while ((len = sftp_read(fichier, data, 4096)) > 0) { + while ((len = sftp_read(source, 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); + sftp_close(source); goto end; } } @@ -219,10 +217,10 @@ static void do_sftp(ssh_session session) { fprintf(stderr, "Error reading file: %s\n", ssh_get_error(session)); } - sftp_close(fichier); + sftp_close(source); sftp_close(to); - printf("fichiers ferm\n"); - to = sftp_open(sftp, "/tmp/grosfichier", O_WRONLY|O_CREAT, 0644); + printf("file closed\n"); + to = sftp_open(sftp, "/tmp/large_file", O_WRONLY|O_CREAT, 0644); for (i = 0; i < 1000; ++i) { len = sftp_write(to, data, sizeof(data)); @@ -243,50 +241,63 @@ static void usage(const char *argv0) { fprintf(stderr, "Usage : %s [-v] remotehost\n" "sample sftp test client - libssh-%s\n" "Options :\n" + " -l user : log in as user\n" + " -p port : connect to port\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; - } - } +int main(int argc, char **argv) +{ + ssh_session session = NULL; + char *destination = NULL; + int auth = 0; + int state; - destination = argv[optind]; - if (destination == NULL) { + ssh_init(); + session = ssh_new(); + + if (ssh_options_getopt(session, &argc, argv)) { + fprintf(stderr, + "Error parsing command line: %s\n", + ssh_get_error(session)); + ssh_free(session); + ssh_finalize(); usage(argv[0]); - return -1; + return EXIT_FAILURE; } - return 0; -} + if (argc < 1) { + usage(argv[0]); + return EXIT_FAILURE; + } + destination = argv[1]; -int main(int argc, char **argv) { - ssh_session session; + if (ssh_options_set(session, SSH_OPTIONS_HOST, destination) < 0) { + return -1; + } + if (ssh_connect(session)) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + return -1; + } - if (opts(argc, argv) < 0) { - return EXIT_FAILURE; + state = verify_knownhost(session); + if (state != 0) { + return -1; } - session = connect_ssh(destination, NULL, verbosity); - if (session == NULL) { - return EXIT_FAILURE; + auth = authenticate_console(session); + if (auth != SSH_AUTH_SUCCESS) { + return -1; } do_sftp(session); ssh_disconnect(session); ssh_free(session); + + ssh_finalize(); + return 0; } diff --git a/examples/samplesshd-cb.c b/examples/samplesshd-cb.c index 628013aa..6b272102 100644 --- a/examples/samplesshd-cb.c +++ b/examples/samplesshd-cb.c @@ -49,6 +49,27 @@ static int tries = 0; static int error = 0; static ssh_channel chan=NULL; +static int auth_none(ssh_session session, + const char *user, + void *userdata) +{ + ssh_string banner = NULL; + + (void)user; /* unused */ + (void)userdata; /* unused */ + + ssh_set_auth_methods(session, + SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_GSSAPI_MIC); + + banner = ssh_string_from_char("Banner Example\n"); + if (banner != NULL) { + ssh_send_issue_banner(session, banner); + } + ssh_string_free(banner); + + return SSH_AUTH_DENIED; +} + static int auth_password(ssh_session session, const char *user, const char *password, void *userdata){ (void)userdata; @@ -152,19 +173,11 @@ static struct argp_option options[] = { .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.", + .doc = "Set the rsa key (deprecated alias for 'k').", .group = 0 }, { @@ -197,15 +210,10 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) { 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 'r': 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; @@ -242,6 +250,7 @@ int main(int argc, char **argv){ ssh_event mainloop; struct ssh_server_callbacks_struct cb = { .userdata = NULL, + .auth_none_function = auth_none, .auth_password_function = auth_password, #ifdef WITH_GSSAPI .auth_gssapi_mic_function = auth_gssapi_mic, @@ -256,8 +265,7 @@ int main(int argc, char **argv){ 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"); + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, KEYS_FOLDER "ssh_host_rsa_key"); #ifdef HAVE_ARGP_H /* diff --git a/examples/samplesshd-kbdint.c b/examples/samplesshd-kbdint.c index 9b09cf27..d382abac 100644 --- a/examples/samplesshd-kbdint.c +++ b/examples/samplesshd-kbdint.c @@ -113,19 +113,11 @@ static struct argp_option options[] = { .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.", + .doc = "Set the rsa key (deprecated alias for 'k').", .group = 0 }, { @@ -151,15 +143,10 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) { 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 'r': 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; @@ -306,10 +293,8 @@ int main(int argc, char **argv){ 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"); + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, + KEYS_FOLDER "ssh_host_rsa_key"); #ifdef HAVE_ARGP_H /* @@ -369,9 +354,9 @@ int main(int argc, char **argv){ } } while(!chan); - if(!chan) { - printf("Error: cleint did not ask for a channel session (%s)\n", - ssh_get_error(session)); + if (!chan) { + printf("Error: client did not ask for a channel session (%s)\n", + ssh_get_error(session)); ssh_finalize(); return 1; } diff --git a/examples/senddata.c b/examples/senddata.c index 042b462b..21181fb9 100644 --- a/examples/senddata.c +++ b/examples/senddata.c @@ -17,7 +17,7 @@ int main(void) { return 1; } - channel = ssh_channel_new(session);; + channel = ssh_channel_new(session); if (channel == NULL) { ssh_disconnect(session); return 1; diff --git a/examples/ssh_X11_client.c b/examples/ssh_X11_client.c new file mode 100644 index 00000000..e3386813 --- /dev/null +++ b/examples/ssh_X11_client.c @@ -0,0 +1,950 @@ +/* + * ssh.c - Simple example of SSH X11 client using libssh + * + * Copyright (C) 2022 Marco Fortina + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + * + * + * ssh_X11_client + * ============== + * + * AUTHOR URL + * https://gitlab.com/marco.fortina/libssh-x11-client/ + * + * This is a simple example of SSH X11 client using libssh. + * + * Features: + * + * - support local display (e.g. :0) + * - support remote display (e.g. localhost:10.0) + * - using callbacks and event polling to significantly reduce CPU utilization + * - use X11 forwarding with authentication spoofing (like openssh) + * + * Note: + * + * - part of this code was inspired by openssh's one. + * + * Dependencies: + * + * - gcc >= 7.5.0 + * - libssh >= 0.8.0 + * - libssh-dev >= 0.8.0 + * + * To Build: + * gcc -o ssh_X11_client ssh_X11_client.c -lssh -g + * + * Donations: + * + * If you liked this work and wish to support the developer please donate to: + * Bitcoin: 1N2rQimKbeUQA8N2LU5vGopYQJmZsBM2d6 + * + */ + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <pthread.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <termios.h> +#include <time.h> + +#include <libssh/libssh.h> +#include <libssh/callbacks.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> + +#include <sys/un.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/socket.h> + +/* + * Data Structures and Macros + */ + +#define _PATH_UNIX_X "/tmp/.X11-unix/X%d" +#define _XAUTH_CMD "/usr/bin/xauth list %s 2>/dev/null" + +typedef struct item { + ssh_channel channel; + int fd_in; + int fd_out; + int protected; + struct item *next; +} node_t; + +node_t *node = NULL; + + +/* + * Mutex + */ + +pthread_mutex_t mutex; + + +/* + * Function declarations + */ + +/* Linked nodes to manage channel/fd tuples */ +static int insert_item(ssh_channel channel, int fd_in, int fd_out, + int protected); +static void delete_item(ssh_channel channel); +static node_t * search_item(ssh_channel channel); + +/* X11 Display */ +const char * ssh_gai_strerror(int gaierr); +static int x11_get_proto(const char *display, char **_proto, char **_data); +static void set_nodelay(int fd); +static int connect_local_xsocket_path(const char *pathname); +static int connect_local_xsocket(int display_number); +static int x11_connect_display(void); + +/* Send data to channel */ +static int copy_fd_to_channel_callback(int fd, int revents, void *userdata); + +/* Read data from channel */ +static int copy_channel_to_fd_callback(ssh_session session, ssh_channel channel, + void *data, uint32_t len, int is_stderr, + void *userdata); + +/* EOF&Close channel */ +static void channel_close_callback(ssh_session session, ssh_channel channel, + void *userdata); + +/* X11 Request */ +static ssh_channel x11_open_request_callback(ssh_session session, + const char *shost, int sport, + void *userdata); + +/* Main loop */ +static int main_loop(ssh_channel channel); + +/* Internals */ +int64_t _current_timestamp(void); + +/* Global variables */ +const char *hostname = NULL; +int enableX11 = 1; + +/* + * Callbacks Data Structures + */ + +/* SSH Channel Callbacks */ +struct ssh_channel_callbacks_struct channel_cb = +{ + .channel_data_function = copy_channel_to_fd_callback, + .channel_eof_function = channel_close_callback, + .channel_close_function = channel_close_callback, + .userdata = NULL +}; + +/* SSH Callbacks */ +struct ssh_callbacks_struct cb = +{ + .channel_open_request_x11_function = x11_open_request_callback, + .userdata = NULL +}; + + +/* + * SSH Event Context + */ + +short events = POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL; +ssh_event event; + + +/* + * Internal data structures + */ + +struct termios _saved_tio; + + +/* + * Internal functions + */ + +int64_t _current_timestamp(void) +{ + struct timeval tv; + int64_t milliseconds; + + gettimeofday(&tv, NULL); + milliseconds = (int64_t)(tv.tv_sec) * 1000 + (tv.tv_usec / 1000); + + return milliseconds; +} + +static void _logging_callback(int priority, const char *function, + const char *buffer, void *userdata) +{ + FILE *fp = NULL; + char buf[100]; + int64_t milliseconds; + + time_t now = time(0); + + (void)userdata; + + strftime(buf, 100, "%Y-%m-%d %H:%M:%S", localtime(&now)); + + fp = fopen("debug.log","a"); + if (fp == NULL) { + printf("Error!"); + exit(-11); + } + + milliseconds = _current_timestamp(); + + fprintf(fp, "[%s.%jd, %d] %s: %s\n", buf, milliseconds, priority, + function, buffer); + fclose(fp); +} + +static int _enter_term_raw_mode(void) +{ + struct termios tio; + int ret = tcgetattr(fileno(stdin), &tio); + if (ret != -1) { + _saved_tio = tio; + tio.c_iflag |= IGNPAR; + tio.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF); +#ifdef IUCLC + tio.c_iflag &= ~IUCLC; +#endif + tio.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); +#ifdef IEXTEN + tio.c_lflag &= ~IEXTEN; +#endif + tio.c_oflag &= ~OPOST; + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + ret = tcsetattr(fileno(stdin), TCSADRAIN, &tio); + } + + return ret; +} + +static int _leave_term_raw_mode(void) +{ + int ret = tcsetattr(fileno(stdin), TCSADRAIN, &_saved_tio); + return ret; +} + + +/* + * Functions + */ + +static int insert_item(ssh_channel channel, int fd_in, int fd_out, + int protected) +{ + node_t *node_iterator = NULL, *new = NULL; + + pthread_mutex_lock(&mutex); + + if (node == NULL) { + /* Calloc ensure that node is full of 0 */ + node = (node_t *) calloc(1, sizeof(node_t)); + if (node == NULL) { + pthread_mutex_unlock(&mutex); + return -1; + } + node->channel = channel; + node->fd_in = fd_in; + node->fd_out = fd_out; + node->protected = protected; + node->next = NULL; + } else { + node_iterator = node; + while (node_iterator->next != NULL) { + node_iterator = node_iterator->next; + } + /* Create the new node */ + new = (node_t *) malloc(sizeof(node_t)); + if (new == NULL) { + pthread_mutex_unlock(&mutex); + return -1; + } + new->channel = channel; + new->fd_in = fd_in; + new->fd_out = fd_out; + new->protected = protected; + new->next = NULL; + node_iterator->next = new; + + } + + pthread_mutex_unlock(&mutex); + return 0; +} + + +static void delete_item(ssh_channel channel) +{ + node_t *current = NULL, *previous = NULL; + + pthread_mutex_lock(&mutex); + + for (current = node; current; previous = current, current = current->next) { + if (current->channel != channel) { + continue; + } + + if (previous == NULL) { + node = current->next; + } else { + previous->next = current->next; + } + + free(current); + pthread_mutex_unlock(&mutex); + return; + } + + pthread_mutex_unlock(&mutex); +} + + +static node_t *search_item(ssh_channel channel) +{ + node_t *current = NULL; + + pthread_mutex_lock(&mutex); + + current = node; + while (current != NULL) { + if (current->channel == channel) { + pthread_mutex_unlock(&mutex); + return current; + } else { + current = current->next; + } + } + + pthread_mutex_unlock(&mutex); + + return NULL; +} + + + +static void set_nodelay(int fd) +{ + int opt, rc; + socklen_t optlen; + + optlen = sizeof(opt); + + rc = getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, &optlen); + if (rc == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "getsockopt TCP_NODELAY: %.100s", + strerror(errno)); + return; + } + if (opt == 1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fd %d is TCP_NODELAY", fd); + return; + } + opt = 1; + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fd %d setting TCP_NODELAY", fd); + + rc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); + if (rc == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "setsockopt TCP_NODELAY: %.100s", + strerror(errno)); + } +} + + +const char *ssh_gai_strerror(int gaierr) +{ + if (gaierr == EAI_SYSTEM && errno != 0) { + return strerror(errno); + } + return gai_strerror(gaierr); +} + + + +static int x11_get_proto(const char *display, char **_proto, char **_cookie) +{ + char cmd[1024], line[512], xdisplay[512]; + static char proto[512], cookie[512]; + FILE *f = NULL; + int ret = 0; + + *_proto = proto; + *_cookie = cookie; + + proto[0] = cookie[0] = '\0'; + + if (strncmp(display, "localhost:", 10) == 0) { + ret = snprintf(xdisplay, sizeof(xdisplay), "unix:%s", display + 10); + if (ret < 0 || (size_t)ret >= sizeof(xdisplay)) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "display name too long. display: %s", display); + return -1; + } + display = xdisplay; + } + + snprintf(cmd, sizeof(cmd), _XAUTH_CMD, display); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "xauth cmd: %s", cmd); + + f = popen(cmd, "r"); + if (f && fgets(line, sizeof(line), f) && + sscanf(line, "%*s %511s %511s", proto, cookie) == 2) { + ret = 0; + } else { + ret = 1; + } + + if (f) { + pclose(f); + } + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "proto: %s - cookie: %s - ret: %d", + proto, cookie, ret); + + return ret; +} + +static int connect_local_xsocket_path(const char *pathname) +{ + int sock, rc; + struct sockaddr_un addr; + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "socket: %.100s", + strerror(errno)); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + addr.sun_path[0] = '\0'; + /* pathname is guaranteed to be initialized and larger than addr.sun_path[108] */ + memcpy(addr.sun_path + 1, pathname, sizeof(addr.sun_path) - 1); + rc = connect(sock, (struct sockaddr *)&addr, + offsetof(struct sockaddr_un, sun_path) + 1 + strlen(pathname)); + if (rc == 0) { + return sock; + } + close(sock); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "connect %.100s: %.100s", + addr.sun_path, strerror(errno)); + + return -1; +} + + +static int connect_local_xsocket(int display_number) +{ + char buf[1024] = {0}; + snprintf(buf, sizeof(buf), _PATH_UNIX_X, display_number); + return connect_local_xsocket_path(buf); +} + + +static int x11_connect_display(void) +{ + int display_number; + const char *display = NULL; + char buf[1024], *cp = NULL; + struct addrinfo hints, *ai = NULL, *aitop = NULL; + char strport[NI_MAXSERV]; + int gaierr = 0, sock = 0; + + /* Try to open a socket for the local X server. */ + display = getenv("DISPLAY"); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "display: %s", display); + + if (display == 0) { + return -1; + } + + /* Check if it is a unix domain socket. */ + if (strncmp(display, "unix:", 5) == 0 || display[0] == ':') { + /* Connect to the unix domain socket. */ + if (sscanf(strrchr(display, ':') + 1, "%d", &display_number) != 1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "Could not parse display number from DISPLAY: %.100s", + display); + return -1; + } + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "display_number: %d", + display_number); + + /* Create a socket. */ + sock = connect_local_xsocket(display_number); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "socket: %d", sock); + + if (sock < 0) { + return -1; + } + + /* OK, we now have a connection to the display. */ + return sock; + } + + /* Connect to an inet socket. */ + strncpy(buf, display, sizeof(buf) - 1); + cp = strchr(buf, ':'); + if (cp == 0) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "Could not find ':' in DISPLAY: %.100s", display); + return -1; + } + *cp = 0; + if (sscanf(cp + 1, "%d", &display_number) != 1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "Could not parse display number from DISPLAY: %.100s", + display); + return -1; + } + + /* Look up the host address */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + snprintf(strport, sizeof(strport), "%u", 6000 + display_number); + gaierr = getaddrinfo(buf, strport, &hints, &aitop); + if (gaierr != 0) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "%.100s: unknown host. (%s)", + buf, ssh_gai_strerror(gaierr)); + return -1; + } + for (ai = aitop; ai; ai = ai->ai_next) { + /* Create a socket. */ + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "socket: %.100s", + strerror(errno)); + continue; + } + /* Connect it to the display. */ + if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "connect %.100s port %u: %.100s", buf, + 6000 + display_number, strerror(errno)); + close(sock); + continue; + } + /* Success */ + break; + } + freeaddrinfo(aitop); + if (ai == 0) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "connect %.100s port %u: %.100s", + buf, 6000 + display_number, strerror(errno)); + return -1; + } + set_nodelay(sock); + + return sock; +} + + + +static int copy_fd_to_channel_callback(int fd, int revents, void *userdata) +{ + ssh_channel channel = (ssh_channel)userdata; + char buf[2097152]; + int sz = 0, ret = 0; + + node_t *temp_node = search_item(channel); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "event: %d - fd: %d", revents, fd); + + if (channel == NULL) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "channel does not exist."); + if (temp_node->protected == 0) { + close(fd); + } + return -1; + } + + if (fcntl(fd, F_GETFD) == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fcntl error. fd: %d", fd); + ssh_channel_close(channel); + return -1; + } + + if ((revents & POLLIN) || (revents & POLLPRI)) { + sz = read(fd, buf, sizeof(buf)); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "sz: %d", sz); + if (sz > 0) { + ret = ssh_channel_write(channel, buf, sz); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "channel_write ret: %d", ret); + } else if (sz < 0) { + ssh_channel_close(channel); + return -1; + } else { + /* sz = 0. Why the hell I'm here? */ + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "Why the hell am I here?: sz: %d", sz); + if (temp_node->protected == 0) { + close(fd); + } + return -1; + } + } + + if ((revents & POLLHUP) || (revents & POLLNVAL) || (revents & POLLERR)) { + ssh_channel_close(channel); + return -1; + } + + return sz; +} + + +static int copy_channel_to_fd_callback(ssh_session session, ssh_channel channel, + void *data, uint32_t len, int is_stderr, + void *userdata) +{ + node_t *temp_node = NULL; + int fd, sz; + + (void)session; + (void)is_stderr; + (void)userdata; + + temp_node = search_item(channel); + + fd = temp_node->fd_out; + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "len: %d - fd: %d - is_stderr: %d", + len, fd, is_stderr); + + sz = write(fd, data, len); + + return sz; +} + + +static void channel_close_callback(ssh_session session, ssh_channel channel, + void *userdata) +{ + node_t *temp_node = NULL; + + (void)session; + (void)userdata; + + temp_node = search_item(channel); + + if (temp_node != NULL) { + int fd = temp_node->fd_in; + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fd: %d", fd); + + delete_item(channel); + ssh_event_remove_fd(event, fd); + + if (temp_node->protected == 0) { + close(fd); + } + } +} + + +static ssh_channel x11_open_request_callback(ssh_session session, + const char *shost, int sport, + void *userdata) +{ + ssh_channel channel = NULL; + int sock, rv; + + (void)shost; + (void)sport; + (void)userdata; + + channel = ssh_channel_new(session); + + sock = x11_connect_display(); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "sock: %d", sock); + + rv = insert_item(channel, sock, sock, 0); + if (rv != 0) { + ssh_channel_free(channel); + return NULL; + } + + ssh_event_add_fd(event, sock, events, copy_fd_to_channel_callback, channel); + ssh_event_add_session(event, session); + + ssh_add_channel_callbacks(channel, &channel_cb); + + return channel; +} + + + +/* + * MAIN LOOP + */ + +static int main_loop(ssh_channel channel) +{ + ssh_session session = ssh_channel_get_session(channel); + int rv; + + rv = insert_item(channel, fileno(stdin), fileno(stdout), 1); + if (rv != 0) { + return -1; + } + + ssh_callbacks_init(&channel_cb); + ssh_set_channel_callbacks(channel, &channel_cb); + + event = ssh_event_new(); + if (event == NULL) { + printf("Couldn't get a event\n"); + return -1; + } + + rv = ssh_event_add_fd(event, fileno(stdin), events, + copy_fd_to_channel_callback, channel); + if (rv != SSH_OK) { + printf("Couldn't add an fd to the event\n"); + return -1; + } + + rv = ssh_event_add_session(event, session); + if (rv != SSH_OK) { + printf("Couldn't add the session to the event\n"); + return -1; + } + + do { + if (ssh_event_dopoll(event, 1000) == SSH_ERROR) { + printf("Error : %s\n", ssh_get_error(session)); + /* fall through */ + } + } while (!ssh_channel_is_closed(channel)); + + delete_item(channel); + ssh_event_remove_fd(event, fileno(stdin)); + ssh_event_remove_session(event, session); + ssh_event_free(event); + + return 0; +} + + +/* + * USAGE + */ + +static void usage(void) +{ + fprintf(stderr, + "Usage : ssh-X11-client [options] [login@]hostname\n" + "sample X11 client - libssh-%s\n" + "Options :\n" + " -l user : Specifies the user to log in as on the remote " + "machine.\n" + " -p port : Port to connect to on the remote host.\n" + " -v : Verbose mode. Multiple -v options increase the " + "verbosity. The maximum is 5.\n" + " -C : Requests compression of all data.\n" + " -x : Disables X11 forwarding.\n" + "\n", + ssh_version(0)); + + exit(0); +} + +static int opts(int argc, char **argv) +{ + int i; + + while ((i = getopt(argc,argv,"x")) != -1) { + switch (i) { + case 'x': + enableX11 = 0; + break; + default: + fprintf(stderr, "Unknown option %c\n", optopt); + return -1; + } + } + + if (optind < argc) { + hostname = argv[optind++]; + } + + if (hostname == NULL) { + return -1; + } + + return 0; +} + +/* + * MAIN + */ + +int main(int argc, char **argv) +{ + char *password = NULL; + + ssh_session session = NULL; + ssh_channel channel = NULL; + + int ret; + + const char *display = NULL; + char *proto = NULL, *cookie = NULL; + + ssh_set_log_callback(_logging_callback); + ret = ssh_init(); + if (ret != SSH_OK) { + return ret; + } + + session = ssh_new(); + if (session == NULL) { + exit(-1); + } + + if (ssh_options_getopt(session, &argc, argv) || opts(argc, argv)) { + fprintf(stderr, "Error parsing command line: %s\n", + ssh_get_error(session)); + ssh_free(session); + ssh_finalize(); + usage(); + } + + if (ssh_options_set(session, SSH_OPTIONS_HOST, hostname) < 0) { + return -1; + } + + ret = ssh_connect(session); + if (ret != SSH_OK) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + exit(-1); + } + + password = getpass("Password: "); + ret = ssh_userauth_password(session, NULL, password); + if (ret != SSH_AUTH_SUCCESS) { + fprintf(stderr, "Error authenticating with password: %s\n", + ssh_get_error(session)); + exit(-1); + } + + channel = ssh_channel_new(session); + if (channel == NULL) { + return SSH_ERROR; + } + + ret = ssh_channel_open_session(channel); + if (ret != SSH_OK) { + return ret; + } + + ret = ssh_channel_request_pty(channel); + if (ret != SSH_OK) { + return ret; + } + + ret = ssh_channel_change_pty_size(channel, 80, 24); + if (ret != SSH_OK) { + return ret; + } + + if (enableX11 == 1) { + display = getenv("DISPLAY"); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "display: %s", display); + + if (display) { + ssh_callbacks_init(&cb); + ret = ssh_set_callbacks(session, &cb); + if (ret != SSH_OK) { + return ret; + } + + ret = x11_get_proto(display, &proto, &cookie); + if (ret != 0) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "Using fake authentication data for X11 forwarding"); + proto = NULL; + cookie = NULL; + } + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "proto: %s - cookie: %s", + proto, cookie); + /* See https://gitlab.com/libssh/libssh-mirror/-/blob/master/src/channels.c#L2062 for details. */ + ret = ssh_channel_request_x11(channel, 0, proto, cookie, 0); + if (ret != SSH_OK) { + return ret; + } + } + } + + ret = _enter_term_raw_mode(); + if (ret != 0) { + exit(-1); + } + + ret = ssh_channel_request_shell(channel); + if (ret != SSH_OK) { + return ret; + } + + ret = main_loop(channel); + if (ret != SSH_OK) { + return ret; + } + + _leave_term_raw_mode(); + + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); +} diff --git a/examples/ssh_client.c b/examples/ssh_client.c index aaf0cb5b..cc249183 100644 --- a/examples/ssh_client.c +++ b/examples/ssh_client.c @@ -94,7 +94,6 @@ static void usage(void) "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" " -F file : parse configuration file instead of default one\n" #ifdef WITH_PCAP @@ -298,25 +297,41 @@ static void shell(ssh_session session) static void batch_shell(ssh_session session) { ssh_channel channel; - char buffer[PATH_MAX]; - size_t i; - int s = 0; + char *buffer = NULL; + size_t i, s, n; + channel = ssh_channel_new(session); + if (channel == NULL) { + return; + } + + n = 0; for (i = 0; i < MAXCMD && cmds[i]; ++i) { - s += snprintf(buffer + s, sizeof(buffer) - s, "%s ", cmds[i]); + /* Including space after cmds[i] */ + n += strlen(cmds[i]) + 1; } + /* Trailing \0 */ + n += 1; - channel = ssh_channel_new(session); - if (channel == NULL) { + buffer = malloc(n); + if (buffer == NULL) { + ssh_channel_free(channel); return; } + s = 0; + for (i = 0; i < MAXCMD && cmds[i]; ++i) { + s += snprintf(buffer + s, n - s, "%s ", cmds[i]); + } + ssh_channel_open_session(channel); if (ssh_channel_request_exec(channel, buffer)) { printf("Error executing '%s' : %s\n", buffer, ssh_get_error(session)); + free(buffer); ssh_channel_free(channel); return; } + free(buffer); select_loop(session, channel); ssh_channel_free(channel); } diff --git a/examples/ssh_server.c b/examples/ssh_server.c index 85eb1689..3e9f344b 100644 --- a/examples/ssh_server.c +++ b/examples/ssh_server.c @@ -45,36 +45,10 @@ The goal is to show the API in action. #define BUF_SIZE 1048576 #endif -#ifndef KEYS_FOLDER -#ifdef _WIN32 -#define KEYS_FOLDER -#else -#define KEYS_FOLDER "/etc/ssh/" -#endif -#endif - #define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) #define SFTP_SERVER_PATH "/usr/lib/sftp-server" +#define AUTH_KEYS_MAX_LINE_SIZE 2048 -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}; char username[128] = "myuser"; @@ -110,19 +84,11 @@ static struct argp_option options[] = { .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.", + .doc = "Set the rsa key (deprecated alias for 'k').", .group = 0 }, { @@ -130,7 +96,7 @@ static struct argp_option options[] = { .key = 'e', .arg = "FILE", .flags = 0, - .doc = "Set the ecdsa key.", + .doc = "Set the ecdsa key (deprecated alias for 'k').", .group = 0 }, { @@ -158,14 +124,6 @@ static struct argp_option options[] = { .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, @@ -181,34 +139,19 @@ 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; + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); break; case 'e': - ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_ECDSAKEY, arg); - ecdsa_already_set = 1; + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); break; case 'a': strncpy(authorizedkeys, arg, DEF_STR_SIZE-1); @@ -235,14 +178,6 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) { /* 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; @@ -256,18 +191,12 @@ static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; static int parse_opt(int argc, char **argv, ssh_bind sshbind) { int no_default_keys = 0; int rsa_already_set = 0; - int dsa_already_set = 0; int ecdsa_already_set = 0; int key; - while((key = getopt(argc, argv, "a:d:e:k:np:P:r:u:v")) != -1) { - if (key == 'n') { - no_default_keys = 1; - } else if (key == 'p') { + while((key = getopt(argc, argv, "a:e:k:p:P:r:u:v")) != -1) { + if (key == 'p') { ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, optarg); - } else if (key == 'd') { - ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, optarg); - dsa_already_set = 1; } else if (key == 'k') { ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, optarg); /* We can't track the types of keys being added with this @@ -275,10 +204,10 @@ static int parse_opt(int argc, char **argv, ssh_bind sshbind) { by just not setting the default keys */ no_default_keys = 1; } else if (key == 'r') { - ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, optarg); + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, optarg); rsa_already_set = 1; } else if (key == 'e') { - ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_ECDSAKEY, optarg); + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, optarg); ecdsa_already_set = 1; } else if (key == 'a') { strncpy(authorizedkeys, optarg, DEF_STR_SIZE-1); @@ -299,14 +228,12 @@ static int parse_opt(int argc, char **argv, ssh_bind sshbind) { "libssh %s -- a Secure Shell protocol implementation\n" "\n" " -a, --authorizedkeys=FILE Set the authorized keys file.\n" - " -d, --dsakey=FILE Set the dsa key.\n" - " -e, --ecdsakey=FILE Set the ecdsa key.\n" + " -e, --ecdsakey=FILE Set the ecdsa key (deprecated alias for 'k').\n" " -k, --hostkey=FILE Set a host key. Can be used multiple times.\n" " Implies no default keys.\n" - " -n, --no-default-keys Do not set default key locations.\n" " -p, --port=PORT Set the port to bind.\n" " -P, --pass=PASSWORD Set expected password.\n" - " -r, --rsakey=FILE Set the rsa key.\n" + " -r, --rsakey=FILE Set the rsa key (deprecated alias for 'k').\n" " -u, --user=USERNAME Set expected username.\n" " -v, --verbose Get verbose output.\n" " -?, --help Give this help list\n" @@ -329,7 +256,6 @@ static int parse_opt(int argc, char **argv, ssh_bind sshbind) { if (!no_default_keys) { set_default_keys(sshbind, rsa_already_set, - dsa_already_set, ecdsa_already_set); } @@ -536,7 +462,7 @@ static int shell_request(ssh_session session, ssh_channel channel, static int subsystem_request(ssh_session session, ssh_channel channel, const char *subsystem, void *userdata) { - /* subsystem requests behave simillarly to exec requests. */ + /* subsystem requests behave similarly to exec requests. */ if (strcmp(subsystem, "sftp") == 0) { return exec_request(session, channel, SFTP_SERVER_PATH, userdata); } @@ -565,6 +491,15 @@ static int auth_publickey(ssh_session session, void *userdata) { struct session_data_struct *sdata = (struct session_data_struct *) userdata; + ssh_key key = NULL; + FILE *fp = NULL; + char line[AUTH_KEYS_MAX_LINE_SIZE] = {0}; + char *p = NULL; + const char *q = NULL; + unsigned int lineno = 0; + int result; + int i; + enum ssh_keytypes_e type; (void) user; (void) session; @@ -577,31 +512,72 @@ static int auth_publickey(ssh_session session, 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; - } + fp = fopen(authorizedkeys, "r"); + if (fp == NULL) { + fprintf(stderr, "Error: opening authorized keys file %s failed, reason: %s\n", + authorizedkeys, strerror(errno)); + return SSH_AUTH_DENIED; + } + + while (fgets(line, sizeof(line), fp)) { + lineno++; + + /* Skip leading whitespace and ignore comments */ + p = line; + + for (i = 0; i < AUTH_KEYS_MAX_LINE_SIZE; i++) { + if (!isspace((int)p[i])) { + break; } } + + if (p[i] == '#' || p[i] == '\0' || p[i] == '\n') { + continue; + } + + q = &p[i]; + for (; i < AUTH_KEYS_MAX_LINE_SIZE; i++) { + if (isspace((int)p[i])) { + p[i] = '\0'; + break; + } + } + + type = ssh_key_type_from_name(q); + + q = &p[i + 1]; + for (; i < AUTH_KEYS_MAX_LINE_SIZE; i++) { + if (isspace((int)p[i])) { + p[i] = '\0'; + break; + } + } + + result = ssh_pki_import_pubkey_base64(q, type, &key); + if (result != SSH_OK) { + fprintf(stderr, + "Warning: Cannot import key on line no. %d in authorized keys file: %s\n", + lineno, + authorizedkeys); + continue; + } + + result = ssh_key_cmp(key, pubkey, SSH_KEY_CMP_PUBLIC); + ssh_key_free(key); + if (result == 0) { + sdata->authenticated = 1; + fclose(fp); + return SSH_AUTH_SUCCESS; + } + } + if (ferror(fp) != 0) { + fprintf(stderr, + "Error: Reading from authorized keys file %s failed, reason: %s\n", + authorizedkeys, strerror(errno)); } + fclose(fp); - // no matches - sdata->authenticated = 0; + /* no matches */ return SSH_AUTH_DENIED; } diff --git a/examples/sshd_direct-tcpip.c b/examples/sshd_direct-tcpip.c index 589c0889..84389b28 100644 --- a/examples/sshd_direct-tcpip.c +++ b/examples/sshd_direct-tcpip.c @@ -15,7 +15,7 @@ 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 + ./sshd_direct-tcpip -v -p 2022 -r serverkey.rsa 127.0.0.1 */ #include "config.h" @@ -27,6 +27,9 @@ clients must be made or how a client should react. #ifdef HAVE_ARGP_H #include <argp.h> #endif +#ifndef _WIN32 +#include <netinet/in.h> +#endif #include <sys/types.h> #include <sys/socket.h> #include <stdbool.h> @@ -91,8 +94,15 @@ cleanup_push(struct cleanup_node_struct** head_ref, { // Allocate memory for node struct cleanup_node_struct *new_node = malloc(sizeof *new_node); + if (new_node == NULL) { + return; + } - new_node->next = (*head_ref); + if (*head_ref != NULL) { + new_node->next = *head_ref; + } else { + new_node->next = NULL; + } // Copy new_data new_node->data = new_data; @@ -193,7 +203,7 @@ subsystem_request(UNUSED_PARAM(ssh_session session), UNUSED_PARAM(void *userdata)) { _ssh_log(SSH_LOG_PROTOCOL, - "=== subsystem_request", "Channel subsystem reqeuest: %s", + "=== subsystem_request", "Channel subsystem request: %s", subsystem); return 0; } @@ -289,7 +299,7 @@ my_channel_eof_function(ssh_session session, _ssh_log(SSH_LOG_PROTOCOL, "=== my_channel_eof_function", - "Got EOF on channel. Shuting down write on socket (fd = %d).", + "Got EOF on channel. Shutting down write on socket (fd = %d).", *event_fd_data->p_fd); stack_socket_close(session, event_fd_data); @@ -418,7 +428,7 @@ my_fd_data_function(UNUSED_PARAM(socket_t fd), break; } wr += i; - _ssh_log(SSH_LOG_FUNCTIONS, "=== my_fd_data_function", "channel_write (%d from %d)", wr, len); + _ssh_log(SSH_LOG_FUNCTIONS, "=== my_fd_data_function", "ssh_channel_write (%d from %d)", wr, len); } while (i > 0 && wr < len); } else { @@ -518,6 +528,13 @@ message_callback(UNUSED_PARAM(ssh_session session), pFd = malloc(sizeof *pFd); cb_chan = malloc(sizeof *cb_chan); event_fd_data = malloc(sizeof *event_fd_data); + if (pFd == NULL || cb_chan == NULL || event_fd_data == NULL) { + SAFE_FREE(pFd); + SAFE_FREE(cb_chan); + SAFE_FREE(event_fd_data); + close(socket_fd); + return 1; + } (*pFd) = socket_fd; event_fd_data->channel = channel; @@ -573,19 +590,11 @@ static struct argp_option options[] = { .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.", + .doc = "Set the rsa key (deprecated alias for 'k').", .group = 0 }, { @@ -612,15 +621,10 @@ parse_opt (int key, char *arg, struct argp_state *state) 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 'r': 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; @@ -671,8 +675,7 @@ main(int argc, char **argv) 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"); + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, KEYS_FOLDER "ssh_host_rsa_key"); #ifdef HAVE_ARGP_H /* diff --git a/examples/sshnetcat.c b/examples/sshnetcat.c index 9bc5d52e..59b0a289 100644 --- a/examples/sshnetcat.c +++ b/examples/sshnetcat.c @@ -238,9 +238,10 @@ void set_pcap(ssh_session session){ } void cleanup_pcap(void); -void cleanup_pcap(){ +void cleanup_pcap(void) +{ ssh_pcap_file_free(pcap); - pcap=NULL; + pcap = NULL; } #endif diff --git a/include/libssh/CMakeLists.txt b/include/libssh/CMakeLists.txt index 83e7b9f8..93445680 100644 --- a/include/libssh/CMakeLists.txt +++ b/include/libssh/CMakeLists.txt @@ -20,6 +20,13 @@ if (WITH_SERVER) ${libssh_HDRS} server.h ) + + if (WITH_SFTP) + set(libssh_HDRS + ${libssh_HDRS} + sftpserver.h + ) + endif (WITH_SFTP) endif (WITH_SERVER) install( diff --git a/include/libssh/agent.h b/include/libssh/agent.h index d4eefbbf..caf8d3e2 100644 --- a/include/libssh/agent.h +++ b/include/libssh/agent.h @@ -70,6 +70,10 @@ #define SSH_AGENT_RSA_SHA2_256 0x02 #define SSH_AGENT_RSA_SHA2_512 0x04 +#ifdef __cplusplus +extern "C" { +#endif + struct ssh_agent_struct { struct ssh_socket_struct *sock; ssh_buffer ident; @@ -77,7 +81,6 @@ struct ssh_agent_struct { ssh_channel channel; }; -#ifndef _WIN32 /* agent.c */ /** * @brief Create a new ssh agent structure. @@ -115,6 +118,9 @@ ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, ssh_string ssh_agent_sign_data(ssh_session session, const ssh_key pubkey, struct ssh_buffer_struct *data); + +#ifdef __cplusplus +} #endif #endif /* __AGENT_H */ diff --git a/include/libssh/auth.h b/include/libssh/auth.h index 90b377d4..b358b7a2 100644 --- a/include/libssh/auth.h +++ b/include/libssh/auth.h @@ -23,6 +23,10 @@ #include "config.h" #include "libssh/callbacks.h" +#ifdef __cplusplus +extern "C" { +#endif + SSH_PACKET_CALLBACK(ssh_packet_userauth_banner); SSH_PACKET_CALLBACK(ssh_packet_userauth_failure); SSH_PACKET_CALLBACK(ssh_packet_userauth_success); @@ -100,4 +104,8 @@ enum ssh_auth_service_state_e { SSH_AUTH_SERVICE_DENIED, }; +#ifdef __cplusplus +} +#endif + #endif /* AUTH_H_ */ diff --git a/include/libssh/bignum.h b/include/libssh/bignum.h index 726ed7b9..6b5dc1a2 100644 --- a/include/libssh/bignum.h +++ b/include/libssh/bignum.h @@ -25,9 +25,16 @@ #include "libssh/libgcrypt.h" #include "libssh/libmbedcrypto.h" +#ifdef __cplusplus +extern "C" { +#endif + 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); +#ifdef __cplusplus +} +#endif #endif /* BIGNUM_H_ */ diff --git a/include/libssh/bind.h b/include/libssh/bind.h index 94256d4a..cd9199b6 100644 --- a/include/libssh/bind.h +++ b/include/libssh/bind.h @@ -25,6 +25,10 @@ #include "libssh/kex.h" #include "libssh/session.h" +#ifdef __cplusplus +extern "C" { +#endif + struct ssh_bind_struct { struct ssh_common_struct common; /* stuff common to ssh_bind and ssh_session */ struct ssh_bind_callbacks_struct *bind_callbacks; @@ -35,11 +39,9 @@ struct ssh_bind_struct { 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; @@ -51,10 +53,14 @@ struct ssh_bind_struct { char *config_dir; char *pubkey_accepted_key_types; char* moduli_file; + int rsa_min_size; }; struct ssh_poll_handle_struct *ssh_bind_get_poll(struct ssh_bind_struct *sshbind); +#ifdef __cplusplus +} +#endif #endif /* BIND_H_ */ diff --git a/include/libssh/bind_config.h b/include/libssh/bind_config.h index cb68da89..5f2dccce 100644 --- a/include/libssh/bind_config.h +++ b/include/libssh/bind_config.h @@ -28,6 +28,10 @@ #include "libssh/server.h" +#ifdef __cplusplus +extern "C" { +#endif + enum ssh_bind_config_opcode_e { /* Known but not allowed in Match block */ BIND_CFG_NOT_ALLOWED_IN_MATCH = -4, @@ -61,4 +65,18 @@ enum ssh_bind_config_opcode_e { */ int ssh_bind_config_parse_file(ssh_bind sshbind, const char *filename); +/* @brief Parse configuration string and set the options to the given bind session + * + * @params[in] bind The ssh bind session + * @params[in] input Null terminated string containing the configuration + * + * @returns SSH_OK on successful parsing the configuration string, + * SSH_ERROR on error + */ +int ssh_bind_config_parse_string(ssh_bind bind, const char *input); + +#ifdef __cplusplus +} +#endif + #endif /* BIND_CONFIG_H_ */ diff --git a/include/libssh/blf.h b/include/libssh/blf.h index ce131e6b..201821a2 100644 --- a/include/libssh/blf.h +++ b/include/libssh/blf.h @@ -49,6 +49,10 @@ #define BLF_MAXKEYLEN ((BLF_N-2)*4) /* 448 bits */ #define BLF_MAXUTILIZED ((BLF_N+2)*4) /* 576 bits */ +#ifdef __cplusplus +extern "C" { +#endif + /* Blowfish context */ typedef struct BlowfishContext { uint32_t S[4][256]; /* S-Boxes */ @@ -84,4 +88,9 @@ void ssh_blf_cbc_decrypt(ssh_blf_ctx *, uint8_t *, uint8_t *, uint32_t); uint32_t Blowfish_stream2word(const uint8_t *, uint16_t , uint16_t *); #endif /* !defined(HAVE_BCRYPT_PBKDF) && !defined(HAVE_BLH_H) */ + +#ifdef __cplusplus +} +#endif + #endif /* _BLF_H */ diff --git a/include/libssh/buffer.h b/include/libssh/buffer.h index cd2dea6a..1fce7b76 100644 --- a/include/libssh/buffer.h +++ b/include/libssh/buffer.h @@ -27,6 +27,10 @@ #define SSH_BUFFER_PACK_END ((uint32_t) 0x4f65feb3) +#ifdef __cplusplus +extern "C" { +#endif + 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); @@ -63,9 +67,9 @@ 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); +uint32_t ssh_buffer_get_u8(ssh_buffer buffer, uint8_t *data); +uint32_t ssh_buffer_get_u32(ssh_buffer buffer, uint32_t *data); +uint32_t 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); @@ -74,4 +78,8 @@ ssh_string ssh_buffer_get_ssh_string(ssh_buffer buffer); 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); +#ifdef __cplusplus +} +#endif + #endif /* BUFFER_H_ */ diff --git a/include/libssh/callbacks.h b/include/libssh/callbacks.h index 972314cb..29c1d391 100644 --- a/include/libssh/callbacks.h +++ b/include/libssh/callbacks.h @@ -27,6 +27,7 @@ #include <libssh/libssh.h> #include <string.h> +#include <stdbool.h> #ifdef __cplusplus extern "C" { @@ -56,7 +57,7 @@ typedef void (*ssh_callback_int) (int code, void *user); * @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 size_t (*ssh_callback_data) (const void *data, size_t len, void *user); typedef void (*ssh_callback_int_int) (int code, int errno_code, void *user); @@ -81,9 +82,9 @@ typedef void (*ssh_log_callback) (ssh_session session, int priority, * * @param priority Priority of the log, the smaller being the more important. * - * @param function The function name calling the the logging fucntions. + * @param function The function name calling the logging functions. * - * @param message The actual message + * @param buffer The actual message * * @param userdata Userdata to be passed to the callback function. */ @@ -117,6 +118,8 @@ typedef void (*ssh_global_request_callback) (ssh_session session, * 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. + * @param originator_address IP address of the machine who sent the request + * @param originator_port port number of the machine who sent the request * @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. @@ -137,6 +140,26 @@ typedef ssh_channel (*ssh_channel_open_request_auth_agent_callback) (ssh_session void *userdata); /** + * @brief Handles an SSH new channel open "forwarded-tcpip" request. This + * happens when the server forwards an incoming TCP connection on a port it was + * previously requested to listen on. This is a client-side API + * @param session current session handler + * @param destination_address the address that the TCP connection connected to + * @param destination_port the port that the TCP connection connected to + * @param originator_address the originator IP address + * @param originator_port the originator port + * @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_forwarded_tcpip_callback) (ssh_session session, + const char *destination_address, int destination_port, + const char *originator_address, int originator_port, + void *userdata); + +/** * The structure to replace libssh functions with appropriate callbacks. */ struct ssh_callbacks_struct { @@ -169,6 +192,11 @@ struct ssh_callbacks_struct { /** 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; + /** + * This function will be called when an incoming "forwarded-tcpip" + * request is received. + */ + ssh_channel_open_request_forwarded_tcpip_callback channel_open_request_forwarded_tcpip_function; }; typedef struct ssh_callbacks_struct *ssh_callbacks; @@ -221,8 +249,8 @@ typedef int (*ssh_auth_gssapi_mic_callback) (ssh_session session, const char *us * @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. + * 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. @@ -268,11 +296,11 @@ typedef ssh_string (*ssh_gssapi_select_oid_callback) (ssh_session session, const int n_oid, ssh_string *oids, void *userdata); /* - * @brief handle the negociation of a security context, server side. + * @brief handle the negotiation 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. + * 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 @@ -397,7 +425,7 @@ struct ssh_socket_callbacks_struct { */ 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. + * on nonblocking state, and the connection succeeded. */ ssh_callback_int_int connected; }; @@ -625,6 +653,7 @@ typedef void (*ssh_channel_signal_callback) (ssh_session session, * @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 exit_status Exit status of the ran command * @param userdata Userdata to be passed to the callback function. */ typedef void (*ssh_channel_exit_status_callback) (ssh_session session, @@ -637,7 +666,7 @@ typedef void (*ssh_channel_exit_status_callback) (ssh_session session, * @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 core a boolean telling whether 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. @@ -652,12 +681,13 @@ typedef void (*ssh_channel_exit_signal_callback) (ssh_session session, /** * @brief SSH channel PTY request from a client. + * @param session the session * @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 pwheight 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 @@ -671,6 +701,7 @@ typedef int (*ssh_channel_pty_request_callback) (ssh_session session, /** * @brief SSH channel Shell request from a client. + * @param session the session * @param channel the channel * @param userdata Userdata to be passed to the callback function. * @returns 0 if the shell request is accepted @@ -683,6 +714,7 @@ typedef int (*ssh_channel_shell_request_callback) (ssh_session session, * @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 session the session * @param channel the channel * @param userdata Userdata to be passed to the callback function. */ @@ -694,7 +726,12 @@ typedef void (*ssh_channel_auth_agent_req_callback) (ssh_session session, * @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 session the session * @param channel the channel + * @param single_connection If true, only one channel should be forwarded + * @param auth_protocol The X11 authentication method to be used + * @param auth_cookie Authentication cookie encoded hexadecimal + * @param screen_number Screen number * @param userdata Userdata to be passed to the callback function. */ typedef void (*ssh_channel_x11_req_callback) (ssh_session session, @@ -706,11 +743,12 @@ typedef void (*ssh_channel_x11_req_callback) (ssh_session session, void *userdata); /** * @brief SSH channel PTY windows change (terminal size) from a client. + * @param session the session * @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 pwheight 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 @@ -723,6 +761,7 @@ typedef int (*ssh_channel_pty_window_change_callback) (ssh_session session, /** * @brief SSH channel Exec request from a client. + * @param session the session * @param channel the channel * @param command the shell command to be executed * @param userdata Userdata to be passed to the callback function. @@ -736,6 +775,7 @@ typedef int (*ssh_channel_exec_request_callback) (ssh_session session, /** * @brief SSH channel environment request from a client. + * @param session the session * @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 @@ -752,6 +792,7 @@ typedef int (*ssh_channel_env_request_callback) (ssh_session session, void *userdata); /** * @brief SSH channel subsystem request from a client. + * @param session the session * @param channel the channel * @param subsystem the subsystem required * @param userdata Userdata to be passed to the callback function. @@ -766,6 +807,8 @@ typedef int (*ssh_channel_subsystem_request_callback) (ssh_session session, /** * @brief SSH channel write will not block (flow control). * + * @param session the session + * * @param channel the channel * * @param[in] bytes size of the remote window in bytes. Writing as much data @@ -777,9 +820,31 @@ typedef int (*ssh_channel_subsystem_request_callback) (ssh_session session, */ typedef int (*ssh_channel_write_wontblock_callback) (ssh_session session, ssh_channel channel, - size_t bytes, + uint32_t bytes, void *userdata); +/** + * @brief SSH channel open callback. Called when a channel open succeeds or fails. + * @param session Current session handler + * @param channel the actual channel + * @param is_success is 1 when the open succeeds, and 0 otherwise. + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_open_resp_callback) (ssh_session session, + ssh_channel channel, + bool is_success, + void *userdata); + +/** + * @brief SSH channel request response callback. Called when a response to the pending request is received. + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_request_resp_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + struct ssh_channel_callbacks_struct { /** DON'T SET THIS use ssh_callbacks_init() instead. */ size_t size; @@ -847,6 +912,14 @@ struct ssh_channel_callbacks_struct { * not to block. */ ssh_channel_write_wontblock_callback channel_write_wontblock_function; + /** + * This functions will be called when the channel has received a channel open confirmation or failure. + */ + ssh_channel_open_resp_callback channel_open_response_function; + /** + * This functions will be called when the channel has received the response to the pending request. + */ + ssh_channel_request_resp_callback channel_request_response_function; }; typedef struct ssh_channel_callbacks_struct *ssh_channel_callbacks; @@ -917,7 +990,7 @@ LIBSSH_API int ssh_remove_channel_callbacks(ssh_channel channel, /** @} */ -/** @group libssh_threads +/** @addtogroup libssh_threads * @{ */ @@ -983,13 +1056,14 @@ LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_pthread(void); * @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. + * @return 0 on success, < 0 on error. */ LIBSSH_API int ssh_set_log_callback(ssh_logging_callback cb); @@ -1000,7 +1074,6 @@ LIBSSH_API int ssh_set_log_callback(ssh_logging_callback cb); */ LIBSSH_API ssh_logging_callback ssh_get_log_callback(void); -/** @} */ #ifdef __cplusplus } #endif diff --git a/include/libssh/chacha.h b/include/libssh/chacha.h index 867f532d..ab3fe492 100644 --- a/include/libssh/chacha.h +++ b/include/libssh/chacha.h @@ -18,6 +18,10 @@ struct chacha_ctx { #define CHACHA_CTRLEN 8 #define CHACHA_STATELEN (CHACHA_NONCELEN+CHACHA_CTRLEN) +#ifdef __cplusplus +extern "C" { +#endif + 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))) @@ -37,4 +41,8 @@ void chacha_encrypt_bytes(struct chacha_ctx *x, const uint8_t *m, #endif ; +#ifdef __cplusplus +} +#endif + #endif /* CHACHA_H */ diff --git a/include/libssh/channels.h b/include/libssh/channels.h index bbabcfd1..cb2bea43 100644 --- a/include/libssh/channels.h +++ b/include/libssh/channels.h @@ -22,6 +22,10 @@ #define CHANNELS_H_ #include "libssh/priv.h" +#ifdef __cplusplus +extern "C" { +#endif + /** @internal * Describes the different possible states in a * outgoing (client) channel request @@ -35,7 +39,7 @@ enum ssh_channel_request_state_e { 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 */ + /** A request has been replied and an error happened */ SSH_CHANNEL_REQ_STATE_ERROR }; @@ -98,7 +102,7 @@ SSH_PACKET_CALLBACK(channel_rcv_request); SSH_PACKET_CALLBACK(channel_rcv_data); int channel_default_bufferize(ssh_channel channel, - void *data, size_t len, + void *data, uint32_t len, bool is_stderr); int ssh_channel_flush(ssh_channel channel); uint32_t ssh_channel_new_id(ssh_session session); @@ -109,4 +113,8 @@ int ssh_global_request(ssh_session session, ssh_buffer buffer, int reply); +#ifdef __cplusplus +} +#endif + #endif /* CHANNELS_H_ */ diff --git a/include/libssh/config.h b/include/libssh/config.h index f2b9b57c..4f8a9b6f 100644 --- a/include/libssh/config.h +++ b/include/libssh/config.h @@ -42,7 +42,6 @@ enum ssh_config_opcode_e { SOC_MACS, SOC_COMPRESSION, SOC_TIMEOUT, - SOC_PROTOCOL, SOC_STRICTHOSTKEYCHECK, SOC_KNOWNHOSTS, SOC_PROXYCOMMAND, @@ -60,8 +59,13 @@ enum ssh_config_opcode_e { SOC_KBDINTERACTIVEAUTHENTICATION, SOC_PASSWORDAUTHENTICATION, SOC_PUBKEYAUTHENTICATION, - SOC_PUBKEYACCEPTEDTYPES, + SOC_PUBKEYACCEPTEDKEYTYPES, SOC_REKEYLIMIT, + SOC_IDENTITYAGENT, + SOC_IDENTITIESONLY, + SOC_CONTROLMASTER, + SOC_CONTROLPATH, + SOC_CERTIFICATE, SOC_MAX /* Keep this one last in the list */ }; diff --git a/include/libssh/config_parser.h b/include/libssh/config_parser.h index e974917c..4648614c 100644 --- a/include/libssh/config_parser.h +++ b/include/libssh/config_parser.h @@ -26,6 +26,12 @@ #ifndef CONFIG_PARSER_H_ #define CONFIG_PARSER_H_ +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdbool.h> + char *ssh_config_get_cmd(char **str); char *ssh_config_get_token(char **str); @@ -45,13 +51,20 @@ int ssh_config_get_yesno(char **str, int notfound); * 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. + * @param[in] ignore_port Set to true if we should not attempt to parse + * port number. * * @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); + char **username, + char **hostname, + char **port, + bool ignore_port); + +#ifdef __cplusplus +} +#endif #endif /* LIBSSH_CONFIG_H_ */ diff --git a/include/libssh/crypto.h b/include/libssh/crypto.h index 67d98392..32016827 100644 --- a/include/libssh/crypto.h +++ b/include/libssh/crypto.h @@ -111,7 +111,11 @@ struct ssh_crypto_struct { #endif /* WITH_GEX */ #ifdef HAVE_ECDH #ifdef HAVE_OPENSSL_ECC +#if OPENSSL_VERSION_NUMBER < 0x30000000L EC_KEY *ecdh_privkey; +#else + EVP_PKEY *ecdh_privkey; +#endif /* OPENSSL_VERSION_NUMBER */ #elif defined HAVE_GCRYPT_ECC gcry_sexp_t ecdh_privkey; #elif defined HAVE_LIBMBEDCRYPTO @@ -126,8 +130,9 @@ struct ssh_crypto_struct { 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 */ + size_t session_id_len; unsigned char *session_id; + size_t digest_len; /* len of the secret hash */ unsigned char *secret_hash; /* Secret hash is same as session id until re-kex */ unsigned char *encryptIV; unsigned char *decryptIV; @@ -207,12 +212,23 @@ struct ssh_cipher_struct { void (*cleanup)(struct ssh_cipher_struct *cipher); }; +#ifdef __cplusplus +extern "C" { +#endif + 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, + uint8_t key_type, unsigned char *output, size_t requested_len); int secure_memcmp(const void *s1, const void *s2, size_t n); +#if defined(HAVE_LIBCRYPTO) && !defined(WITH_PKCS11_PROVIDER) +ENGINE *pki_get_engine(void); +#endif /* HAVE_LIBCRYPTO */ + +#ifdef __cplusplus +} +#endif #endif /* _CRYPTO_H_ */ diff --git a/include/libssh/curve25519.h b/include/libssh/curve25519.h index f0cc6348..a55f52c7 100644 --- a/include/libssh/curve25519.h +++ b/include/libssh/curve25519.h @@ -33,6 +33,10 @@ #define crypto_scalarmult crypto_scalarmult_curve25519 #else +#ifdef __cplusplus +extern "C" { +#endif + #define CURVE25519_PUBKEY_SIZE 32 #define CURVE25519_PRIVKEY_SIZE 32 int crypto_scalarmult_base(unsigned char *q, const unsigned char *n); @@ -48,9 +52,14 @@ typedef unsigned char ssh_curve25519_privkey[CURVE25519_PRIVKEY_SIZE]; int ssh_client_curve25519_init(ssh_session session); +void ssh_client_curve25519_remove_callbacks(ssh_session session); #ifdef WITH_SERVER void ssh_server_curve25519_init(ssh_session session); #endif /* WITH_SERVER */ +#ifdef __cplusplus +} +#endif + #endif /* CURVE25519_H_ */ diff --git a/include/libssh/dh-gex.h b/include/libssh/dh-gex.h index 4fc23d82..0f547e37 100644 --- a/include/libssh/dh-gex.h +++ b/include/libssh/dh-gex.h @@ -23,10 +23,19 @@ #ifndef SRC_DH_GEX_H_ #define SRC_DH_GEX_H_ +#ifdef __cplusplus +extern "C" { +#endif + int ssh_client_dhgex_init(ssh_session session); +void ssh_client_dhgex_remove_callbacks(ssh_session session); #ifdef WITH_SERVER void ssh_server_dhgex_init(ssh_session session); #endif /* WITH_SERVER */ +#ifdef __cplusplus +} +#endif + #endif /* SRC_DH_GEX_H_ */ diff --git a/include/libssh/dh.h b/include/libssh/dh.h index 390b30da..34c4a7ed 100644 --- a/include/libssh/dh.h +++ b/include/libssh/dh.h @@ -30,20 +30,34 @@ struct dh_ctx; #define DH_CLIENT_KEYPAIR 0 #define DH_SERVER_KEYPAIR 1 +#ifdef __cplusplus +extern "C" { +#endif + /* functions implemented by crypto backends */ int ssh_dh_init_common(struct ssh_crypto_struct *crypto); void ssh_dh_cleanup(struct ssh_crypto_struct *crypto); +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L int ssh_dh_get_parameters(struct dh_ctx *ctx, const_bignum *modulus, const_bignum *generator); +#else +int ssh_dh_get_parameters(struct dh_ctx *ctx, + bignum *modulus, bignum *generator); +#endif /* OPENSSL_VERSION_NUMBER */ 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); +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, const_bignum *priv, const_bignum *pub); +#else +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + bignum *priv, bignum *pub); +#endif /* OPENSSL_VERSION_NUMBER */ int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, - const bignum priv, const bignum pub); + bignum priv, bignum pub); int ssh_dh_compute_shared_secret(struct dh_ctx *ctx, int local, int remote, bignum *dest); @@ -63,8 +77,10 @@ int ssh_dh_get_current_server_publickey_blob(ssh_session session, 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 dh_handshake(ssh_session session); int ssh_client_dh_init(ssh_session session); +void ssh_client_dh_remove_callbacks(ssh_session session); #ifdef WITH_SERVER void ssh_server_dh_init(ssh_session session); #endif /* WITH_SERVER */ @@ -72,4 +88,8 @@ 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); +#ifdef __cplusplus +} +#endif + #endif /* DH_H_ */ diff --git a/include/libssh/ecdh.h b/include/libssh/ecdh.h index 17fe02e7..4c4c54eb 100644 --- a/include/libssh/ecdh.h +++ b/include/libssh/ecdh.h @@ -42,9 +42,14 @@ #define HAVE_ECDH 1 #endif +#ifdef __cplusplus +extern "C" { +#endif + extern struct ssh_packet_callbacks_struct ssh_ecdh_client_callbacks; /* Backend-specific functions. */ int ssh_client_ecdh_init(ssh_session session); +void ssh_client_ecdh_remove_callbacks(ssh_session session); int ecdh_build_k(ssh_session session); #ifdef WITH_SERVER @@ -53,4 +58,8 @@ void ssh_server_ecdh_init(ssh_session session); SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init); #endif /* WITH_SERVER */ +#ifdef __cplusplus +} +#endif + #endif /* ECDH_H_ */ diff --git a/include/libssh/ed25519.h b/include/libssh/ed25519.h index 8a3263c8..72a86c0b 100644 --- a/include/libssh/ed25519.h +++ b/include/libssh/ed25519.h @@ -24,10 +24,10 @@ /** * @defgroup ed25519 ed25519 API - * @internal * @brief API for DJB's ed25519 * - * @{ */ + * @{ + */ #define ED25519_PK_LEN 32 #define ED25519_SK_LEN 64 @@ -37,6 +37,10 @@ typedef uint8_t ed25519_pubkey[ED25519_PK_LEN]; typedef uint8_t ed25519_privkey[ED25519_SK_LEN]; typedef uint8_t ed25519_signature[ED25519_SIG_LEN]; +#ifdef __cplusplus +extern "C" { +#endif + /** @internal * @brief generate an ed25519 key pair * @param[out] pk generated public key @@ -76,4 +80,8 @@ int crypto_sign_ed25519_open( const ed25519_pubkey pk); /** @} */ +#ifdef __cplusplus +} +#endif + #endif /* ED25519_H_ */ diff --git a/include/libssh/fe25519.h b/include/libssh/fe25519.h index 438d85db..0dfb0613 100644 --- a/include/libssh/fe25519.h +++ b/include/libssh/fe25519.h @@ -33,6 +33,10 @@ typedef struct { uint32_t v[32]; } fe25519; +#ifdef __cplusplus +extern "C" { +#endif + void fe25519_freeze(fe25519 *r); void fe25519_unpack(fe25519 *r, const unsigned char x[32]); @@ -65,4 +69,8 @@ void fe25519_invert(fe25519 *r, const fe25519 *x); void fe25519_pow2523(fe25519 *r, const fe25519 *x); +#ifdef __cplusplus +} +#endif + #endif diff --git a/include/libssh/ge25519.h b/include/libssh/ge25519.h index 329bd042..480f29dc 100644 --- a/include/libssh/ge25519.h +++ b/include/libssh/ge25519.h @@ -28,6 +28,10 @@ typedef struct fe25519 t; } ge25519; +#ifdef __cplusplus +extern "C" { +#endif + extern const ge25519 ge25519_base; int ge25519_unpackneg_vartime(ge25519 *r, const unsigned char p[32]); @@ -40,4 +44,8 @@ void ge25519_double_scalarmult_vartime(ge25519 *r, const ge25519 *p1, const sc25 void ge25519_scalarmult_base(ge25519 *r, const sc25519 *s); +#ifdef __cplusplus +} +#endif + #endif diff --git a/include/libssh/gssapi.h b/include/libssh/gssapi.h index ccd83664..b0c74c73 100644 --- a/include/libssh/gssapi.h +++ b/include/libssh/gssapi.h @@ -29,6 +29,10 @@ typedef struct ssh_gssapi_struct *ssh_gssapi; +#ifdef __cplusplus +extern "C" { +#endif + #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); @@ -42,4 +46,8 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response); int ssh_gssapi_auth_mic(ssh_session session); +#ifdef __cplusplus +} +#endif + #endif /* GSSAPI_H */ diff --git a/include/libssh/kex.h b/include/libssh/kex.h index 3a1f4a6f..4a2ecb99 100644 --- a/include/libssh/kex.h +++ b/include/libssh/kex.h @@ -31,15 +31,24 @@ struct ssh_kex_struct { char *methods[SSH_KEX_METHODS]; }; +#ifdef __cplusplus +extern "C" { +#endif + SSH_PACKET_CALLBACK(ssh_packet_kexinit); -int ssh_send_kex(ssh_session session, int server_kex); +int ssh_send_kex(ssh_session session); void ssh_list_kex(struct ssh_kex_struct *kex); int ssh_set_client_kex(ssh_session session); +int ssh_kex_append_extensions(ssh_session session, struct ssh_kex_struct *pkex); 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_add_to_default_algos(enum ssh_kex_types_e algo, const char *list); +char *ssh_remove_from_default_algos(enum ssh_kex_types_e algo, + const char *list); +char *ssh_prefix_default_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); @@ -56,4 +65,8 @@ 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); +#ifdef __cplusplus +} +#endif + #endif /* KEX_H_ */ diff --git a/include/libssh/keys.h b/include/libssh/keys.h index 7b138612..1379539e 100644 --- a/include/libssh/keys.h +++ b/include/libssh/keys.h @@ -29,34 +29,36 @@ 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; + EVP_PKEY *key_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; + EVP_PKEY *key_priv; #elif defined(HAVE_LIBMBEDCRYPTO) mbedtls_pk_context *rsa_priv; - void *dsa_priv; #endif }; +#ifdef __cplusplus +extern "C" { +#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); +#ifdef __cplusplus +} +#endif + #endif /* KEYS_H_ */ diff --git a/include/libssh/knownhosts.h b/include/libssh/knownhosts.h index 44e434c0..b50018cd 100644 --- a/include/libssh/knownhosts.h +++ b/include/libssh/knownhosts.h @@ -22,6 +22,10 @@ #ifndef SSH_KNOWNHOSTS_H_ #define SSH_KNOWNHOSTS_H_ +#ifdef __cplusplus +extern "C" { +#endif + 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 @@ -29,4 +33,8 @@ ssh_session_get_known_hosts_entry_file(ssh_session session, const char *filename, struct ssh_knownhosts_entry **pentry); +#ifdef __cplusplus +} +#endif + #endif /* SSH_KNOWNHOSTS_H_ */ diff --git a/include/libssh/legacy.h b/include/libssh/legacy.h index 911173ee..38bef4da 100644 --- a/include/libssh/legacy.h +++ b/include/libssh/legacy.h @@ -31,6 +31,10 @@ typedef struct ssh_private_key_struct* ssh_private_key; typedef struct ssh_public_key_struct* ssh_public_key; +#ifdef __cplusplus +extern "C" { +#endif + 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); @@ -88,19 +92,19 @@ SSH_DEPRECATED LIBSSH_API int channel_select(ssh_channel *readchans, ssh_channel 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, +SSH_DEPRECATED LIBSSH_API void privatekey_free(ssh_private_key prv); +SSH_DEPRECATED 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_DEPRECATED LIBSSH_API void publickey_free(ssh_public_key key); +SSH_DEPRECATED 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, +SSH_DEPRECATED 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_DEPRECATED LIBSSH_API ssh_public_key publickey_from_privatekey(ssh_private_key prv); +SSH_DEPRECATED LIBSSH_API ssh_string publickey_to_string(ssh_public_key key); +SSH_DEPRECATED 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); +SSH_DEPRECATED LIBSSH_API enum ssh_keytypes_e ssh_privatekey_type(ssh_private_key privatekey); LIBSSH_API ssh_string ssh_get_pubkey(ssh_session session); @@ -117,4 +121,8 @@ 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); +#ifdef __cplusplus +} +#endif + #endif /* LEGACY_H_ */ diff --git a/include/libssh/libcrypto.h b/include/libssh/libcrypto.h index 403c2d22..2f6bdc0a 100644 --- a/include/libssh/libcrypto.h +++ b/include/libssh/libcrypto.h @@ -25,13 +25,14 @@ #ifdef HAVE_LIBCRYPTO -#include <openssl/dsa.h> +#include "libssh/libssh.h" #include <openssl/rsa.h> #include <openssl/sha.h> #include <openssl/md5.h> #include <openssl/hmac.h> #include <openssl/evp.h> #include <openssl/crypto.h> +#include <openssl/ec.h> typedef EVP_MD_CTX* SHACTX; typedef EVP_MD_CTX* SHA256CTX; @@ -39,11 +40,6 @@ typedef EVP_MD_CTX* SHA384CTX; typedef EVP_MD_CTX* SHA512CTX; typedef EVP_MD_CTX* MD5CTX; typedef EVP_MD_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 @@ -58,8 +54,15 @@ typedef void *EVPCTX; #define EVP_DIGEST_LEN EVP_MAX_MD_SIZE #endif +/* Use ssh_crypto_free() to release memory allocated by bignum_bn2dec(), + bignum_bn2hex() and other functions that use crypto-library functions that + are documented to allocate memory that needs to be de-allocate with + OPENSSL_free. */ +#define ssh_crypto_free(x) OPENSSL_free(x) + #include <openssl/bn.h> #include <openssl/opensslv.h> + typedef BIGNUM* bignum; typedef const BIGNUM* const_bignum; typedef BN_CTX* bignum_CTX; @@ -110,10 +113,14 @@ typedef BN_CTX* bignum_CTX; /* Returns true if the OpenSSL is operating in FIPS mode */ #ifdef HAVE_OPENSSL_FIPS_MODE #define ssh_fips_mode() (FIPS_mode() != 0) +#elif OPENSSL_VERSION_NUMBER >= 0x30000000L +#define ssh_fips_mode() EVP_default_properties_is_fips_enabled(NULL) #else #define ssh_fips_mode() false #endif +ssh_string pki_key_make_ecpoint_string(const EC_GROUP *g, const EC_POINT *p); +int pki_key_ecgroup_name_to_nid(const char *group); #endif /* HAVE_LIBCRYPTO */ #endif /* LIBCRYPTO_H_ */ diff --git a/include/libssh/libgcrypt.h b/include/libssh/libgcrypt.h index 347d851b..a8044545 100644 --- a/include/libssh/libgcrypt.h +++ b/include/libssh/libgcrypt.h @@ -32,7 +32,6 @@ 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 @@ -49,6 +48,8 @@ typedef gcry_md_hd_t EVPCTX; #define EVP_DIGEST_LEN EVP_MAX_MD_SIZE +#define ssh_crypto_free(x) gcry_free(x) + typedef gcry_mpi_t bignum; typedef const struct gcry_mpi *const_bignum; typedef void* bignum_CTX; @@ -104,6 +105,10 @@ int ssh_gcry_rand_range(bignum rnd, bignum max); } while(0) /* Helper functions for data conversions. */ +#ifdef __cplusplus +extern "C" { +#endif + /* 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. */ @@ -114,6 +119,10 @@ ssh_string ssh_sexp_extract_mpi(const gcry_sexp_t sexp, #define ssh_fips_mode() false +#ifdef __cplusplus +} +#endif + #endif /* HAVE_LIBGCRYPT */ #endif /* LIBGCRYPT_H_ */ diff --git a/include/libssh/libmbedcrypto.h b/include/libssh/libmbedcrypto.h index fe53019b..918fe293 100644 --- a/include/libssh/libmbedcrypto.h +++ b/include/libssh/libmbedcrypto.h @@ -34,6 +34,7 @@ #include <mbedtls/cipher.h> #include <mbedtls/entropy.h> #include <mbedtls/ctr_drbg.h> +#include <mbedtls/platform.h> typedef mbedtls_md_context_t *SHACTX; typedef mbedtls_md_context_t *SHA256CTX; @@ -41,7 +42,6 @@ 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 @@ -59,6 +59,8 @@ typedef mbedtls_md_context_t *EVPCTX; #define EVP_DIGEST_LEN EVP_MAX_MD_SIZE +#define ssh_crypto_free(x) mbedtls_free(x) + typedef mbedtls_mpi *bignum; typedef const mbedtls_mpi *const_bignum; typedef void* bignum_CTX; @@ -73,9 +75,13 @@ struct mbedtls_ecdsa_sig { bignum s; }; +#ifdef __cplusplus +extern "C" { +#endif + bignum ssh_mbedcry_bn_new(void); void ssh_mbedcry_bn_free(bignum num); -unsigned char *ssh_mbedcry_bn2num(const_bignum num, int radix); +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); @@ -101,7 +107,7 @@ int ssh_mbedcry_hex2bn(bignum *dest, char *data); } 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_bn2hex(num, dest) (*dest)=(unsigned char *)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) @@ -136,5 +142,9 @@ ssh_string make_ecpoint_string(const mbedtls_ecp_group *g, const #define ssh_fips_mode() false +#ifdef __cplusplus +} +#endif + #endif /* HAVE_LIBMBEDCRYPTO */ #endif /* LIBMBEDCRYPTO_H_ */ diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h index 4bfd81ef..13f1b812 100644 --- a/include/libssh/libssh.h +++ b/include/libssh/libssh.h @@ -1,7 +1,7 @@ /* * This file is part of the SSH Library * - * Copyright (c) 2003-2021 by Aris Adamantiadis and the libssh team + * Copyright (c) 2003-2024 by Aris Adamantiadis and the libssh team * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -50,18 +50,13 @@ #endif #include <stdarg.h> +#include <stdint.h> +#include <inttypes.h> #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 <unistd.h> - #include <inttypes.h> #include <sys/types.h> #endif /* _MSC_VER */ @@ -82,7 +77,7 @@ #define PRINTF_ATTRIBUTE(a,b) #endif /* __GNUC__ */ -#ifdef __GNUC__ +#if !defined(SSH_SUPPRESS_DEPRECATED) && defined(__GNUC__) #define SSH_DEPRECATED __attribute__ ((deprecated)) #else #define SSH_DEPRECATED @@ -196,7 +191,8 @@ 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 + SSH_GLOBAL_REQUEST_KEEPALIVE, + SSH_GLOBAL_REQUEST_NO_MORE_SESSIONS }; enum ssh_publickey_state_e { @@ -277,12 +273,12 @@ enum ssh_error_types_e { /* some types for keys */ enum ssh_keytypes_e{ SSH_KEYTYPE_UNKNOWN=0, - SSH_KEYTYPE_DSS=1, + SSH_KEYTYPE_DSS=1, /* deprecated */ SSH_KEYTYPE_RSA, SSH_KEYTYPE_RSA1, SSH_KEYTYPE_ECDSA, /* deprecated */ SSH_KEYTYPE_ED25519, - SSH_KEYTYPE_DSS_CERT01, + SSH_KEYTYPE_DSS_CERT01, /* deprecated */ SSH_KEYTYPE_RSA_CERT01, SSH_KEYTYPE_ECDSA_P256, SSH_KEYTYPE_ECDSA_P384, @@ -299,7 +295,8 @@ enum ssh_keytypes_e{ enum ssh_keycmp_e { SSH_KEY_CMP_PUBLIC = 0, - SSH_KEY_CMP_PRIVATE + SSH_KEY_CMP_PRIVATE = 1, + SSH_KEY_CMP_CERTIFICATE = 2, }; #define SSH_ADDRSTRLEN 46 @@ -328,16 +325,16 @@ enum { /** No logging at all */ SSH_LOG_NOLOG=0, - /** Only warnings + /** Only unrecoverable errors */ SSH_LOG_WARNING, - /** High level protocol information + /** Information for the users */ SSH_LOG_PROTOCOL, - /** Lower level protocol infomations, packet level + /** Debug information, to see what is going on */ SSH_LOG_PACKET, - /** Every function path + /** Trace information and recoverable error messages */ SSH_LOG_FUNCTIONS }; @@ -353,17 +350,25 @@ enum { /** No logging at all */ #define SSH_LOG_NONE 0 -/** Show only warnings */ +/** Show only fatal warnings */ #define SSH_LOG_WARN 1 /** Get some information what's going on */ #define SSH_LOG_INFO 2 -/** Get detailed debuging information **/ +/** Get detailed debugging information **/ #define SSH_LOG_DEBUG 3 /** Get trace output, packet information, ... */ #define SSH_LOG_TRACE 4 /** @} */ +enum ssh_control_master_options_e { + SSH_CONTROL_MASTER_NO, + SSH_CONTROL_MASTER_AUTO, + SSH_CONTROL_MASTER_YES, + SSH_CONTROL_MASTER_ASK, + SSH_CONTROL_MASTER_AUTOASK +}; + enum ssh_options_e { SSH_OPTIONS_HOST, SSH_OPTIONS_PORT, @@ -406,6 +411,12 @@ enum ssh_options_e { SSH_OPTIONS_PROCESS_CONFIG, SSH_OPTIONS_REKEY_DATA, SSH_OPTIONS_REKEY_TIME, + SSH_OPTIONS_RSA_MIN_SIZE, + SSH_OPTIONS_IDENTITY_AGENT, + SSH_OPTIONS_IDENTITIES_ONLY, + SSH_OPTIONS_CONTROL_MASTER, + SSH_OPTIONS_CONTROL_PATH, + SSH_OPTIONS_CERTIFICATE, }; enum { @@ -468,6 +479,8 @@ 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_pty_size_modes(ssh_channel channel, const char *term, + int cols, int rows, const unsigned char* modes, size_t modes_len); 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); @@ -477,8 +490,6 @@ LIBSSH_API int ssh_channel_request_x11(ssh_channel channel, int single_connectio 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); @@ -509,7 +520,12 @@ 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, +LIBSSH_API ssh_channel ssh_channel_open_forward_port(ssh_session session, + int timeout_ms, + int *destination_port, + char **originator, + int *originator_port); +SSH_DEPRECATED 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, @@ -528,6 +544,7 @@ 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_request_no_more_sessions(ssh_session session); LIBSSH_API int ssh_get_server_publickey(ssh_session session, ssh_key *key); @@ -551,7 +568,27 @@ 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); +SSH_DEPRECATED LIBSSH_API int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout); +SSH_DEPRECATED LIBSSH_API int ssh_scp_accept_request(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_close(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_deny_request(ssh_scp scp, const char *reason); +SSH_DEPRECATED LIBSSH_API void ssh_scp_free(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_init(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_leave_directory(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location); +SSH_DEPRECATED LIBSSH_API int ssh_scp_pull_request(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode); +SSH_DEPRECATED LIBSSH_API int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int perms); +SSH_DEPRECATED LIBSSH_API int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, int perms); +SSH_DEPRECATED LIBSSH_API int ssh_scp_read(ssh_scp scp, void *buffer, size_t size); +SSH_DEPRECATED LIBSSH_API const char *ssh_scp_request_get_filename(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_request_get_permissions(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API size_t ssh_scp_request_get_size(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API uint64_t ssh_scp_request_get_size64(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API const char *ssh_scp_request_get_warning(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len); LIBSSH_API int ssh_get_random(void *where,int len,int strong); @@ -651,6 +688,12 @@ typedef int (*ssh_auth_callback) (const char *prompt, char *buf, size_t len, /** @} */ +enum ssh_file_format_e { + SSH_FILE_FORMAT_DEFAULT = 0, + SSH_FILE_FORMAT_OPENSSH, + SSH_FILE_FORMAT_PEM, +}; + 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) @@ -663,6 +706,7 @@ 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 ssh_key ssh_key_dup(const ssh_key key); LIBSSH_API int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, ssh_key *pkey); @@ -676,6 +720,13 @@ LIBSSH_API int ssh_pki_export_privkey_base64(const ssh_key privkey, ssh_auth_callback auth_fn, void *auth_data, char **b64_key); +LIBSSH_API int +ssh_pki_export_privkey_base64_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key, + enum ssh_file_format_e format); LIBSSH_API int ssh_pki_import_privkey_file(const char *filename, const char *passphrase, ssh_auth_callback auth_fn, @@ -686,6 +737,13 @@ LIBSSH_API int ssh_pki_export_privkey_file(const ssh_key privkey, ssh_auth_callback auth_fn, void *auth_data, const char *filename); +LIBSSH_API int +ssh_pki_export_privkey_file_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename, + enum ssh_file_format_e format); LIBSSH_API int ssh_pki_copy_cert_to_privkey(const ssh_key cert_key, ssh_key privkey); @@ -718,24 +776,6 @@ LIBSSH_API void ssh_print_hash(enum ssh_publickey_hash_type type, unsigned char 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); @@ -759,10 +799,8 @@ LIBSSH_API int ssh_userauth_try_publickey(ssh_session session, 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_get_current_identity(ssh_session session, char** value); LIBSSH_API int ssh_userauth_publickey_auto(ssh_session session, @@ -833,6 +871,7 @@ LIBSSH_API int ssh_buffer_add_data(ssh_buffer buffer, const void *data, uint32_t 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); +LIBSSH_API int ssh_session_set_disconnect_message(ssh_session session, const char *message); #ifndef LIBSSH_LEGACY_0_4 #include "libssh/legacy.h" diff --git a/include/libssh/libsshpp.hpp b/include/libssh/libsshpp.hpp index f792465c..e0f21e85 100644 --- a/include/libssh/libsshpp.hpp +++ b/include/libssh/libsshpp.hpp @@ -523,7 +523,7 @@ public: return ssh_channel_is_open(channel) != 0; } int openForward(const char *remotehost, int remoteport, - const char *sourcehost=NULL, int localport=0){ + const char *sourcehost, int localport=0){ int err=ssh_channel_open_forward(channel,remotehost,remoteport, sourcehost, localport); ssh_throw(err); @@ -587,9 +587,12 @@ public: ssh_throw(err); return_throwable; } - void_throwable requestPty(const char *term=NULL, int cols=0, int rows=0){ + void_throwable requestPty(const char *term=NULL, int cols=0, int rows=0, + const unsigned char* modes=NULL, size_t modes_len=0){ int err; - if(term != NULL && cols != 0 && rows != 0) + if(term != NULL && cols != 0 && rows != 0 && modes != NULL) + err=ssh_channel_request_pty_size_modes(channel,term,cols,rows,modes,modes_len); + else if(term != NULL && cols != 0 && rows != 0) err=ssh_channel_request_pty_size(channel,term,cols,rows); else err=ssh_channel_request_pty(channel); @@ -630,8 +633,8 @@ public: * @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 + * @see ssh_channel_write + * @see ssh_channel_write_stderr */ int write(const void *data, size_t len, bool is_stderr=false){ int ret; @@ -669,7 +672,7 @@ private: inline Channel *Session::acceptForward(int timeout_ms){ ssh_channel forward = - ssh_channel_accept_forward(c_session, timeout_ms, NULL); + ssh_channel_open_forward_port(c_session, timeout_ms, NULL, NULL, NULL); ssh_throw_null(c_session,forward); Channel *newchan = new Channel(*this,forward); return newchan; diff --git a/include/libssh/messages.h b/include/libssh/messages.h index 04d041d4..160306cc 100644 --- a/include/libssh/messages.h +++ b/include/libssh/messages.h @@ -28,6 +28,7 @@ struct ssh_auth_request { int method; char *password; struct ssh_key_struct *pubkey; + char *sigtype; enum ssh_publickey_state_e signature_state; char kbdint_response; }; @@ -91,6 +92,10 @@ struct ssh_message_struct { struct ssh_global_request global_request; }; +#ifdef __cplusplus +extern "C" { +#endif + SSH_PACKET_CALLBACK(ssh_packet_channel_open); SSH_PACKET_CALLBACK(ssh_packet_global_request); @@ -103,4 +108,8 @@ int ssh_message_handle_channel_request(ssh_session session, ssh_channel channel, const char *request, uint8_t want_reply); ssh_message ssh_message_pop_head(ssh_session session); +#ifdef __cplusplus +} +#endif + #endif /* MESSAGES_H_ */ diff --git a/include/libssh/misc.h b/include/libssh/misc.h index 5044817a..fc8596f7 100644 --- a/include/libssh/misc.h +++ b/include/libssh/misc.h @@ -21,6 +21,25 @@ #ifndef MISC_H_ #define MISC_H_ +#ifdef _WIN32 + +# ifdef _MSC_VER +# ifndef _SSIZE_T_DEFINED +# undef ssize_t +# include <BaseTsd.h> + typedef _W64 SSIZE_T ssize_t; +# define _SSIZE_T_DEFINED +# endif /* _SSIZE_T_DEFINED */ +# endif /* _MSC_VER */ + +#else +# include <sys/types.h> +#endif /* _WIN32 */ + +#ifdef __cplusplus +extern "C" { +#endif + /* in misc.c */ /* gets the user home dir. */ char *ssh_get_user_home_dir(void); @@ -75,13 +94,13 @@ const void *_ssh_list_pop_head(struct ssh_list *list); /** @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 + * @param ssh_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); +int ssh_make_milliseconds(unsigned long sec, unsigned 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); @@ -96,7 +115,18 @@ 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); -int ssh_tmpname(char *template); +int ssh_tmpname(char *name); char *ssh_strreplace(const char *src, const char *pattern, const char *repl); + +ssize_t ssh_readn(int fd, void *buf, size_t nbytes); +ssize_t ssh_writen(int fd, const void *buf, size_t nbytes); + +int ssh_check_hostname_syntax(const char *hostname); +int ssh_check_username_syntax(const char *username); + +#ifdef __cplusplus +} +#endif + #endif /* MISC_H_ */ diff --git a/include/libssh/options.h b/include/libssh/options.h index e8dc6c69..9050d3be 100644 --- a/include/libssh/options.h +++ b/include/libssh/options.h @@ -21,11 +21,20 @@ #ifndef _OPTIONS_H #define _OPTIONS_H +#ifdef __cplusplus +extern "C" { +#endif + int ssh_config_parse_file(ssh_session session, const char *filename); int ssh_config_parse_string(ssh_session session, const char *input); int ssh_options_set_algo(ssh_session session, enum ssh_kex_types_e algo, - const char *list); + const char *list, + char **place); int ssh_options_apply(ssh_session session); +#ifdef __cplusplus +} +#endif + #endif /* _OPTIONS_H */ diff --git a/include/libssh/packet.h b/include/libssh/packet.h index 561bba8e..f0c8cb20 100644 --- a/include/libssh/packet.h +++ b/include/libssh/packet.h @@ -51,6 +51,10 @@ enum ssh_packet_filter_result_e { int ssh_packet_send(ssh_session session); +#ifdef __cplusplus +extern "C" { +#endif + SSH_PACKET_CALLBACK(ssh_packet_unimplemented); SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback); SSH_PACKET_CALLBACK(ssh_packet_ignore_callback); @@ -63,11 +67,12 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info); SSH_PACKET_CALLBACK(ssh_packet_kexdh_init); #endif +int ssh_packet_send_newkeys(ssh_session session); 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); +size_t 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); @@ -80,7 +85,7 @@ int ssh_packet_decrypt(ssh_session session, uint8_t *destination, uint8_t *sourc size_t start, size_t encrypted_size); unsigned char *ssh_packet_encrypt(ssh_session session, void *packet, - uint32_t len); + size_t 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, @@ -88,4 +93,8 @@ int ssh_packet_set_newkeys(ssh_session session, struct ssh_crypto_struct *ssh_packet_get_current_crypto(ssh_session session, enum ssh_crypto_direction_e direction); +#ifdef __cplusplus +} +#endif + #endif /* PACKET_H_ */ diff --git a/include/libssh/pcap.h b/include/libssh/pcap.h index 2e43ae87..2a6c3c27 100644 --- a/include/libssh/pcap.h +++ b/include/libssh/pcap.h @@ -27,6 +27,10 @@ #ifdef WITH_PCAP typedef struct ssh_pcap_context_struct* ssh_pcap_context; +#ifdef __cplusplus +extern "C" { +#endif + 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); @@ -41,5 +45,9 @@ int ssh_pcap_context_write(ssh_pcap_context,enum ssh_pcap_direction direction, v uint32_t len, uint32_t origlen); +#ifdef __cplusplus +} +#endif + #endif /* WITH_PCAP */ #endif /* PCAP_H_ */ diff --git a/include/libssh/pki.h b/include/libssh/pki.h index 6357b0b6..efb9bdbf 100644 --- a/include/libssh/pki.h +++ b/include/libssh/pki.h @@ -33,8 +33,8 @@ #include <openssl/evp.h> #endif #include "libssh/crypto.h" -#ifdef HAVE_OPENSSL_ED25519 -/* If using OpenSSL implementation, define the signature lenght which would be +#ifdef HAVE_LIBCRYPTO +/* If using OpenSSL implementation, define the signature length which would be * defined in libssh/ed25519.h otherwise */ #define ED25519_SIG_LEN 64 #else @@ -57,32 +57,24 @@ struct ssh_key_struct { 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; - EVP_PKEY *key; /* Saving the OpenSSL context here to save time while converting*/ -# if defined(HAVE_OPENSSL_ECC) - EC_KEY *ecdsa; -# else - void *ecdsa; -# endif /* HAVE_OPENSSL_EC_H */ -#endif /* HAVE_LIBGCRYPT */ -#ifdef HAVE_OPENSSL_ED25519 + /* This holds either ENGINE key for PKCS#11 support or just key in + * high-level format */ + EVP_PKEY *key; uint8_t *ed25519_pubkey; uint8_t *ed25519_privkey; -#else +#endif /* HAVE_LIBGCRYPT */ +#ifndef HAVE_LIBCRYPTO ed25519_pubkey *ed25519_pubkey; ed25519_privkey *ed25519_privkey; -#endif +#endif /* HAVE_LIBCRYPTO */ ssh_string sk_application; - void *cert; + ssh_buffer cert; enum ssh_keytypes_e cert_type; }; @@ -91,16 +83,15 @@ struct ssh_signature_struct { 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 +#ifndef HAVE_LIBCRYPTO ed25519_signature *ed25519_sig; -#endif +#endif /* HAVE_LIBGCRYPT */ ssh_string raw_sig; /* Security Key specific additions */ @@ -110,8 +101,11 @@ struct ssh_signature_struct { typedef struct ssh_signature_struct *ssh_signature; +#ifdef __cplusplus +extern "C" { +#endif + /* SSH Key Functions */ -ssh_key ssh_key_dup(const ssh_key key); void ssh_key_clean (ssh_key key); const char * @@ -127,10 +121,11 @@ enum ssh_digest_e ssh_key_hash_from_name(const char *name); ((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)) + ((kt) == SSH_KEYTYPE_RSA_CERT01 ||\ + (kt) == SSH_KEYTYPE_SK_ECDSA_CERT01 ||\ + (kt) == SSH_KEYTYPE_SK_ED25519_CERT01 ||\ + ((kt) >= SSH_KEYTYPE_ECDSA_P256_CERT01 &&\ + (kt) <= SSH_KEYTYPE_ED25519_CERT01)) /* SSH Signature Functions */ ssh_signature ssh_signature_new(void); @@ -158,6 +153,10 @@ int ssh_pki_import_pubkey_blob(const ssh_string key_blob, int ssh_pki_import_cert_blob(const ssh_string cert_blob, ssh_key *pkey); +/* SSH Private Key Functions */ +int ssh_pki_export_privkey_blob(const ssh_key key, + ssh_string *pblob); + /* SSH Signing Functions */ ssh_string ssh_pki_do_sign(ssh_session session, ssh_buffer sigbuf, @@ -174,9 +173,19 @@ 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); +bool ssh_key_size_allowed(ssh_session session, ssh_key key); + +/* Return the key size in bits */ +int ssh_key_size(ssh_key key); /* PKCS11 URI function to check if filename is a path or a PKCS11 URI */ +#ifdef WITH_PKCS11_URI bool ssh_pki_is_uri(const char *filename); char *ssh_pki_export_pub_uri_from_priv_uri(const char *priv_uri); +#endif /* WITH_PKCS11_URI */ + +#ifdef __cplusplus +} +#endif #endif /* PKI_H_ */ diff --git a/include/libssh/pki_priv.h b/include/libssh/pki_priv.h index 71418fdc..2061ebd7 100644 --- a/include/libssh/pki_priv.h +++ b/include/libssh/pki_priv.h @@ -23,6 +23,10 @@ #include "libssh/pki.h" +#ifdef __cplusplus +extern "C" { +#endif + /* defined in bcrypt_pbkdf.c */ int bcrypt_pbkdf(const char *pass, size_t passlen, @@ -34,8 +38,6 @@ int bcrypt_pbkdf(const char *pass, #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-----" @@ -49,6 +51,8 @@ enum ssh_key_e { SSH_KEY_PRIVATE }; +void pki_key_clean(ssh_key key); + 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, @@ -59,7 +63,6 @@ enum ssh_digest_e ssh_key_type_to_hash(ssh_session session, /* 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); @@ -85,24 +88,13 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, 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_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type); /* 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, @@ -114,7 +106,6 @@ 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, @@ -140,15 +131,18 @@ ssh_signature pki_do_sign_hash(const ssh_key privkey, const unsigned char *hash, size_t hlen, enum ssh_digest_e hash_type); +#ifndef HAVE_LIBCRYPTO 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); +#endif /* HAVE_LIBCRYPTO */ 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_key_dup(ssh_key new_key, const ssh_key key); int pki_ed25519_public_key_to_blob(ssh_buffer buffer, ssh_key key); +int pki_ed25519_private_key_to_blob(ssh_buffer buffer, const ssh_key privkey); 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, @@ -162,7 +156,14 @@ ssh_key ssh_pki_openssh_privkey_import(const char *text_key, ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, const char *passphrase, ssh_auth_callback auth_fn, void *auth_data); +#ifdef WITH_PKCS11_URI /* URI Function */ int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type); +#endif /* WITH_PKCS11_URI */ + +bool ssh_key_size_allowed_rsa(int min_size, ssh_key key); +#ifdef __cplusplus +} +#endif #endif /* PKI_PRIV_H_ */ diff --git a/include/libssh/poll.h b/include/libssh/poll.h index 3aa9a49b..8e30676e 100644 --- a/include/libssh/poll.h +++ b/include/libssh/poll.h @@ -114,6 +114,10 @@ typedef unsigned long int nfds_t; #endif /* WIN32 */ #endif /* HAVE_POLL */ +#ifdef __cplusplus +extern "C" { +#endif + void ssh_poll_init(void); void ssh_poll_cleanup(void); int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout); @@ -158,4 +162,8 @@ 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); +#ifdef __cplusplus +} +#endif + #endif /* POLL_H_ */ diff --git a/include/libssh/poly1305.h b/include/libssh/poly1305.h index 513f1b99..a22fea87 100644 --- a/include/libssh/poly1305.h +++ b/include/libssh/poly1305.h @@ -7,6 +7,10 @@ #define POLY1305_H #include "libssh/chacha20-poly1305-common.h" +#ifdef __cplusplus +extern "C" { +#endif + 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 @@ -16,4 +20,8 @@ void poly1305_auth(uint8_t out[POLY1305_TAGLEN], const uint8_t *m, size_t inlen, #endif ; +#ifdef __cplusplus +} +#endif + #endif /* POLY1305_H */ diff --git a/include/libssh/priv.h b/include/libssh/priv.h index b4729bb8..bfef771d 100644 --- a/include/libssh/priv.h +++ b/include/libssh/priv.h @@ -47,6 +47,14 @@ # endif #endif /* !defined(HAVE_STRTOULL) */ +#ifdef HAVE_TERMIOS_H +#include <termios.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif + #if !defined(HAVE_STRNDUP) char *strndup(const char *s, size_t n); #endif /* ! HAVE_STRNDUP */ @@ -152,10 +160,26 @@ char *strndup(const char *s, size_t n); # endif /* _MSC_VER */ struct timeval; -int gettimeofday(struct timeval *__p, void *__t); +int ssh_gettimeofday(struct timeval *__p, void *__t); + +#define gettimeofday ssh_gettimeofday #define _XCLOSESOCKET closesocket +# ifdef HAVE_IO_H +# include <io.h> +# undef open +# define open _open +# undef close +# define close _close +# undef read +# define read _read +# undef write +# define write _write +# undef unlink +# define unlink _unlink +# endif /* HAVE_IO_H */ + #else /* _WIN32 */ #include <unistd.h> @@ -284,6 +308,7 @@ int ssh_auth_reply_success(ssh_session session, int partial); /* client.c */ int ssh_send_banner(ssh_session session, int is_server); +void ssh_session_socket_close(ssh_session session); /* connect.c */ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, @@ -299,7 +324,7 @@ 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); + size_t len, int dolower); int match_hostname(const char *host, const char *pattern, unsigned int len); /* connector.c */ @@ -429,4 +454,15 @@ void ssh_agent_state_free(void *data); bool is_ssh_initialized(void); +#define SSH_ERRNO_MSG_MAX 1024 +char *ssh_strerror(int err_num, char *buf, size_t buflen); + +/** 55 defined options (5 bytes each) + terminator */ +#define SSH_TTY_MODES_MAX_BUFSIZE (55 * 5 + 1) +int encode_current_tty_opts(unsigned char *buf, size_t buflen); + +#ifdef __cplusplus +} +#endif + #endif /* _LIBSSH_PRIV_H */ diff --git a/include/libssh/sc25519.h b/include/libssh/sc25519.h index 5a2c1b85..43b09a05 100644 --- a/include/libssh/sc25519.h +++ b/include/libssh/sc25519.h @@ -35,6 +35,10 @@ typedef struct { uint32_t v[16]; } shortsc25519; +#ifdef __cplusplus +extern "C" { +#endif + void sc25519_from32bytes(sc25519 *r, const unsigned char x[32]); void shortsc25519_from16bytes(shortsc25519 *r, const unsigned char x[16]); @@ -71,4 +75,8 @@ void sc25519_window5(signed char r[51], const sc25519 *s); void sc25519_2interleave2(unsigned char r[127], const sc25519 *s1, const sc25519 *s2); +#ifdef __cplusplus +} +#endif + #endif diff --git a/include/libssh/scp.h b/include/libssh/scp.h index d356d89b..089fcfc9 100644 --- a/include/libssh/scp.h +++ b/include/libssh/scp.h @@ -47,9 +47,17 @@ struct ssh_scp_struct { int request_mode; }; +#ifdef __cplusplus +extern "C" { +#endif + 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); +#ifdef __cplusplus +} +#endif + #endif diff --git a/include/libssh/server.h b/include/libssh/server.h index 1c18b38c..885ef576 100644 --- a/include/libssh/server.h +++ b/include/libssh/server.h @@ -40,12 +40,12 @@ enum ssh_bind_options_e { SSH_BIND_OPTIONS_BINDPORT, SSH_BIND_OPTIONS_BINDPORT_STR, SSH_BIND_OPTIONS_HOSTKEY, - SSH_BIND_OPTIONS_DSAKEY, - SSH_BIND_OPTIONS_RSAKEY, + SSH_BIND_OPTIONS_DSAKEY, /* deprecated */ + SSH_BIND_OPTIONS_RSAKEY, /* deprecated */ SSH_BIND_OPTIONS_BANNER, SSH_BIND_OPTIONS_LOG_VERBOSITY, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, - SSH_BIND_OPTIONS_ECDSAKEY, + SSH_BIND_OPTIONS_ECDSAKEY, /* deprecated */ SSH_BIND_OPTIONS_IMPORT_KEY, SSH_BIND_OPTIONS_KEY_EXCHANGE, SSH_BIND_OPTIONS_CIPHERS_C_S, @@ -57,6 +57,7 @@ enum ssh_bind_options_e { SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, SSH_BIND_OPTIONS_PROCESS_CONFIG, SSH_BIND_OPTIONS_MODULI, + SSH_BIND_OPTIONS_RSA_MIN_SIZE, }; typedef struct ssh_bind_struct* ssh_bind; @@ -116,7 +117,7 @@ LIBSSH_API int ssh_bind_listen(ssh_bind ssh_bind_o); * * @param[in] userdata A pointer to private data to pass to the callbacks. * - * @return SSH_OK on success, SSH_ERROR if an error occured. + * @return SSH_OK on success, SSH_ERROR if an error occurred. * * @code * struct ssh_callbacks_struct cb = { @@ -221,6 +222,9 @@ LIBSSH_API int ssh_server_init_kex(ssh_session session); /** * @brief Free a ssh servers bind. * + * Note that this will also free options that have been set on the bind, + * including keys set with SSH_BIND_OPTIONS_IMPORT_KEY. + * * @param ssh_bind_o The ssh server bind to free. */ LIBSSH_API void ssh_bind_free(ssh_bind ssh_bind_o); @@ -244,6 +248,18 @@ LIBSSH_API void ssh_bind_free(ssh_bind ssh_bind_o); */ LIBSSH_API void ssh_set_auth_methods(ssh_session session, int auth_methods); +/** + * @brief Send the server's issue-banner to client. + * + * + * @param[in] session The server session. + * + * @param[in] banner The server's banner. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +LIBSSH_API int ssh_send_issue_banner(ssh_session session, const ssh_string banner); + /********************************************************** * SERVER MESSAGING **********************************************************/ @@ -267,7 +283,7 @@ LIBSSH_API int ssh_message_reply_default(ssh_message msg); * * @param[in] msg The message to get the username from. * - * @return The username or NULL if an error occured. + * @return The username or NULL if an error occurred. * * @see ssh_message_get() * @see ssh_message_type() @@ -279,12 +295,14 @@ LIBSSH_API const char *ssh_message_auth_user(ssh_message msg); * * @param[in] msg The message to get the password from. * - * @return The username or NULL if an error occured. + * @return The password or NULL if an error occurred. * * @see ssh_message_get() * @see ssh_message_type() + * @deprecated This function should not be used anymore as there is a + * callback based server implementation now auth_password_function. */ -LIBSSH_API const char *ssh_message_auth_password(ssh_message msg); +SSH_DEPRECATED LIBSSH_API const char *ssh_message_auth_password(ssh_message msg); /** * @brief Get the publickey of the authenticated user. @@ -299,11 +317,21 @@ LIBSSH_API const char *ssh_message_auth_password(ssh_message msg); * @see ssh_key_cmp() * @see ssh_message_get() * @see ssh_message_type() + * @deprecated This function should not be used anymore as there is a + * callback based server implementation auth_pubkey_function. */ -LIBSSH_API ssh_key ssh_message_auth_pubkey(ssh_message msg); +SSH_DEPRECATED 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); + +/** + * @param[in] msg The message to get the public key state from. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation auth_pubkey_function + */ +SSH_DEPRECATED 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); @@ -332,11 +360,12 @@ 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); +/* Replaced by callback based server implementation function channel_pty_request_function*/ +SSH_DEPRECATED LIBSSH_API const char *ssh_message_channel_request_pty_term(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_width(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_height(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_pxwidth(ssh_message msg); +SSH_DEPRECATED 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); @@ -345,17 +374,18 @@ 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); +/* Replaced by callback based server implementation function channel_open_request_x11_function*/ +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_x11_single_connection(ssh_message msg); +SSH_DEPRECATED LIBSSH_API const char *ssh_message_channel_request_x11_auth_protocol(ssh_message msg); +SSH_DEPRECATED LIBSSH_API const char *ssh_message_channel_request_x11_auth_cookie(ssh_message msg); +SSH_DEPRECATED 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, +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, diff --git a/include/libssh/session.h b/include/libssh/session.h index ddd52fd6..27da7a83 100644 --- a/include/libssh/session.h +++ b/include/libssh/session.h @@ -23,6 +23,7 @@ #include <stdbool.h> #include "libssh/priv.h" +#include "libssh/callbacks.h" #include "libssh/kex.h" #include "libssh/packet.h" #include "libssh/pcap.h" @@ -70,10 +71,24 @@ enum ssh_pending_call_e { }; /* libssh calls may block an undefined amount of time */ -#define SSH_SESSION_FLAG_BLOCKING 1 +#define SSH_SESSION_FLAG_BLOCKING 0x0001 /* Client successfully authenticated */ -#define SSH_SESSION_FLAG_AUTHENTICATED 2 +#define SSH_SESSION_FLAG_AUTHENTICATED 0x0002 + +/* Do not accept new session channels (no-more-sessions@openssh.com) */ +#define SSH_SESSION_FLAG_NO_MORE_SESSIONS 0x0004 + +/* The KEXINIT message can be sent first by either of the parties so this flag + * indicates that the message was already sent to make sure it is sent and avoid + * sending it twice during key exchange to simplify the state machine. */ +#define SSH_SESSION_FLAG_KEXINIT_SENT 0x0008 + +/* The current SSH2 session implements the "strict KEX" feature and should behave + * differently on SSH2_MSG_NEWKEYS. */ +#define SSH_SESSION_FLAG_KEX_STRICT 0x0010 +/* Unexpected packets have been sent while the session was still unencrypted */ +#define SSH_SESSION_FLAG_KEX_TAINTED 0x0020 /* codes to use with ssh_handle_packets*() */ /* Infinite timeout */ @@ -92,6 +107,13 @@ enum ssh_pending_call_e { #define SSH_OPT_FLAG_KBDINT_AUTH 0x4 #define SSH_OPT_FLAG_GSSAPI_AUTH 0x8 +/* Escape expansion of different variables */ +#define SSH_OPT_EXP_FLAG_KNOWNHOSTS 0x1 +#define SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS 0x2 +#define SSH_OPT_EXP_FLAG_PROXYCOMMAND 0x4 +#define SSH_OPT_EXP_FLAG_IDENTITY 0x8 +#define SSH_OPT_EXP_FLAG_CONTROL_PATH 0x10 + /* extensions flags */ /* negotiation enabled */ #define SSH_EXT_NEGOTIATION 0x01 @@ -131,10 +153,9 @@ struct ssh_session_struct { /* 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_string banner; /* that's the issue banner from the server */ + char *peer_discon_msg; /* disconnect message from the remote host */ + char *disconnect_message; /* disconnect message to be set */ ssh_buffer in_buffer; PACKET in_packet; ssh_buffer out_buffer; @@ -158,32 +179,39 @@ struct ssh_session_struct { uint32_t current_method; } auth; + /* Sending this flag before key exchange to save one round trip during the + * key exchange. This might make sense on high-latency connections. + * So far internal only for testing. Usable only on the client side -- + * there is no key exchange method that would start with server message */ + bool send_first_kex_follows; /* * 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. + * be ignored on the receiving side. Once that packet has been received and + * ignored, this field is cleared. + * On the sending side, this is set after we got peer KEXINIT message and we + * need to resend the initial message of the negotiated KEX algorithm. */ - int first_kex_follows_guess_wrong; + bool 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 */ + /* next_crypto is going to be used after a SSH2_MSG_NEWKEYS */ + struct ssh_crypto_struct *next_crypto; struct ssh_list *channels; /* linked list of channels */ - int maxchannel; + uint32_t maxchannel; ssh_agent agent; /* ssh agent */ -/* keyb interactive data */ + /* keyboard 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 */ @@ -193,7 +221,8 @@ struct ssh_session_struct { /* 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); + 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); @@ -207,6 +236,9 @@ struct ssh_session_struct { #endif struct { struct ssh_list *identity; + struct ssh_list *identity_non_exp; + struct ssh_list *certificate; + struct ssh_list *certificate_non_exp; char *username; char *host; char *bindaddr; /* bind the client to an ip addr */ @@ -218,9 +250,10 @@ struct ssh_session_struct { char *ProxyCommand; char *custombanner; char *moduli_file; + char *agent_socket; unsigned long timeout; /* seconds */ unsigned long timeout_usec; - unsigned int port; + uint16_t port; socket_t fd; int StrictHostKeyChecking; char compressionlevel; @@ -228,11 +261,16 @@ struct ssh_session_struct { char *gss_client_identity; int gss_delegate_creds; int flags; + int exp_flags; int nodelay; bool config_processed; uint8_t options_seen[SOC_MAX]; uint64_t rekey_data; uint32_t rekey_time; + int rsa_min_size; + bool identities_only; + int control_master; + char *control_path; } opts; /* counters */ ssh_counter socket_counter; @@ -247,7 +285,7 @@ struct ssh_session_struct { 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, + int timeout, ssh_termination_function fct, void *user); void ssh_socket_exception_callback(int code, int errno_code, void *user); diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h index c855df8a..754a54c3 100644 --- a/include/libssh/sftp.h +++ b/include/libssh/sftp.h @@ -77,6 +77,8 @@ 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; +typedef struct sftp_limits_struct* sftp_limits_t; +typedef struct sftp_aio_struct* sftp_aio; struct sftp_session_struct { ssh_session session; @@ -90,6 +92,7 @@ struct sftp_session_struct { void **handles; sftp_ext ext; sftp_packet read_packet; + sftp_limits_t limits; }; struct sftp_packet_struct { @@ -201,6 +204,16 @@ struct sftp_statvfs_struct { }; /** + * @brief SFTP limits structure. + */ +struct sftp_limits_struct { + uint64_t max_packet_length; /** maximum number of bytes in a single sftp packet */ + uint64_t max_read_length; /** maximum length in a SSH_FXP_READ packet */ + uint64_t max_write_length; /** maximum length in a SSH_FXP_WRITE packet */ + uint64_t max_open_handles; /** maximum number of active handles allowed by server */ +}; + +/** * @brief Creates a new sftp session. * * This function creates a new sftp session and allocates a new sftp channel @@ -476,13 +489,18 @@ LIBSSH_API void sftp_file_set_blocking(sftp_file handle); /** * @brief Read from a file using an opened sftp file handle. * + * This function caps the length a user is allowed to read from an sftp file. + * + * The value used for the cap is same as the value of the max_read_length + * field of the sftp_limits_t returned by sftp_limits(). + * * @param file The opened sftp file handle to be read from. * * @param buf Pointer to buffer to receive read data. * * @param count Size of the buffer in bytes. * - * @return Number of bytes written, < 0 on error with ssh and sftp + * @return Number of bytes read, < 0 on error with ssh and sftp * error set. * * @see sftp_get_error() @@ -520,7 +538,8 @@ LIBSSH_API ssize_t sftp_read(sftp_file file, void *buf, size_t count); * @see sftp_async_read() * @see sftp_open() */ -LIBSSH_API int sftp_async_read_begin(sftp_file file, uint32_t len); +SSH_DEPRECATED 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. @@ -545,11 +564,19 @@ LIBSSH_API int sftp_async_read_begin(sftp_file file, uint32_t len); * * @see sftp_async_read_begin() */ -LIBSSH_API int sftp_async_read(sftp_file file, void *data, uint32_t len, uint32_t id); +SSH_DEPRECATED 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. * + * This function caps the length a user is allowed to write to an sftp file. + * + * The value used for the cap is same as the value of the max_write_length + * field of the sftp_limits_t returned by sftp_limits(). + * * @param file Open sftp file handle to write to. * * @param buf Pointer to buffer to write data. @@ -566,6 +593,229 @@ LIBSSH_API int sftp_async_read(sftp_file file, void *data, uint32_t len, uint32_ LIBSSH_API ssize_t sftp_write(sftp_file file, const void *buf, size_t count); /** + * @brief Deallocate memory corresponding to a sftp aio handle. + * + * This function deallocates memory corresponding to the aio handle returned + * by the sftp_aio_begin_*() functions. Users can use this function to free + * memory corresponding to an aio handle for an outstanding async i/o request + * on encountering some error. + * + * @param aio sftp aio handle corresponding to which memory has + * to be deallocated. + * + * @see sftp_aio_begin_read() + * @see sftp_aio_wait_read() + * @see sftp_aio_begin_write() + * @see sftp_aio_wait_write() + */ +LIBSSH_API void sftp_aio_free(sftp_aio aio); +#define SFTP_AIO_FREE(x) \ + do { if(x != NULL) {sftp_aio_free(x); x = NULL;} } while(0) + +/** + * @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_aio_begin_read() and sftp_aio_wait_read(). + * + * - The first step is to call sftp_aio_begin_read(). This function sends a + * read request to the sftp server, dynamically allocates memory to store + * information about the sent request and provides the caller an sftp aio + * handle to that memory. + * + * - The second step is to call sftp_aio_wait_read() and pass it the address + * of a location storing the sftp aio handle provided by + * sftp_aio_begin_read(). + * + * These two functions do not close the open sftp file handle passed to + * sftp_aio_begin_read() irrespective of whether they fail or not. + * + * It is the responsibility of the caller to ensure that the open sftp file + * handle passed to sftp_aio_begin_read() must not be closed before the + * corresponding call to sftp_aio_wait_read(). After sftp_aio_wait_read() + * returns, it is caller's decision whether to immediately close the file by + * calling sftp_close() or to keep it open and perform some more operations + * on it. + * + * This function caps the length a user is allowed to read from an sftp file, + * the value of len parameter after capping is returned on success. + * + * The value used for the cap is same as the value of the max_read_length + * field of the sftp_limits_t returned by sftp_limits(). + * + * @param file The opened sftp file handle to be read from. + * + * @param len Number of bytes to read. + * + * @param aio Pointer to a location where the sftp aio handle + * (corresponding to the sent request) should be stored. + * + * @returns On success, the number of bytes the server is + * requested to read (value of len parameter after + * capping). On error, SSH_ERROR with sftp and ssh + * errors set. + * + * @warning When calling this function, the internal file offset is + * updated corresponding to the number of bytes requested + * to read. + * + * @warning A call to sftp_aio_begin_read() sends a request to + * the server. When the server answers, libssh allocates + * memory to store it until sftp_aio_wait_read() is called. + * Not calling sftp_aio_wait_read() will lead to memory + * leaks. + * + * @see sftp_aio_wait_read() + * @see sftp_aio_free() + * @see sftp_open() + * @see sftp_close() + * @see sftp_get_error() + * @see ssh_get_error() + */ +LIBSSH_API ssize_t sftp_aio_begin_read(sftp_file file, + size_t len, + sftp_aio *aio); + +/** + * @brief Wait for an asynchronous read to complete and store the read data + * in the supplied buffer. + * + * A pointer to an sftp aio handle should be passed while calling + * this function. Except when the return value is SSH_AGAIN, + * this function releases the memory corresponding to the supplied + * aio handle and assigns NULL to that aio handle using the passed + * pointer to that handle. + * + * If the file is opened in non-blocking mode and the request hasn't been + * executed yet, this function returns SSH_AGAIN and must be called again + * using the same sftp aio handle. + * + * @param aio Pointer to the sftp aio handle returned by + * sftp_aio_begin_read(). + * + * @param buf Pointer to the buffer in which read data will be stored. + * + * @param buf_size Size of the buffer in bytes. It should be bigger or + * equal to the length parameter of the + * sftp_aio_begin_read() call. + * + * @return Number of bytes read, 0 on EOF, SSH_ERROR if an error + * occurred, 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 sftp aio handle + * may never return. + * + * @see sftp_aio_begin_read() + * @see sftp_aio_free() + */ +LIBSSH_API ssize_t sftp_aio_wait_read(sftp_aio *aio, + void *buf, + size_t buf_size); + +/** + * @brief Start an asynchronous write to a file using an opened sftp + * file handle. + * + * Its goal is to avoid the slowdowns related to the request/response pattern + * of a synchronous write. To do so, you must call 2 functions : + * + * sftp_aio_begin_write() and sftp_aio_wait_write(). + * + * - The first step is to call sftp_aio_begin_write(). This function sends a + * write request to the sftp server, dynamically allocates memory to store + * information about the sent request and provides the caller an sftp aio + * handle to that memory. + * + * - The second step is to call sftp_aio_wait_write() and pass it the address + * of a location storing the sftp aio handle provided by + * sftp_aio_begin_write(). + * + * These two functions do not close the open sftp file handle passed to + * sftp_aio_begin_write() irrespective of whether they fail or not. + * + * It is the responsibility of the caller to ensure that the open sftp file + * handle passed to sftp_aio_begin_write() must not be closed before the + * corresponding call to sftp_aio_wait_write(). After sftp_aio_wait_write() + * returns, it is caller's decision whether to immediately close the file by + * calling sftp_close() or to keep it open and perform some more operations + * on it. + * + * This function caps the length a user is allowed to write to an sftp file, + * the value of len parameter after capping is returned on success. + * + * The value used for the cap is same as the value of the max_write_length + * field of the sftp_limits_t returned by sftp_limits(). + * + * @param file The opened sftp file handle to write to. + * + * @param buf Pointer to the buffer containing data to write. + * + * @param len Number of bytes to write. + * + * @param aio Pointer to a location where the sftp aio handle + * (corresponding to the sent request) should be stored. + * + * @returns On success, the number of bytes the server is + * requested to write (value of len parameter after + * capping). On error, SSH_ERROR with sftp and ssh errors + * set. + * + * @warning When calling this function, the internal file offset is + * updated corresponding to the number of bytes requested + * to write. + * + * @warning A call to sftp_aio_begin_write() sends a request to + * the server. When the server answers, libssh allocates + * memory to store it until sftp_aio_wait_write() is + * called. Not calling sftp_aio_wait_write() will lead to + * memory leaks. + * + * @see sftp_aio_wait_write() + * @see sftp_aio_free() + * @see sftp_open() + * @see sftp_close() + * @see sftp_get_error() + * @see ssh_get_error() + */ +LIBSSH_API ssize_t sftp_aio_begin_write(sftp_file file, + const void *buf, + size_t len, + sftp_aio *aio); + +/** + * @brief Wait for an asynchronous write to complete. + * + * A pointer to an sftp aio handle should be passed while calling + * this function. Except when the return value is SSH_AGAIN, + * this function releases the memory corresponding to the supplied + * aio handle and assigns NULL to that aio handle using the passed + * pointer to that handle. + * + * If the file is opened in non-blocking mode and the request hasn't + * been executed yet, this function returns SSH_AGAIN and must be called + * again using the same sftp aio handle. + * + * @param aio Pointer to the sftp aio handle returned by + * sftp_aio_begin_write(). + * + * @return Number of bytes written on success, SSH_ERROR + * if an error occurred, 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 sftp aio handle + * may never return. + * + * @see sftp_aio_begin_write() + * @see sftp_aio_free() + */ +LIBSSH_API ssize_t sftp_aio_wait_write(sftp_aio *aio); + +/** * @brief Seek to a specific location in a file. * * @param file Open sftp file handle to seek in. @@ -605,8 +855,7 @@ LIBSSH_API unsigned long sftp_tell(sftp_file 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. + * of the file associated with the file descriptor. */ LIBSSH_API uint64_t sftp_tell64(sftp_file file); @@ -681,6 +930,11 @@ LIBSSH_API int sftp_rename(sftp_session sftp, const char *original, const char /** * @brief Set file attributes on a file, directory or symbolic link. * + * Note, that this function can only set time values using 32 bit values due to + * the restrictions in the SFTP protocol version 3 implemented by libssh. + * The support for 64 bit time values was introduced in SFTP version 5, which is + * not implemented by libssh nor any major SFTP servers. + * * @param sftp The sftp session handle. * * @param file The file which attributes should be changed. @@ -767,12 +1021,30 @@ LIBSSH_API int sftp_symlink(sftp_session sftp, const char *target, const char *d * @param path Specifies the path name of the symlink to be read. * * @return The target of the link, NULL on error. + * The caller needs to free the memory + * using ssh_string_free_char(). * * @see sftp_get_error() */ LIBSSH_API char *sftp_readlink(sftp_session sftp, const char *path); /** + * @brief Create a hard link. + * + * @param sftp The sftp session handle. + * + * @param oldpath Specifies the pathname of the file for + * which the new hardlink is to be created. + * + * @param newpath Specifies the pathname of the hardlink to be created. + * + * @return 0 on success, -1 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_hardlink(sftp_session sftp, const char *oldpath, const char *newpath); + +/** * @brief Get information about a mounted file system. * * @param sftp The sftp session handle. @@ -820,6 +1092,24 @@ LIBSSH_API void sftp_statvfs_free(sftp_statvfs_t statvfs_o); LIBSSH_API int sftp_fsync(sftp_file file); /** + * @brief Get information about the various limits the server might impose. + * + * @param sftp The sftp session handle. + * + * @return A limits structure or NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_limits_t sftp_limits(sftp_session sftp); + +/** + * @brief Free the memory of an allocated limits. + * + * @param limits The limits to free. + */ +LIBSSH_API void sftp_limits_free(sftp_limits_t limits); + +/** * @brief Canonicalize a sftp path. * * @param sftp The sftp session handle. @@ -841,6 +1131,19 @@ LIBSSH_API char *sftp_canonicalize_path(sftp_session sftp, const char *path); */ LIBSSH_API int sftp_server_version(sftp_session sftp); +/** + * @brief Canonicalize path using expand-path@openssh.com extension + * + * @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_expand_path(sftp_session sftp, const char *path); + #ifdef WITH_SERVER /** * @brief Create a new sftp server session. @@ -860,7 +1163,7 @@ LIBSSH_API sftp_session sftp_server_new(ssh_session session, ssh_channel chan); * * @return 0 on success, < 0 on error. */ -LIBSSH_API int sftp_server_init(sftp_session sftp); +SSH_DEPRECATED LIBSSH_API int sftp_server_init(sftp_session sftp); /** * @brief Close and deallocate a sftp server session. diff --git a/include/libssh/sftp_priv.h b/include/libssh/sftp_priv.h index 83925191..8470a8ad 100644 --- a/include/libssh/sftp_priv.h +++ b/include/libssh/sftp_priv.h @@ -21,12 +21,63 @@ #ifndef SFTP_PRIV_H #define SFTP_PRIV_H +#ifdef __cplusplus +extern "C" { +#endif + sftp_packet sftp_packet_read(sftp_session sftp); -ssize_t sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload); +int 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); +/** + * @brief Reply to the SSH_FXP_INIT message with the SSH_FXP_VERSION message + * + * @param client_msg The pointer to client message. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +int sftp_reply_version(sftp_client_message client_msg); +/** + * @brief Decode the data from channel buffer into sftp read_packet. + * + * @param sftp The sftp session handle. + * + * @param data The pointer to the data buffer of channel. + * @param len The data buffer length + * + * @return Length of data decoded. + */ +int sftp_decode_channel_data_to_packet(sftp_session sftp, void *data, uint32_t len); + +void sftp_set_error(sftp_session sftp, int errnum); + +void sftp_message_free(sftp_message msg); + +int sftp_read_and_dispatch(sftp_session sftp); + +sftp_message sftp_dequeue(sftp_session sftp, uint32_t id); + +/* + * 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; +} + +sftp_status_message parse_status_msg(sftp_message msg); + +void status_msg_free(sftp_status_message status); + +#ifdef __cplusplus +} +#endif #endif /* SFTP_PRIV_H */ diff --git a/include/libssh/sftpserver.h b/include/libssh/sftpserver.h new file mode 100644 index 00000000..d7ed6e49 --- /dev/null +++ b/include/libssh/sftpserver.h @@ -0,0 +1,73 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2022 Zeyu Sheng <shengzeyu19_98@163.com> + * Copyright (c) 2023 Red Hat, Inc. + * + * Authors: Jakub Jelen <jjelen@redhat.com> + * + * 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_SERVER_H +#define SFTP_SERVER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> +/** + * @defgroup libssh_sftp_server The libssh SFTP server API + * + * @brief SFTP server handling functions + * + * TODO + * + * @{ + */ + +#define SSH_SFTP_CALLBACK(name) \ + static int name(sftp_client_message message) + +typedef int (*sftp_server_message_callback)(sftp_client_message message); + +struct sftp_message_handler +{ + const char *name; + const char *extended_name; + uint8_t type; + + sftp_server_message_callback cb; +}; + +LIBSSH_API int sftp_channel_default_subsystem_request(ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata); +LIBSSH_API int sftp_channel_default_data_callback(ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* SFTP_SERVER_H */ diff --git a/include/libssh/socket.h b/include/libssh/socket.h index 5e345c68..cdd3c837 100644 --- a/include/libssh/socket.h +++ b/include/libssh/socket.h @@ -35,13 +35,14 @@ 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 +void ssh_socket_set_connected(ssh_socket s, struct ssh_poll_handle_struct *p); int ssh_socket_unix(ssh_socket s, const char *path); void ssh_execute_command(const char *command, socket_t in, socket_t out); +#ifndef _WIN32 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_write(ssh_socket s,const void *buffer, uint32_t 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); diff --git a/include/libssh/string.h b/include/libssh/string.h index 8c7db1df..35b2ea2e 100644 --- a/include/libssh/string.h +++ b/include/libssh/string.h @@ -22,6 +22,10 @@ #define STRING_H_ #include "libssh/priv.h" +#ifdef __cplusplus +extern "C" { +#endif + /* must be 32 bits number + immediately our data */ #ifdef _MSC_VER #pragma pack(1) @@ -38,4 +42,8 @@ __attribute__ ((packed)) #endif ; +#ifdef __cplusplus +} +#endif + #endif /* STRING_H_ */ diff --git a/include/libssh/threads.h b/include/libssh/threads.h index 522f91d5..47340d17 100644 --- a/include/libssh/threads.h +++ b/include/libssh/threads.h @@ -49,6 +49,10 @@ #endif +#ifdef __cplusplus +extern "C" { +#endif + int ssh_threads_init(void); void ssh_threads_finalize(void); const char *ssh_threads_get_type(void); @@ -60,4 +64,8 @@ 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); +#ifdef __cplusplus +} +#endif + #endif /* THREADS_H_ */ diff --git a/include/libssh/token.h b/include/libssh/token.h index 9896fb06..550ad792 100644 --- a/include/libssh/token.h +++ b/include/libssh/token.h @@ -31,6 +31,10 @@ struct ssh_tokens_st { char **tokens; }; +#ifdef __cplusplus +extern "C" { +#endif + struct ssh_tokens_st *ssh_tokenize(const char *chain, char separator); void ssh_tokens_free(struct ssh_tokens_st *tokens); @@ -45,4 +49,13 @@ char *ssh_remove_duplicates(const char *list); char *ssh_append_without_duplicates(const char *list, const char *appended_list); +char *ssh_prefix_without_duplicates(const char *list, + const char *prefixed_list); +char *ssh_remove_all_matching(const char *list, + const char *remove_list); + +#ifdef __cplusplus +} +#endif + #endif /* TOKEN_H_ */ diff --git a/include/libssh/wrapper.h b/include/libssh/wrapper.h index df6544ee..b3e28eac 100644 --- a/include/libssh/wrapper.h +++ b/include/libssh/wrapper.h @@ -29,6 +29,10 @@ #include "libssh/libgcrypt.h" #include "libssh/libmbedcrypto.h" +#ifdef __cplusplus +extern "C" { +#endif + enum ssh_kdf_digest { SSH_KDF_SHA1=1, SSH_KDF_SHA256, @@ -68,42 +72,42 @@ 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); +void md5_ctx_free(MD5CTX); +int md5_update(MD5CTX c, const void *data, size_t len); +int 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); +void sha1_ctx_free(SHACTX); +int sha1_update(SHACTX c, const void *data, size_t len); +int sha1_final(unsigned char *md,SHACTX c); +int sha1(const unsigned char *digest,size_t 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); +void sha256_ctx_free(SHA256CTX); +int sha256_update(SHA256CTX c, const void *data, size_t len); +int sha256_final(unsigned char *md,SHA256CTX c); +int sha256(const unsigned char *digest, size_t 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); +void sha384_ctx_free(SHA384CTX); +int sha384_update(SHA384CTX c, const void *data, size_t len); +int sha384_final(unsigned char *md,SHA384CTX c); +int sha384(const unsigned char *digest, size_t 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); +void sha512_ctx_free(SHA512CTX); +int sha512_update(SHA512CTX c, const void *data, size_t len); +int sha512_final(unsigned char *md,SHA512CTX c); +int sha512(const unsigned char *digest, size_t len, unsigned char *hash); + +HMACCTX hmac_init(const void *key,size_t len, enum ssh_hmac_e type); +int hmac_update(HMACCTX c, const void *data, size_t len); +int hmac_final(HMACCTX ctx, unsigned char *hashmacbuf, size_t *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, + uint8_t key_type, unsigned char *output, size_t requested_len); int crypt_set_algorithms_client(ssh_session session); @@ -120,4 +124,15 @@ 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); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L +int evp_build_pkey(const char* name, OSSL_PARAM_BLD *param_bld, EVP_PKEY **pkey, int selection); +int evp_dup_dsa_pkey(const ssh_key key, ssh_key new_key, int demote); +int evp_dup_rsa_pkey(const ssh_key key, ssh_key new_key, int demote); +int evp_dup_ecdsa_pkey(const ssh_key key, ssh_key new_key, int demote); +#endif /* HAVE_LIBCRYPTO && OPENSSL_VERSION_NUMBER */ + +#ifdef __cplusplus +} +#endif + #endif /* WRAPPER_H_ */ diff --git a/libssh.pc.cmake b/libssh.pc.cmake index b37cb3fc..f288b94d 100644 --- a/libssh.pc.cmake +++ b/libssh.pc.cmake @@ -1,6 +1,10 @@ -Name: ${PROJECT_NAME} -Description: The SSH Library -Version: ${PROJECT_VERSION} -Libs: -L${CMAKE_INSTALL_FULL_LIBDIR} -lssh -Cflags: -I${CMAKE_INSTALL_FULL_INCLUDEDIR} +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ +Name: @PROJECT_NAME@ +Description: The SSH Library +Version: @PROJECT_VERSION@ +Libs: -L${libdir} -lssh +Cflags: -I${includedir} diff --git a/src/ABI/current b/src/ABI/current index 28715673..b617d997 100644 --- a/src/ABI/current +++ b/src/ABI/current @@ -1 +1 @@ -4.8.1
\ No newline at end of file +4.9.0
\ No newline at end of file diff --git a/src/ABI/libssh-4.9.0.symbols b/src/ABI/libssh-4.9.0.symbols new file mode 100644 index 00000000..a26e2c5e --- /dev/null +++ b/src/ABI/libssh-4.9.0.symbols @@ -0,0 +1,427 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char
\ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4f350d9d..93ecb5e7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,6 @@ set(LIBSSH_PUBLIC_INCLUDE_DIRS ${libssh_SOURCE_DIR}/include) set(LIBSSH_PRIVATE_INCLUDE_DIRS - ${libssh_BINARY_DIR}/include ${libssh_BINARY_DIR} ) @@ -9,24 +8,9 @@ set(LIBSSH_LINK_LIBRARIES ${LIBSSH_REQUIRED_LIBRARIES} ) -if (WIN32) - set(LIBSSH_LINK_LIBRARIES - ${LIBSSH_LINK_LIBRARIES} - ws2_32 - ) -endif (WIN32) - -if (OPENSSL_CRYPTO_LIBRARIES) - set(LIBSSH_PRIVATE_INCLUDE_DIRS - ${LIBSSH_PRIVATE_INCLUDE_DIRS} - ${OPENSSL_INCLUDE_DIR} - ) - - set(LIBSSH_LINK_LIBRARIES - ${LIBSSH_LINK_LIBRARIES} - ${OPENSSL_CRYPTO_LIBRARIES} - ) -endif (OPENSSL_CRYPTO_LIBRARIES) +if (TARGET OpenSSL::Crypto) + list(APPEND LIBSSH_LINK_LIBRARIES OpenSSL::Crypto) +endif () if (MBEDTLS_CRYPTO_LIBRARY) set(LIBSSH_PRIVATE_INCLUDE_DIRS @@ -51,15 +35,7 @@ if (GCRYPT_LIBRARIES) endif() if (WITH_ZLIB) - set(LIBSSH_PRIVATE_INCLUDE_DIRS - ${LIBSSH_PRIVATE_INCLUDE_DIRS} - ${ZLIB_INCLUDE_DIR} - ) - - set(LIBSSH_LINK_LIBRARIES - ${LIBSSH_LINK_LIBRARIES} - ${ZLIB_LIBRARY} - ) + list(APPEND LIBSSH_LINK_LIBRARIES ZLIB::ZLIB) endif (WITH_ZLIB) if (WITH_GSSAPI AND GSSAPI_FOUND) @@ -93,6 +69,16 @@ if (MINGW AND Threads_FOUND) ) endif() +# The ws2_32 needs to be last for mingw to build +# https://gitlab.com/libssh/libssh-mirror/-/issues/84 +if (WIN32) + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + iphlpapi + ws2_32 + ) +endif (WIN32) + if (BUILD_STATIC_LIB) set(LIBSSH_STATIC_LIBRARY ssh_static @@ -141,6 +127,7 @@ set(libssh_SRCS socket.c string.c threads.c + ttyopts.c wrapper.c external/bcrypt_pbkdf.c external/blowfish.c @@ -182,6 +169,8 @@ if (WITH_GCRYPT) gcrypt_missing.c pki_gcrypt.c ecdh_gcrypt.c + getrandom_gcrypt.c + md_gcrypt.c dh_key.c pki_ed25519.c external/ed25519.c @@ -205,6 +194,8 @@ elseif (WITH_MBEDTLS) mbedcrypto_missing.c pki_mbedcrypto.c ecdh_mbedcrypto.c + getrandom_mbedcrypto.c + md_mbedcrypto.c dh_key.c pki_ed25519.c external/ed25519.c @@ -227,36 +218,27 @@ else (WITH_GCRYPT) threads/libcrypto.c pki_crypto.c ecdh_crypto.c + getrandom_crypto.c + md_crypto.c libcrypto.c dh_crypto.c ) - if (NOT HAVE_OPENSSL_ED25519) - set(libssh_SRCS - ${libssh_SRCS} - pki_ed25519.c - external/ed25519.c - external/fe25519.c - external/ge25519.c - external/sc25519.c - ) - endif (NOT HAVE_OPENSSL_ED25519) - if (NOT (HAVE_OPENSSL_EVP_CHACHA20 AND HAVE_OPENSSL_EVP_POLY1305)) + if (NOT HAVE_OPENSSL_EVP_CHACHA20) set(libssh_SRCS ${libssh_SRCS} external/chacha.c external/poly1305.c chachapoly.c ) - endif (NOT (HAVE_OPENSSL_EVP_CHACHA20 AND HAVE_OPENSSL_EVP_POLY1305)) - if(OPENSSL_VERSION VERSION_LESS "1.1.0") - set(libssh_SRCS ${libssh_SRCS} libcrypto-compat.c) - endif() + endif (NOT HAVE_OPENSSL_EVP_CHACHA20) endif (WITH_GCRYPT) if (WITH_SFTP) set(libssh_SRCS ${libssh_SRCS} sftp.c + sftp_common.c + sftp_aio.c ) if (WITH_SERVER) @@ -298,12 +280,12 @@ if (WITH_GSSAPI AND GSSAPI_FOUND) endif (WITH_GSSAPI AND GSSAPI_FOUND) if (NOT WITH_NACL) - if (NOT HAVE_OPENSSL_ED25519) + if (NOT HAVE_LIBCRYPTO) set(libssh_SRCS ${libssh_SRCS} external/curve25519_ref.c ) - endif (NOT HAVE_OPENSSL_ED25519) + endif() endif (NOT WITH_NACL) # Set the path to the default map file @@ -342,11 +324,14 @@ endif (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT AND ABIMAP_FOUND) add_library(ssh ${libssh_SRCS}) target_compile_options(ssh PRIVATE - ${DEFAULT_C_COMPILE_FLAGS} - -D_GNU_SOURCE) + ${DEFAULT_C_COMPILE_FLAGS}) +if (CYGWIN) + target_compile_definitions(ssh PRIVATE _GNU_SOURCE) +endif () target_include_directories(ssh PUBLIC $<BUILD_INTERFACE:${libssh_SOURCE_DIR}/include> + $<BUILD_INTERFACE:${libssh_BINARY_DIR}/include> $<INSTALL_INTERFACE:include> PRIVATE ${LIBSSH_PRIVATE_INCLUDE_DIRS}) @@ -386,6 +371,10 @@ if (MINGW) target_link_libraries(ssh PRIVATE "-Wl,--enable-stdcall-fixup") target_compile_definitions(ssh PRIVATE "_POSIX_SOURCE") endif () +if (WITH_COVERAGE) + include(CodeCoverage) + append_coverage_compiler_flags_to_target(ssh) +endif (WITH_COVERAGE) install(TARGETS ssh @@ -402,12 +391,15 @@ if (BUILD_STATIC_LIB) add_library(ssh-static STATIC ${libssh_SRCS}) target_compile_options(ssh-static PRIVATE - ${DEFAULT_C_COMPILE_FLAGS} - -D_GNU_SOURCE) + ${DEFAULT_C_COMPILE_FLAGS}) + if (CYGWIN) + target_compile_definitions(ssh-static PRIVATE _GNU_SOURCE) + endif () target_include_directories(ssh-static PUBLIC $<BUILD_INTERFACE:${libssh_SOURCE_DIR}/include> + $<BUILD_INTERFACE:${libssh_BINARY_DIR}/include> $<INSTALL_INTERFACE:include> PRIVATE ${LIBSSH_PRIVATE_INCLUDE_DIRS}) target_link_libraries(ssh-static @@ -435,6 +427,9 @@ if (BUILD_STATIC_LIB) if (WIN32) target_compile_definitions(ssh-static PUBLIC "LIBSSH_STATIC") endif (WIN32) + if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(ssh-static) + endif (WITH_COVERAGE) endif (BUILD_STATIC_LIB) message(STATUS "Threads_FOUND=${Threads_FOUND}") diff --git a/src/agent.c b/src/agent.c index 3bc99a73..1c79c6eb 100644 --- a/src/agent.c +++ b/src/agent.c @@ -33,8 +33,6 @@ * the agent returns the signed data */ -#ifndef _WIN32 - #include "config.h" #include <stdlib.h> @@ -46,8 +44,14 @@ #include <unistd.h> #endif +#ifndef _WIN32 #include <netinet/in.h> #include <arpa/inet.h> +#include <sys/socket.h> +#else +#include <winsock2.h> +#include <ws2tcpip.h> +#endif #include "libssh/agent.h" #include "libssh/priv.h" @@ -63,9 +67,9 @@ (((x) == SSH_AGENT_FAILURE) || ((x) == SSH_COM_AGENT2_FAILURE) || \ ((x) == SSH2_AGENT_FAILURE)) -static size_t atomicio(struct ssh_agent_struct *agent, void *buf, size_t n, int do_read) { +static uint32_t atomicio(struct ssh_agent_struct *agent, void *buf, uint32_t n, int do_read) { char *b = buf; - size_t pos = 0; + uint32_t pos = 0; ssize_t res; ssh_pollfd_t pfd; ssh_channel channel = agent->channel; @@ -79,9 +83,9 @@ static size_t atomicio(struct ssh_agent_struct *agent, void *buf, size_t n, int while (n > pos) { if (do_read) { - res = read(fd, b + pos, n - pos); + res = recv(fd, b + pos, n - pos, 0); } else { - res = write(fd, b + pos, n - pos); + res = send(fd, b + pos, n - pos, 0); } switch (res) { case -1: @@ -102,7 +106,7 @@ static size_t atomicio(struct ssh_agent_struct *agent, void *buf, size_t n, int errno = do_read ? 0 : EPIPE; return pos; default: - pos += (size_t) res; + pos += (uint32_t) res; } } return pos; @@ -117,7 +121,7 @@ static size_t atomicio(struct ssh_agent_struct *agent, void *buf, size_t n, int continue; if (res == SSH_ERROR) return 0; - pos += (size_t)res; + pos += (uint32_t)res; } return pos; } @@ -146,11 +150,21 @@ static void agent_set_channel(struct ssh_agent_struct *agent, ssh_channel channe agent->channel = channel; } +/** + * @addtogroup libssh_auth + * + * @{ + */ + /** @brief sets the SSH agent channel. * The SSH agent channel will be used to authenticate this client using * an agent through a channel, from another session. The most likely use * is to implement SSH Agent forwarding into a SSH proxy. + * + * @param session the session + * * @param[in] channel a SSH channel from another session. + * * @returns SSH_OK in case of success * SSH_ERROR in case of an error */ @@ -185,6 +199,10 @@ int ssh_set_agent_socket(ssh_session session, socket_t fd){ return SSH_OK; } +/** + * @} + */ + void ssh_agent_close(struct ssh_agent_struct *agent) { if (agent == NULL) { return; @@ -216,7 +234,8 @@ static int agent_connect(ssh_session session) { if (session->agent->channel != NULL) return 0; - auth_sock = getenv("SSH_AUTH_SOCK"); + auth_sock = session->opts.agent_socket ? + session->opts.agent_socket : getenv("SSH_AUTH_SOCK"); if (auth_sock && *auth_sock) { if (ssh_socket_unix(session->agent->sock, auth_sock) < 0) { @@ -253,29 +272,30 @@ static int agent_talk(struct ssh_session_struct *session, uint32_t len = 0; uint8_t tmpbuf[4]; uint8_t *payload = tmpbuf; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; len = ssh_buffer_get_len(request); - SSH_LOG(SSH_LOG_TRACE, "Request length: %u", len); + SSH_LOG(SSH_LOG_TRACE, "Request length: %" PRIu32, len); PUSH_BE_U32(payload, 0, len); /* send length and then the request packet */ if (atomicio(session->agent, payload, 4, 0) == 4) { if (atomicio(session->agent, ssh_buffer_get(request), len, 0) != len) { - SSH_LOG(SSH_LOG_WARN, "atomicio sending request failed: %s", + SSH_LOG(SSH_LOG_TRACE, "atomicio sending request failed: %s", strerror(errno)); return -1; } } else { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "atomicio sending request length failed: %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return -1; } /* wait for response, read the length of the response packet */ if (atomicio(session->agent, payload, 4, 1) != 4) { - SSH_LOG(SSH_LOG_WARN, "atomicio read response length failed: %s", + SSH_LOG(SSH_LOG_TRACE, "atomicio read response length failed: %s", strerror(errno)); return -1; } @@ -283,19 +303,19 @@ static int agent_talk(struct ssh_session_struct *session, len = PULL_BE_U32(payload, 0); if (len > 256 * 1024) { ssh_set_error(session, SSH_FATAL, - "Authentication response too long: %u", len); + "Authentication response too long: %" PRIu32, len); return -1; } - SSH_LOG(SSH_LOG_TRACE, "Response length: %u", len); + SSH_LOG(SSH_LOG_TRACE, "Response length: %" PRIu32, len); payload = ssh_buffer_allocate(reply, len); if (payload == NULL) { - SSH_LOG(SSH_LOG_WARN, "Not enough space"); + SSH_LOG(SSH_LOG_DEBUG, "Not enough space"); return -1; } if (atomicio(session->agent, payload, len, 1) != len) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_DEBUG, "Error reading response from authentication socket."); /* Rollback the unused space */ ssh_buffer_pass_bytes_end(reply, len); @@ -311,7 +331,7 @@ uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session) ssh_buffer reply = NULL; unsigned int type = 0; uint32_t count = 0; - int rc; + uint32_t rc; /* send message to the agent requesting the list of identities */ request = ssh_buffer_new(); @@ -343,7 +363,7 @@ uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session) rc = ssh_buffer_get_u8(reply, (uint8_t *) &type); if (rc != sizeof(uint8_t)) { ssh_set_error(session, SSH_FATAL, - "Bad authentication reply size: %d", rc); + "Bad authentication reply size: %" PRIu32, rc); SSH_BUFFER_FREE(reply); return 0; } @@ -351,7 +371,7 @@ uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session) type = bswap_32(type); #endif - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Answer type: %d, expected answer: %d", type, SSH2_AGENT_IDENTITIES_ANSWER); @@ -384,15 +404,13 @@ uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session) return 0; } - if (session->agent->ident) { - ssh_buffer_reinit(session->agent->ident); - } + ssh_buffer_free(session->agent->ident); session->agent->ident = reply; return session->agent->count; } -/* caller has to free commment */ +/* caller has to free comment */ ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, char **comment) { if (ssh_agent_get_ident_count(session) > 0) { @@ -402,7 +420,7 @@ ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, return NULL; } -/* caller has to free commment */ +/* caller has to free comment */ ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session, char **comment) { struct ssh_key_struct *key; @@ -571,7 +589,7 @@ ssh_string ssh_agent_sign_data(ssh_session session, #endif if (agent_failed(type)) { - SSH_LOG(SSH_LOG_WARN, "Agent reports failure in signing the key"); + SSH_LOG(SSH_LOG_DEBUG, "Agent reports failure in signing the key"); SSH_BUFFER_FREE(reply); return NULL; } else if (type != SSH2_AGENT_SIGN_RESPONSE) { @@ -588,5 +606,3 @@ ssh_string ssh_agent_sign_data(ssh_session session, return sig_blob; } - -#endif /* _WIN32 */ @@ -47,7 +47,7 @@ #include "libssh/legacy.h" /** - * @defgroup libssh_auth The SSH authentication functions. + * @defgroup libssh_auth The SSH authentication functions * @ingroup libssh * * Functions to authenticate with a server. @@ -58,7 +58,7 @@ /** * @internal * - * @brief Ask access to the ssh-userauth service. + * @brief Ask for access to the ssh-userauth service. * * @param[in] session The SSH session handle. * @@ -66,19 +66,21 @@ * @returns SSH_AGAIN on nonblocking mode, if calling that function * again is necessary */ -static int ssh_userauth_request_service(ssh_session session) { +static int ssh_userauth_request_service(ssh_session session) +{ int rc; rc = ssh_service_request(session, "ssh-userauth"); if ((rc != SSH_OK) && (rc != SSH_AGAIN)) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Failed to request \"ssh-userauth\" service"); } return rc; } -static int ssh_auth_response_termination(void *user) { +static int ssh_auth_response_termination(void *user) +{ ssh_session session = (ssh_session)user; switch (session->auth.state) { case SSH_AUTH_STATE_NONE: @@ -139,7 +141,8 @@ static const char *ssh_auth_get_current_method(ssh_session session) * SSH_AUTH_AGAIN In nonblocking mode, call has to be made again * SSH_AUTH_ERROR Error during the process. */ -static int ssh_userauth_get_response(ssh_session session) { +static int ssh_userauth_get_response(ssh_session session) +{ int rc = SSH_AUTH_ERROR; rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, @@ -199,7 +202,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_banner) { banner = ssh_buffer_get_ssh_string(packet); if (banner == NULL) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Invalid SSH_USERAUTH_BANNER packet"); } else { SSH_LOG(SSH_LOG_DEBUG, @@ -237,7 +240,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_failure) { if (partial) { session->auth.state = SSH_AUTH_STATE_PARTIAL; - SSH_LOG(SSH_LOG_INFO, + SSH_LOG(SSH_LOG_DEBUG, "Partial success for '%s'. Authentication that can continue: %s", current_method, auth_methods); @@ -247,7 +250,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_failure) { "Access denied for '%s'. Authentication that can continue: %s", current_method, auth_methods); - SSH_LOG(SSH_LOG_INFO, + SSH_LOG(SSH_LOG_DEBUG, "%s", ssh_get_error(session)); @@ -364,7 +367,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok) { * * @param[in] username Deprecated, set to NULL. * - * @returns A bitfield of the fllowing values: + * @returns A bitfield of the following values: * - SSH_AUTH_METHOD_PASSWORD * - SSH_AUTH_METHOD_PUBLICKEY * - SSH_AUTH_METHOD_HOSTBASED @@ -403,10 +406,11 @@ int ssh_userauth_list(ssh_session session, const char *username) * authentication. The username should only be set with ssh_options_set() only * before you connect to the server. */ -int ssh_userauth_none(ssh_session session, const char *username) { +int ssh_userauth_none(ssh_session session, const char *username) +{ int rc; - switch(session->pending_call_state) { + switch (session->pending_call_state) { case SSH_PENDING_CALL_NONE: break; case SSH_PENDING_CALL_AUTH_NONE: @@ -492,6 +496,7 @@ int ssh_userauth_try_publickey(ssh_session session, { ssh_string pubkey_s = NULL; const char *sig_type_c = NULL; + bool allowed; int rc; if (session == NULL) { @@ -513,7 +518,7 @@ int ssh_userauth_try_publickey(ssh_session session, SSH_FATAL, "Wrong state (%d) during pending SSH call", session->pending_call_state); - return SSH_ERROR; + return SSH_AUTH_ERROR; } /* Check if the given public key algorithm is allowed */ @@ -523,13 +528,21 @@ int ssh_userauth_try_publickey(ssh_session session, "Invalid key type (unknown)"); return SSH_AUTH_DENIED; } - if (!ssh_key_algorithm_allowed(session, sig_type_c)) { + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { ssh_set_error(session, SSH_REQUEST_DENIED, "The key algorithm '%s' is not allowed to be used by" " PUBLICKEY_ACCEPTED_TYPES configuration option", sig_type_c); return SSH_AUTH_DENIED; } + allowed = ssh_key_size_allowed(session, pubkey); + if (!allowed) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", sig_type_c, ssh_key_size(pubkey)); + return SSH_AUTH_DENIED; + } rc = ssh_userauth_request_service(session); if (rc == SSH_AGAIN) { @@ -544,6 +557,7 @@ int ssh_userauth_try_publickey(ssh_session session, goto fail; } + SSH_LOG(SSH_LOG_TRACE, "Trying signature type %s", sig_type_c); /* request */ rc = ssh_buffer_pack(session->out_buffer, "bsssbsS", SSH2_MSG_USERAUTH_REQUEST, @@ -611,6 +625,7 @@ int ssh_userauth_publickey(ssh_session session, const ssh_key privkey) { ssh_string str = NULL; + bool allowed; int rc; const char *sig_type_c = NULL; enum ssh_keytypes_e key_type; @@ -647,13 +662,21 @@ int ssh_userauth_publickey(ssh_session session, "Invalid key type (unknown)"); return SSH_AUTH_DENIED; } - if (!ssh_key_algorithm_allowed(session, sig_type_c)) { + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { ssh_set_error(session, SSH_REQUEST_DENIED, "The key algorithm '%s' is not allowed to be used by" " PUBLICKEY_ACCEPTED_TYPES configuration option", sig_type_c); return SSH_AUTH_DENIED; } + allowed = ssh_key_size_allowed(session, privkey); + if (!allowed) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", sig_type_c, ssh_key_size(privkey)); + return SSH_AUTH_DENIED; + } rc = ssh_userauth_request_service(session); if (rc == SSH_AGAIN) { @@ -668,6 +691,7 @@ int ssh_userauth_publickey(ssh_session session, goto fail; } + SSH_LOG(SSH_LOG_TRACE, "Sending signature type %s", sig_type_c); /* request */ rc = ssh_buffer_pack(session->out_buffer, "bsssbsS", SSH2_MSG_USERAUTH_REQUEST, @@ -722,7 +746,6 @@ fail: return SSH_AUTH_ERROR; } -#ifndef _WIN32 static int ssh_userauth_agent_publickey(ssh_session session, const char *username, ssh_key pubkey) @@ -730,18 +753,20 @@ static int ssh_userauth_agent_publickey(ssh_session session, ssh_string pubkey_s = NULL; ssh_string sig_blob = NULL; const char *sig_type_c = NULL; + bool allowed; int rc; - switch(session->pending_call_state) { - case SSH_PENDING_CALL_NONE: - break; - case SSH_PENDING_CALL_AUTH_AGENT: - goto pending; - default: - ssh_set_error(session, - SSH_FATAL, - "Bad call during pending SSH call in ssh_userauth_try_publickey"); - return SSH_ERROR; + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_AGENT: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Bad call during pending SSH call in %s", + __func__); + return SSH_ERROR; } rc = ssh_userauth_request_service(session); @@ -765,7 +790,8 @@ static int ssh_userauth_agent_publickey(ssh_session session, SSH_STRING_FREE(pubkey_s); return SSH_AUTH_DENIED; } - if (!ssh_key_algorithm_allowed(session, sig_type_c)) { + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { ssh_set_error(session, SSH_REQUEST_DENIED, "The key algorithm '%s' is not allowed to be used by" " PUBLICKEY_ACCEPTED_TYPES configuration option", @@ -773,17 +799,25 @@ static int ssh_userauth_agent_publickey(ssh_session session, SSH_STRING_FREE(pubkey_s); return SSH_AUTH_DENIED; } + allowed = ssh_key_size_allowed(session, pubkey); + if (!allowed) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", sig_type_c, ssh_key_size(pubkey)); + SSH_STRING_FREE(pubkey_s); + return SSH_AUTH_DENIED; + } /* request */ rc = ssh_buffer_pack(session->out_buffer, "bsssbsS", - SSH2_MSG_USERAUTH_REQUEST, - username ? username : session->opts.username, - "ssh-connection", - "publickey", - 1, /* private key */ - sig_type_c, /* algo */ - pubkey_s /* public key */ - ); + SSH2_MSG_USERAUTH_REQUEST, + username ? username : session->opts.username, + "ssh-connection", + "publickey", + 1, /* private key */ + sig_type_c, /* algo */ + pubkey_s /* public key */ + ); SSH_STRING_FREE(pubkey_s); if (rc < 0) { goto fail; @@ -827,6 +861,7 @@ fail: enum ssh_agent_state_e { SSH_AGENT_STATE_NONE = 0, SSH_AGENT_STATE_PUBKEY, + SSH_AGENT_STATE_CERT, SSH_AGENT_STATE_AUTH }; @@ -837,7 +872,8 @@ struct ssh_agent_state_struct { }; /* Internal function */ -void ssh_agent_state_free(void *data) { +void ssh_agent_state_free(void *data) +{ struct ssh_agent_state_struct *state = data; if (state) { @@ -870,9 +906,15 @@ void ssh_agent_state_free(void *data) { * before you connect to the server. */ int ssh_userauth_agent(ssh_session session, - const char *username) { + const char *username) +{ int rc = SSH_AUTH_ERROR; - struct ssh_agent_state_struct *state; + struct ssh_agent_state_struct *state = NULL; + ssh_key *configKeys = NULL; + ssh_key *configCerts = NULL; + size_t configKeysCount = 0; + size_t configCertsCount = 0; + size_t i; if (session == NULL) { return SSH_AUTH_ERROR; @@ -889,7 +931,7 @@ int ssh_userauth_agent(ssh_session session, return SSH_AUTH_ERROR; } ZERO_STRUCTP(session->agent_state); - session->agent_state->state=SSH_AGENT_STATE_NONE; + session->agent_state->state = SSH_AGENT_STATE_NONE; } state = session->agent_state; @@ -901,70 +943,239 @@ int ssh_userauth_agent(ssh_session session, return SSH_AUTH_DENIED; } + if (session->opts.identities_only) { + /* + * Read keys mentioned in the config, so we can check if key from agent + * is in there. + */ + size_t identityLen = ssh_list_count(session->opts.identity); + size_t certsLen = ssh_list_count(session->opts.certificate); + struct ssh_iterator *it = ssh_list_get_iterator(session->opts.identity); + + configKeys = malloc(identityLen * sizeof(ssh_key)); + configCerts = malloc((certsLen + identityLen) * sizeof(ssh_key)); + if (configKeys == NULL || configCerts == NULL) { + free(configKeys); + free(configCerts); + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + + while (it != NULL && configKeysCount < identityLen) { + const char *privkeyFile = it->data; + size_t certPathLen; + char *certFile = NULL; + ssh_key pubkey = NULL; + ssh_key cert = NULL; + + /* + * Read the private key file listed in the config, but we're only + * interested in the public key. Don't try to decrypt private key. + */ + rc = ssh_pki_import_pubkey_file(privkeyFile, &pubkey); + if (rc == SSH_OK) { + configKeys[configKeysCount++] = pubkey; + } else { + char *pubkeyFile = NULL; + size_t pubkeyPathLen = strlen(privkeyFile) + sizeof(".pub"); + + SSH_KEY_FREE(pubkey); + + /* + * If we couldn't get the public key from the private key file, + * try a .pub file instead. + */ + pubkeyFile = malloc(pubkeyPathLen); + if (!pubkeyFile) { + ssh_set_error_oom(session); + rc = SSH_AUTH_ERROR; + goto done; + } + snprintf(pubkeyFile, pubkeyPathLen, "%s.pub", privkeyFile); + rc = ssh_pki_import_pubkey_file(pubkeyFile, &pubkey); + free(pubkeyFile); + if (rc == SSH_OK) { + configKeys[configKeysCount++] = pubkey; + } else if (pubkey) { + SSH_KEY_FREE(pubkey); + } + } + /* Now try to see if there is a certificate with default name + * do not merge it yet with the key as we need to try first the + * non-certified key */ + certPathLen = strlen(privkeyFile) + sizeof("-cert.pub"); + certFile = malloc(certPathLen); + if (!certFile) { + ssh_set_error_oom(session); + rc = SSH_AUTH_ERROR; + goto done; + } + snprintf(certFile, certPathLen, "%s-cert.pub", privkeyFile); + rc = ssh_pki_import_cert_file(certFile, &cert); + free(certFile); + if (rc == SSH_OK) { + configCerts[configCertsCount++] = cert; + } else if (cert) { + SSH_KEY_FREE(cert); + } + + it = it->next; + } + /* And now load separately-listed certificates. */ + it = ssh_list_get_iterator(session->opts.certificate); + while (it != NULL && configCertsCount < certsLen + identityLen) { + const char *certFile = it->data; + ssh_key cert = NULL; + + rc = ssh_pki_import_cert_file(certFile, &cert); + if (rc == SSH_OK) { + configCerts[configCertsCount++] = cert; + } else if (cert) { + SSH_KEY_FREE(cert); + } + + it = it->next; + } + } + while (state->pubkey != NULL) { if (state->state == SSH_AGENT_STATE_NONE) { SSH_LOG(SSH_LOG_DEBUG, - "Trying identity %s", state->comment); + "Trying identity %s", + state->comment); + if (session->opts.identities_only) { + /* Check if this key is one of the keys listed in the config */ + bool found_key = false; + for (i = 0; i < configKeysCount; i++) { + int cmp = ssh_key_cmp(state->pubkey, + configKeys[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + found_key = true; + break; + } + } + /* or in separate certificates */ + for (i = 0; i < configCertsCount; i++) { + int cmp = ssh_key_cmp(state->pubkey, + configCerts[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + found_key = true; + break; + } + } + + if (!found_key) { + SSH_LOG(SSH_LOG_DEBUG, + "Identities only is enabled and identity %s was " + "not listed in config, skipping", + state->comment); + SSH_STRING_FREE_CHAR(state->comment); + state->comment = NULL; + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_agent_get_next_ident( + session, &state->comment); + + if (state->pubkey == NULL) { + rc = SSH_AUTH_DENIED; + } + continue; + } + } } if (state->state == SSH_AGENT_STATE_NONE || - state->state == SSH_AGENT_STATE_PUBKEY) { + state->state == SSH_AGENT_STATE_PUBKEY || + state->state == SSH_AGENT_STATE_CERT) { rc = ssh_userauth_try_publickey(session, username, state->pubkey); if (rc == SSH_AUTH_ERROR) { - ssh_agent_state_free (state); + ssh_agent_state_free(state); session->agent_state = NULL; - return rc; + goto done; } else if (rc == SSH_AUTH_AGAIN) { - state->state = SSH_AGENT_STATE_PUBKEY; - return rc; + state->state = (state->state == SSH_AGENT_STATE_NONE ? + SSH_AGENT_STATE_PUBKEY : state->state); + goto done; } else if (rc != SSH_AUTH_SUCCESS) { SSH_LOG(SSH_LOG_DEBUG, - "Public key of %s refused by server", state->comment); + "Public key of %s refused by server", + state->comment); + if (state->state == SSH_AGENT_STATE_PUBKEY) { + for (i = 0; i < configCertsCount; i++) { + int cmp = ssh_key_cmp(state->pubkey, + configCerts[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + SSH_LOG(SSH_LOG_DEBUG, + "Retry with matching certificate"); + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_key_dup(configCerts[i]); + state->state = SSH_AGENT_STATE_CERT; + continue; + } + } + } SSH_STRING_FREE_CHAR(state->comment); state->comment = NULL; - ssh_key_free(state->pubkey); - state->pubkey = ssh_agent_get_next_ident(session, &state->comment); + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_agent_get_next_ident(session, + &state->comment); state->state = SSH_AGENT_STATE_NONE; continue; } SSH_LOG(SSH_LOG_DEBUG, - "Public key of %s accepted by server", state->comment); + "Public key of %s accepted by server", + state->comment); state->state = SSH_AGENT_STATE_AUTH; } if (state->state == SSH_AGENT_STATE_AUTH) { rc = ssh_userauth_agent_publickey(session, username, state->pubkey); - if (rc == SSH_AUTH_AGAIN) - return rc; + if (rc == SSH_AUTH_AGAIN) { + goto done; + } SSH_STRING_FREE_CHAR(state->comment); state->comment = NULL; if (rc == SSH_AUTH_ERROR || rc == SSH_AUTH_PARTIAL) { - ssh_agent_state_free (session->agent_state); + ssh_agent_state_free(session->agent_state); session->agent_state = NULL; - return rc; + goto done; } else if (rc != SSH_AUTH_SUCCESS) { - SSH_LOG(SSH_LOG_INFO, + SSH_LOG(SSH_LOG_DEBUG, "Server accepted public key but refused the signature"); - ssh_key_free(state->pubkey); - state->pubkey = ssh_agent_get_next_ident(session, &state->comment); + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_agent_get_next_ident(session, + &state->comment); state->state = SSH_AGENT_STATE_NONE; continue; } ssh_agent_state_free (session->agent_state); session->agent_state = NULL; - return SSH_AUTH_SUCCESS; + rc = SSH_AUTH_SUCCESS; + goto done; } } ssh_agent_state_free (session->agent_state); session->agent_state = NULL; +done: + for (i = 0; i < configKeysCount; i++) { + ssh_key_free(configKeys[i]); + } + free(configKeys); + for (i = 0; i < configCertsCount; i++) { + ssh_key_free(configCerts[i]); + } + free(configCerts); return rc; } -#endif enum ssh_auth_auto_state_e { SSH_AUTH_AUTO_STATE_NONE = 0, SSH_AUTH_AUTO_STATE_PUBKEY, SSH_AUTH_AUTO_STATE_KEY_IMPORTED, + SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE, + SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION, SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED }; @@ -973,10 +1184,12 @@ struct ssh_auth_auto_state_struct { struct ssh_iterator *it; ssh_key privkey; ssh_key pubkey; + ssh_key cert; + struct ssh_iterator *cert_it; }; /** - * @brief Get the identity that is currenly being processed by + * @brief Get the identity that is currently being processed by * ssh_userauth_publickey_auto() * * This is meant to be used by a callback that happens as part of the @@ -1007,7 +1220,8 @@ int ssh_userauth_publickey_auto_get_current_identity(ssh_session session, return SSH_ERROR; } - if (session->auth.auto_state != NULL && session->auth.auto_state->it != NULL) { + if (session->auth.auto_state != NULL && + session->auth.auto_state->it != NULL) { id = session->auth.auto_state->it->data; } @@ -1051,6 +1265,9 @@ int ssh_userauth_publickey_auto_get_current_identity(ssh_session session, * @note Most server implementations do not permit changing the username during * authentication. The username should only be set with ssh_options_set() only * before you connect to the server. + * + * The OpenSSH iterates over the identities and first try the plain public key + * and then the certificate if it is in place. */ int ssh_userauth_publickey_auto(ssh_session session, const char *username, @@ -1058,7 +1275,7 @@ int ssh_userauth_publickey_auto(ssh_session session, { ssh_auth_callback auth_fn = NULL; void *auth_data = NULL; - struct ssh_auth_auto_state_struct *state; + struct ssh_auth_auto_state_struct *state = NULL; int rc; if (session == NULL) { @@ -1085,15 +1302,13 @@ int ssh_userauth_publickey_auto(ssh_session session, } state = session->auth.auto_state; if (state->state == SSH_AUTH_AUTO_STATE_NONE) { -#ifndef _WIN32 /* Try authentication with ssh-agent first */ rc = ssh_userauth_agent(session, username); if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL || - rc == SSH_AUTH_AGAIN ) { + rc == SSH_AUTH_AGAIN) { return rc; } -#endif state->state = SSH_AUTH_AUTO_STATE_PUBKEY; } if (state->it == NULL) { @@ -1106,10 +1321,13 @@ int ssh_userauth_publickey_auto(ssh_session session, if (state->state == SSH_AUTH_AUTO_STATE_PUBKEY) { SSH_LOG(SSH_LOG_DEBUG, - "Trying to authenticate with %s", privkey_file); + "Trying to authenticate with %s", + privkey_file); + state->cert = NULL; state->privkey = NULL; state->pubkey = NULL; +#ifdef WITH_PKCS11_URI if (ssh_pki_is_uri(privkey_file)) { char *pub_uri_from_priv = NULL; SSH_LOG(SSH_LOG_INFO, @@ -1118,84 +1336,165 @@ int ssh_userauth_publickey_auto(ssh_session session, if (pub_uri_from_priv == NULL) { return SSH_ERROR; } else { - snprintf(pubkey_file, sizeof(pubkey_file), "%s", + snprintf(pubkey_file, + sizeof(pubkey_file), + "%s", pub_uri_from_priv); SAFE_FREE(pub_uri_from_priv); } - } else { - snprintf(pubkey_file, sizeof(pubkey_file), "%s.pub", privkey_file); + } else +#endif /* WITH_PKCS11_URI */ + { + snprintf(pubkey_file, + sizeof(pubkey_file), + "%s.pub", + privkey_file); } rc = ssh_pki_import_pubkey_file(pubkey_file, &state->pubkey); if (rc == SSH_ERROR) { ssh_set_error(session, - SSH_FATAL, - "Failed to import public key: %s", - pubkey_file); + SSH_FATAL, + "Failed to import public key: %s", + pubkey_file); SAFE_FREE(session->auth.auto_state); return SSH_AUTH_ERROR; } else if (rc == SSH_EOF) { /* Read the private key and save the public key to file */ rc = ssh_pki_import_privkey_file(privkey_file, - passphrase, - auth_fn, - auth_data, - &state->privkey); + passphrase, + auth_fn, + auth_data, + &state->privkey); if (rc == SSH_ERROR) { ssh_set_error(session, - SSH_FATAL, - "Failed to read private key: %s", - privkey_file); - state->it=state->it->next; + SSH_FATAL, + "Failed to read private key: %s", + privkey_file); + state->it = state->it->next; continue; } else if (rc == SSH_EOF) { /* If the file doesn't exist, continue */ SSH_LOG(SSH_LOG_DEBUG, "Private key %s doesn't exist.", privkey_file); - state->it=state->it->next; + state->it = state->it->next; continue; } - rc = ssh_pki_export_privkey_to_pubkey(state->privkey, &state->pubkey); + rc = ssh_pki_export_privkey_to_pubkey(state->privkey, + &state->pubkey); if (rc == SSH_ERROR) { - ssh_key_free(state->privkey); + SSH_KEY_FREE(state->privkey); SAFE_FREE(session->auth.auto_state); return SSH_AUTH_ERROR; } rc = ssh_pki_export_pubkey_file(state->pubkey, pubkey_file); if (rc == SSH_ERROR) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Could not write public key to file: %s", pubkey_file); } } state->state = SSH_AUTH_AUTO_STATE_KEY_IMPORTED; } - if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED) { - rc = ssh_userauth_try_publickey(session, username, state->pubkey); + if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED || + state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE || + state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION) { + ssh_key k = state->pubkey; + if (state->state != SSH_AUTH_AUTO_STATE_KEY_IMPORTED) { + k = state->cert; + } + rc = ssh_userauth_try_publickey(session, username, k); if (rc == SSH_AUTH_ERROR) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Public key authentication error for %s", privkey_file); - ssh_key_free(state->privkey); - state->privkey = NULL; - ssh_key_free(state->pubkey); - state->pubkey = NULL; + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); SAFE_FREE(session->auth.auto_state); return rc; } else if (rc == SSH_AUTH_AGAIN) { return rc; } else if (rc != SSH_AUTH_SUCCESS) { + int r; /* do not reuse `rc` as it is used to return from here */ + SSH_KEY_FREE(state->cert); SSH_LOG(SSH_LOG_DEBUG, - "Public key for %s refused by server", - privkey_file); - ssh_key_free(state->privkey); - state->privkey = NULL; - ssh_key_free(state->pubkey); - state->pubkey = NULL; - state->it=state->it->next; + "Public key for %s%s refused by server", + privkey_file, + (state->state != SSH_AUTH_AUTO_STATE_KEY_IMPORTED + ? " (with certificate)" : "")); + /* Try certificate file by appending -cert.pub (if present) */ + if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED) { + char cert_file[PATH_MAX] = {0}; + ssh_key cert = NULL; + + snprintf(cert_file, + sizeof(cert_file), + "%s-cert.pub", + privkey_file); + SSH_LOG(SSH_LOG_TRACE, + "Trying to load the certificate %s (default path)", + cert_file); + r = ssh_pki_import_cert_file(cert_file, &cert); + if (r == SSH_OK) { + /* TODO check the pubkey and certs match */ + SSH_LOG(SSH_LOG_TRACE, + "Certificate loaded %s. Retry the authentication.", + cert_file); + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE; + SSH_KEY_FREE(state->cert); + state->cert = cert; + /* try to authenticate with this certificate */ + continue; + } + /* if the file does not exists, try configuration options */ + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION; + } + /* Try certificate files loaded through options */ + if (state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION) { + SSH_KEY_FREE(state->cert); + if (state->cert_it == NULL) { + state->cert_it = ssh_list_get_iterator(session->opts.certificate); + } + while (state->cert_it != NULL) { + const char *cert_file = state->cert_it->data; + ssh_key cert = NULL; + + SSH_LOG(SSH_LOG_TRACE, + "Trying to load the certificate %s (options)", + cert_file); + r = ssh_pki_import_cert_file(cert_file, &cert); + if (r == SSH_OK) { + int cmp = ssh_key_cmp(cert, + state->pubkey, + SSH_KEY_CMP_PUBLIC); + if (cmp != 0) { + state->cert_it = state->cert_it->next; + SSH_KEY_FREE(cert); + continue; /* with next cert */ + } + SSH_LOG(SSH_LOG_TRACE, + "Found matching certificate %s in options. Retry the authentication.", + cert_file); + state->cert = cert; + cert = NULL; + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION; + /* try to authenticate with this identity */ + break; /* try this cert */ + } + /* continue with next identity */ + } + if (state->cert != NULL) { + continue; /* retry with the certificate */ + } + } + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); + state->it = state->it->next; state->state = SSH_AUTH_AUTO_STATE_PUBKEY; continue; } @@ -1205,25 +1504,25 @@ int ssh_userauth_publickey_auto(ssh_session session, /* Public key has been accepted by the server */ if (state->privkey == NULL) { rc = ssh_pki_import_privkey_file(privkey_file, - passphrase, - auth_fn, - auth_data, - &state->privkey); + passphrase, + auth_fn, + auth_data, + &state->privkey); if (rc == SSH_ERROR) { - ssh_key_free(state->pubkey); - state->pubkey=NULL; + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->pubkey); ssh_set_error(session, - SSH_FATAL, - "Failed to read private key: %s", - privkey_file); - state->it=state->it->next; + SSH_FATAL, + "Failed to read private key: %s", + privkey_file); + state->it = state->it->next; state->state = SSH_AUTH_AUTO_STATE_PUBKEY; continue; } else if (rc == SSH_EOF) { /* If the file doesn't exist, continue */ - ssh_key_free(state->pubkey); - state->pubkey = NULL; - SSH_LOG(SSH_LOG_INFO, + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->pubkey); + SSH_LOG(SSH_LOG_DEBUG, "Private key %s doesn't exist.", privkey_file); state->it = state->it->next; @@ -1231,16 +1530,33 @@ int ssh_userauth_publickey_auto(ssh_session session, continue; } } + if (state->cert != NULL && !is_cert_type(state->privkey->cert_type)) { + rc = ssh_pki_copy_cert_to_privkey(state->cert, state->privkey); + if (rc != SSH_OK) { + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); + ssh_set_error(session, + SSH_FATAL, + "Failed to copy cert to private key"); + state->it = state->it->next; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + continue; + } + } rc = ssh_userauth_publickey(session, username, state->privkey); if (rc != SSH_AUTH_AGAIN && rc != SSH_AUTH_DENIED) { - ssh_key_free(state->privkey); - ssh_key_free(state->pubkey); + bool cert_used = (state->cert != NULL); + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); SAFE_FREE(session->auth.auto_state); if (rc == SSH_AUTH_SUCCESS) { - SSH_LOG(SSH_LOG_INFO, - "Successfully authenticated using %s", - privkey_file); + SSH_LOG(SSH_LOG_DEBUG, + "Successfully authenticated using %s%s", + privkey_file, + (cert_used ? " and certificate" : "")); } return rc; } @@ -1248,18 +1564,19 @@ int ssh_userauth_publickey_auto(ssh_session session, return rc; } - ssh_key_free(state->privkey); - ssh_key_free(state->pubkey); + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_DEBUG, "The server accepted the public key but refused the signature"); state->it = state->it->next; state->state = SSH_AUTH_AUTO_STATE_PUBKEY; /* continue */ } } - SSH_LOG(SSH_LOG_INFO, - "Tried every public key, none matched"); + SSH_LOG(SSH_LOG_WARN, + "Access denied: Tried every public key, none matched"); SAFE_FREE(session->auth.auto_state); return SSH_AUTH_DENIED; } @@ -1298,10 +1615,11 @@ int ssh_userauth_publickey_auto(ssh_session session, */ int ssh_userauth_password(ssh_session session, const char *username, - const char *password) { + const char *password) +{ int rc; - switch(session->pending_call_state) { + switch (session->pending_call_state) { case SSH_PENDING_CALL_NONE: break; case SSH_PENDING_CALL_AUTH_PASSWORD: @@ -1359,7 +1677,6 @@ fail: return SSH_AUTH_ERROR; } -#ifndef _WIN32 /* LEGACY */ int ssh_userauth_agent_pubkey(ssh_session session, const char *username, @@ -1376,20 +1693,26 @@ int ssh_userauth_agent_pubkey(ssh_session session, key->type = publickey->type; key->type_c = ssh_key_type_to_char(key->type); key->flags = SSH_KEY_FLAG_PUBLIC; - key->dsa = publickey->dsa_pub; +#ifndef HAVE_LIBCRYPTO key->rsa = publickey->rsa_pub; +#else + key->key = publickey->key_pub; +#endif /* HAVE_LIBCRYPTO */ rc = ssh_userauth_agent_publickey(session, username, key); - key->dsa = NULL; +#ifndef HAVE_LIBCRYPTO key->rsa = NULL; +#else + key->key = NULL; +#endif /* HAVE_LIBCRYPTO */ ssh_key_free(key); return rc; } -#endif /* _WIN32 */ -ssh_kbdint ssh_kbdint_new(void) { +ssh_kbdint ssh_kbdint_new(void) +{ ssh_kbdint kbd; kbd = calloc(1, sizeof(struct ssh_kbdint_struct)); @@ -1401,7 +1724,8 @@ ssh_kbdint ssh_kbdint_new(void) { } -void ssh_kbdint_free(ssh_kbdint kbd) { +void ssh_kbdint_free(ssh_kbdint kbd) +{ size_t i, n; if (kbd == NULL) { @@ -1437,7 +1761,8 @@ void ssh_kbdint_free(ssh_kbdint kbd) { SAFE_FREE(kbd); } -void ssh_kbdint_clean(ssh_kbdint kbd) { +void ssh_kbdint_clean(ssh_kbdint kbd) +{ size_t i, n; if (kbd == NULL) { @@ -1637,10 +1962,10 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request) { } SSH_LOG(SSH_LOG_DEBUG, - "%d keyboard-interactive prompts", nprompts); + "%" PRIu32 " keyboard-interactive prompts", nprompts); if (nprompts > KBDINT_MAX_PROMPT) { ssh_set_error(session, SSH_FATAL, - "Too much prompts requested by the server: %u (0x%.4x)", + "Too much prompts requested by the server: %" PRIu32 " (0x%.4" PRIx32 ")", nprompts, nprompts); ssh_kbdint_free(session->kbdint); session->kbdint = NULL; @@ -1715,7 +2040,8 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request) { * @see ssh_userauth_kbdint_setanswer() */ int ssh_userauth_kbdint(ssh_session session, const char *user, - const char *submethods) { + const char *submethods) +{ int rc = SSH_AUTH_ERROR; if (session == NULL) { @@ -1757,7 +2083,8 @@ int ssh_userauth_kbdint(ssh_session session, const char *user, * * @returns The number of prompts. */ -int ssh_userauth_kbdint_getnprompts(ssh_session session) { +int ssh_userauth_kbdint_getnprompts(ssh_session session) +{ if (session == NULL) { return SSH_ERROR; } @@ -1779,7 +2106,8 @@ int ssh_userauth_kbdint_getnprompts(ssh_session session) { * * @returns The name of the message block. Do not free it. */ -const char *ssh_userauth_kbdint_getname(ssh_session session) { +const char *ssh_userauth_kbdint_getname(ssh_session session) +{ if (session == NULL) { return NULL; } @@ -1802,7 +2130,8 @@ const char *ssh_userauth_kbdint_getname(ssh_session session) { * @returns The instruction of the message block. */ -const char *ssh_userauth_kbdint_getinstruction(ssh_session session) { +const char *ssh_userauth_kbdint_getinstruction(ssh_session session) +{ if (session == NULL) return NULL; if (session->kbdint == NULL) { @@ -1837,8 +2166,9 @@ const char *ssh_userauth_kbdint_getinstruction(ssh_session session) { * if (echo) ... * @endcode */ -const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, - char *echo) { +const char * +ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, char *echo) +{ if (session == NULL) return NULL; if (session->kbdint == NULL) { @@ -1865,7 +2195,8 @@ const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, * * @returns The number of answers. */ -int ssh_userauth_kbdint_getnanswers(ssh_session session) { +int ssh_userauth_kbdint_getnanswers(ssh_session session) +{ if (session == NULL || session->kbdint == NULL) { return SSH_ERROR; } @@ -1873,15 +2204,17 @@ int ssh_userauth_kbdint_getnanswers(ssh_session session) { } /** - * @brief Get the answer for a question from a message block. + * @brief Get the answer to a question from a message block. * * @param[in] session The ssh session to use. * * @param[in] i index The number of the ith answer. * - * @return 0 on success, < 0 on error. + * @return The answer string, or NULL if the answer is not + * available. Do not free the string. */ -const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) { +const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) +{ if (session == NULL || session->kbdint == NULL || session->kbdint->answers == NULL) { return NULL; @@ -1912,8 +2245,10 @@ const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) { * * @return 0 on success, < 0 on error. */ -int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, - const char *answer) { +int +ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, + const char *answer) +{ if (session == NULL) { return -1; } @@ -1959,7 +2294,8 @@ int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again * later. */ -int ssh_userauth_gssapi(ssh_session session) { +int ssh_userauth_gssapi(ssh_session session) +{ int rc = SSH_AUTH_DENIED; #ifdef WITH_GSSAPI switch(session->pending_call_state) { @@ -1981,7 +2317,7 @@ int ssh_userauth_gssapi(ssh_session session) { } else if (rc == SSH_ERROR) { return SSH_AUTH_ERROR; } - SSH_LOG(SSH_LOG_PROTOCOL, "Authenticating with gssapi-with-mic"); + SSH_LOG(SSH_LOG_DEBUG, "Authenticating with gssapi-with-mic"); session->auth.current_method = SSH_AUTH_METHOD_GSSAPI_MIC; session->auth.state = SSH_AUTH_STATE_NONE; diff --git a/src/bignum.c b/src/bignum.c index ef8de31f..72429460 100644 --- a/src/bignum.c +++ b/src/bignum.c @@ -44,7 +44,7 @@ ssh_string ssh_make_bignum_string(bignum num) { #ifdef DEBUG_CRYPTO SSH_LOG(SSH_LOG_TRACE, - "%zu bits, %zu bytes, %zu padding\n", + "%zu bits, %zu bytes, %zu padding", bits, len, pad); #endif /* DEBUG_CRYPTO */ @@ -70,7 +70,7 @@ bignum ssh_make_string_bn(ssh_string string) #ifdef DEBUG_CRYPTO SSH_LOG(SSH_LOG_TRACE, - "Importing a %zu bits, %zu bytes object ...\n", + "Importing a %zu bits, %zu bytes object ...", len * 8, len); #endif /* DEBUG_CRYPTO */ @@ -86,12 +86,7 @@ void ssh_print_bignum(const char *name, const_bignum num) if (num != NULL) { bignum_bn2hex(num, &hex); } - fprintf(stderr, "%s value: %s\n", name, (hex == NULL) ? "(null)" : (char *) hex); -#ifdef HAVE_LIBGCRYPT - SAFE_FREE(hex); -#elif defined HAVE_LIBCRYPTO - OPENSSL_free(hex); -#elif defined HAVE_LIBMBEDCRYPTO - SAFE_FREE(hex); -#endif + SSH_LOG(SSH_LOG_DEBUG, "%s value: %s", name, + (hex == NULL) ? "(null)" : (char *)hex); + ssh_crypto_free(hex); } @@ -79,6 +79,7 @@ static socket_t bind_socket(ssh_bind sshbind, const char *hostname, int opt = 1; socket_t s; int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; ZERO_STRUCT(hints); @@ -98,7 +99,8 @@ static socket_t bind_socket(ssh_bind sshbind, const char *hostname, ai->ai_socktype, ai->ai_protocol); if (s == SSH_INVALID_SOCKET) { - ssh_set_error(sshbind, SSH_FATAL, "%s", strerror(errno)); + ssh_set_error(sshbind, SSH_FATAL, "%s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); freeaddrinfo (ai); return -1; } @@ -108,7 +110,7 @@ static socket_t bind_socket(ssh_bind sshbind, const char *hostname, ssh_set_error(sshbind, SSH_FATAL, "Setting socket options failed: %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); freeaddrinfo (ai); CLOSE_SOCKET(s); return -1; @@ -120,7 +122,7 @@ static socket_t bind_socket(ssh_bind sshbind, const char *hostname, "Binding to %s:%d: %s", hostname, port, - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); freeaddrinfo (ai); CLOSE_SOCKET(s); return -1; @@ -147,15 +149,6 @@ ssh_bind ssh_bind_new(void) { static int ssh_bind_import_keys(ssh_bind sshbind) { int rc; - if (sshbind->ecdsakey == NULL && - sshbind->dsakey == NULL && - sshbind->rsakey == NULL && - sshbind->ed25519key == NULL) { - ssh_set_error(sshbind, SSH_FATAL, - "ECDSA, ED25519, DSA, or RSA host key file must be set"); - return SSH_ERROR; - } - #ifdef HAVE_ECC if (sshbind->ecdsa == NULL && sshbind->ecdsakey != NULL) { rc = ssh_pki_import_privkey_file(sshbind->ecdsakey, @@ -179,30 +172,6 @@ static int ssh_bind_import_keys(ssh_bind sshbind) { } #endif -#ifdef HAVE_DSA - if (sshbind->dsa == NULL && sshbind->dsakey != NULL) { - rc = ssh_pki_import_privkey_file(sshbind->dsakey, - NULL, - NULL, - NULL, - &sshbind->dsa); - if (rc == SSH_ERROR || rc == SSH_EOF) { - ssh_set_error(sshbind, SSH_FATAL, - "Failed to import private DSA host key"); - return SSH_ERROR; - } - - if (ssh_key_type(sshbind->dsa) != SSH_KEYTYPE_DSS) { - ssh_set_error(sshbind, SSH_FATAL, - "The DSA host key has the wrong type: %d", - ssh_key_type(sshbind->dsa)); - ssh_key_free(sshbind->dsa); - sshbind->dsa = NULL; - return SSH_ERROR; - } - } -#endif - if (sshbind->rsa == NULL && sshbind->rsakey != NULL) { rc = ssh_pki_import_privkey_file(sshbind->rsakey, NULL, @@ -249,97 +218,124 @@ static int ssh_bind_import_keys(ssh_bind sshbind) { } int ssh_bind_listen(ssh_bind sshbind) { - const char *host; - socket_t fd; - int rc; + const char *host; + socket_t fd; + int rc; - if (sshbind->rsa == NULL && - sshbind->dsa == NULL && - sshbind->ecdsa == NULL && - sshbind->ed25519 == NULL) { - rc = ssh_bind_import_keys(sshbind); - if (rc != SSH_OK) { - return SSH_ERROR; - } - } + /* Apply global bind configurations, if it hasn't been applied before */ + rc = ssh_bind_options_parse_config(sshbind, NULL); + if (rc != 0) { + ssh_set_error(sshbind, SSH_FATAL,"Could not parse global config"); + return SSH_ERROR; + } - if (sshbind->bindfd == SSH_INVALID_SOCKET) { - host = sshbind->bindaddr; - if (host == NULL) { - host = "0.0.0.0"; - } + /* Set default hostkey paths if no hostkey was found before */ + if (sshbind->ecdsakey == NULL && + sshbind->rsakey == NULL && + sshbind->ed25519key == NULL) { - fd = bind_socket(sshbind, host, sshbind->bindport); - if (fd == SSH_INVALID_SOCKET) { - ssh_key_free(sshbind->dsa); - sshbind->dsa = NULL; - ssh_key_free(sshbind->rsa); - sshbind->rsa = NULL; - /* XXX should this clear also other structures that were allocated */ - return -1; - } + sshbind->ecdsakey = strdup("/etc/ssh/ssh_host_ecdsa_key"); + sshbind->rsakey = strdup("/etc/ssh/ssh_host_rsa_key"); + sshbind->ed25519key = strdup("/etc/ssh/ssh_host_ed25519_key"); + } - if (listen(fd, 10) < 0) { - ssh_set_error(sshbind, SSH_FATAL, - "Listening to socket %d: %s", - fd, strerror(errno)); - CLOSE_SOCKET(fd); - ssh_key_free(sshbind->dsa); - sshbind->dsa = NULL; - ssh_key_free(sshbind->rsa); - sshbind->rsa = NULL; - /* XXX should this clear also other structures that were allocated */ - return -1; - } + /* Apply global bind configurations, if it hasn't been applied before */ + rc = ssh_bind_options_parse_config(sshbind, NULL); + if (rc != 0) { + ssh_set_error(sshbind, SSH_FATAL, "Could not parse global config"); + return SSH_ERROR; + } - sshbind->bindfd = fd; + /* Set default hostkey paths if no hostkey was found before */ + if (sshbind->ecdsakey == NULL && + sshbind->rsakey == NULL && + sshbind->ed25519key == NULL) { + + sshbind->ecdsakey = strdup("/etc/ssh/ssh_host_ecdsa_key"); + sshbind->rsakey = strdup("/etc/ssh/ssh_host_rsa_key"); + sshbind->ed25519key = strdup("/etc/ssh/ssh_host_ed25519_key"); + } + + if (sshbind->rsa == NULL && + sshbind->ecdsa == NULL && + sshbind->ed25519 == NULL) { + rc = ssh_bind_import_keys(sshbind); + if (rc != SSH_OK) { + return SSH_ERROR; + } + } + + if (sshbind->bindfd == SSH_INVALID_SOCKET) { + host = sshbind->bindaddr; + if (host == NULL) { + host = "0.0.0.0"; + } + + fd = bind_socket(sshbind, host, sshbind->bindport); + if (fd == SSH_INVALID_SOCKET) { + return SSH_ERROR; + } + + if (listen(fd, 10) < 0) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(sshbind, + SSH_FATAL, + "Listening to socket %d: %s", + fd, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + CLOSE_SOCKET(fd); + return SSH_ERROR; + } + + sshbind->bindfd = fd; } else { - SSH_LOG(SSH_LOG_INFO, "Using app-provided bind socket"); + SSH_LOG(SSH_LOG_DEBUG, "Using app-provided bind socket"); } return 0; } -int ssh_bind_set_callbacks(ssh_bind sshbind, ssh_bind_callbacks callbacks, - void *userdata){ - if (sshbind == NULL) { - return SSH_ERROR; - } - if (callbacks == NULL) { - ssh_set_error_invalid(sshbind); - return SSH_ERROR; - } - if(callbacks->size <= 0 || callbacks->size > 1024 * sizeof(void *)){ - ssh_set_error(sshbind,SSH_FATAL, - "Invalid callback passed in (badly initialized)"); - return SSH_ERROR; - } - sshbind->bind_callbacks = callbacks; - sshbind->bind_callbacks_userdata=userdata; - return 0; +int ssh_bind_set_callbacks(ssh_bind sshbind, ssh_bind_callbacks callbacks, void *userdata) +{ + if (sshbind == NULL) { + return SSH_ERROR; + } + if (callbacks == NULL) { + ssh_set_error_invalid(sshbind); + return SSH_ERROR; + } + if (callbacks->size <= 0 || callbacks->size > 1024 * sizeof(void *)) { + ssh_set_error(sshbind, + SSH_FATAL, + "Invalid callback passed in (badly initialized)"); + return SSH_ERROR; + } + sshbind->bind_callbacks = callbacks; + sshbind->bind_callbacks_userdata = userdata; + return 0; } /** @internal * @brief callback being called by poll when an event happens * */ -static int ssh_bind_poll_callback(ssh_poll_handle sshpoll, - socket_t fd, int revents, void *user){ - ssh_bind sshbind=(ssh_bind)user; - (void)sshpoll; - (void)fd; - - if(revents & POLLIN){ - /* new incoming connection */ - if(ssh_callbacks_exists(sshbind->bind_callbacks,incoming_connection)){ - sshbind->bind_callbacks->incoming_connection(sshbind, - sshbind->bind_callbacks_userdata); +static int ssh_bind_poll_callback(ssh_poll_handle sshpoll, socket_t fd, int revents, void *user) +{ + ssh_bind sshbind = (ssh_bind)user; + (void)sshpoll; + (void)fd; + + if (revents & POLLIN) { + /* new incoming connection */ + if (ssh_callbacks_exists(sshbind->bind_callbacks, incoming_connection)) { + sshbind->bind_callbacks->incoming_connection(sshbind, + sshbind->bind_callbacks_userdata); + } } - } - return 0; + return 0; } /** @internal - * @brief returns the current poll handle, or create it + * @brief returns the current poll handle, or creates it * @param sshbind the ssh_bind object * @returns a ssh_poll handle suitable for operation */ @@ -363,20 +359,24 @@ ssh_poll_handle ssh_bind_get_poll(ssh_bind sshbind) return sshbind->poll; } -void ssh_bind_set_blocking(ssh_bind sshbind, int blocking) { - sshbind->blocking = blocking ? 1 : 0; +void ssh_bind_set_blocking(ssh_bind sshbind, int blocking) +{ + sshbind->blocking = blocking ? 1 : 0; } -socket_t ssh_bind_get_fd(ssh_bind sshbind) { - return sshbind->bindfd; +socket_t ssh_bind_get_fd(ssh_bind sshbind) +{ + return sshbind->bindfd; } -void ssh_bind_set_fd(ssh_bind sshbind, socket_t fd) { - sshbind->bindfd = fd; +void ssh_bind_set_fd(ssh_bind sshbind, socket_t fd) +{ + sshbind->bindfd = fd; } -void ssh_bind_fd_toaccept(ssh_bind sshbind) { - sshbind->toaccept = 1; +void ssh_bind_fd_toaccept(ssh_bind sshbind) +{ + sshbind->toaccept = 1; } void ssh_bind_free(ssh_bind sshbind){ @@ -398,13 +398,10 @@ void ssh_bind_free(ssh_bind sshbind){ 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); @@ -421,7 +418,9 @@ void ssh_bind_free(ssh_bind sshbind){ SAFE_FREE(sshbind); } -int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd){ +int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd) +{ + ssh_poll_handle handle = NULL; int i, rc; if (sshbind == NULL) { @@ -433,13 +432,6 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd){ return SSH_ERROR; } - /* Apply global bind configurations, if it hasn't been applied before */ - rc = ssh_bind_options_parse_config(sshbind, NULL); - if (rc != 0) { - ssh_set_error(sshbind, SSH_FATAL,"Could not parse global config"); - return SSH_ERROR; - } - session->server = 1; /* Copy options from bind to session */ @@ -503,6 +495,8 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd){ } } + session->opts.rsa_min_size = sshbind->rsa_min_size; + ssh_socket_free(session->socket); session->socket = ssh_socket_new(session); if (session->socket == NULL) { @@ -511,7 +505,12 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd){ return SSH_ERROR; } ssh_socket_set_fd(session->socket, fd); - ssh_socket_get_poll_handle(session->socket); + handle = ssh_socket_get_poll_handle(session->socket); + if (handle == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + ssh_socket_set_connected(session->socket, handle); /* We must try to import any keys that could be imported in case * we are not using ssh_bind_listen (which is the other place @@ -519,7 +518,6 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd){ * only using ssh_bind_accept_fd to manage sockets ourselves. */ if (sshbind->rsa == NULL && - sshbind->dsa == NULL && sshbind->ecdsa == NULL && sshbind->ed25519 == NULL) { rc = ssh_bind_import_keys(sshbind); @@ -537,15 +535,6 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd){ } } #endif -#ifdef HAVE_DSA - if (sshbind->dsa) { - session->srv.dsa_key = ssh_key_dup(sshbind->dsa); - if (session->srv.dsa_key == NULL) { - ssh_set_error_oom(sshbind); - return SSH_ERROR; - } - } -#endif if (sshbind->rsa) { session->srv.rsa_key = ssh_key_dup(sshbind->rsa); if (session->srv.rsa_key == NULL) { @@ -584,9 +573,16 @@ int ssh_bind_accept(ssh_bind sshbind, ssh_session session) fd = accept(sshbind->bindfd, NULL, NULL); if (fd == SSH_INVALID_SOCKET) { - ssh_set_error(sshbind, SSH_FATAL, - "Accepting a new connection: %s", - strerror(errno)); + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + if (errno == EINTR) { + ssh_set_error(sshbind, SSH_EINTR, + "Accepting a new connection (child signal error): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } else { + ssh_set_error(sshbind, SSH_FATAL, + "Accepting a new connection: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } return SSH_ERROR; } rc = ssh_bind_accept_fd(sshbind, session, fd); diff --git a/src/bind_config.c b/src/bind_config.c index ace5a0ac..a4c7a8d7 100644 --- a/src/bind_config.c +++ b/src/bind_config.c @@ -189,18 +189,29 @@ ssh_bind_config_parse_line(ssh_bind bind, const char *line, unsigned int count, uint32_t *parser_flags, - uint8_t *seen); - -static void local_parse_file(ssh_bind bind, - const char *filename, - uint32_t *parser_flags, - uint8_t *seen) + uint8_t *seen, + unsigned int depth); + +#define LIBSSH_BIND_CONF_MAX_DEPTH 16 +static void +local_parse_file(ssh_bind bind, + const char *filename, + uint32_t *parser_flags, + uint8_t *seen, + unsigned int depth) { FILE *f; char line[MAX_LINE_SIZE] = {0}; unsigned int count = 0; int rv; + if (depth > LIBSSH_BIND_CONF_MAX_DEPTH) { + ssh_set_error(bind, SSH_FATAL, + "ERROR - Too many levels of configuration includes " + "when processing file '%s'", filename); + return; + } + f = fopen(filename, "r"); if (f == NULL) { SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", @@ -213,7 +224,7 @@ static void local_parse_file(ssh_bind bind, while (fgets(line, sizeof(line), f)) { count++; - rv = ssh_bind_config_parse_line(bind, line, count, parser_flags, seen); + rv = ssh_bind_config_parse_line(bind, line, count, parser_flags, seen, depth); if (rv < 0) { fclose(f); return; @@ -228,7 +239,8 @@ static void local_parse_file(ssh_bind bind, static void local_parse_glob(ssh_bind bind, const char *fileglob, uint32_t *parser_flags, - uint8_t *seen) + uint8_t *seen, + unsigned int depth) { glob_t globbuf = { .gl_flags = 0, @@ -248,7 +260,7 @@ static void local_parse_glob(ssh_bind bind, } for (i = 0; i < globbuf.gl_pathc; i++) { - local_parse_file(bind, globbuf.gl_pathv[i], parser_flags, seen); + local_parse_file(bind, globbuf.gl_pathv[i], parser_flags, seen, depth); } globfree(&globbuf); @@ -274,7 +286,8 @@ ssh_bind_config_parse_line(ssh_bind bind, const char *line, unsigned int count, uint32_t *parser_flags, - uint8_t *seen) + uint8_t *seen, + unsigned int depth) { enum ssh_bind_config_opcode_e opcode; const char *p = NULL; @@ -288,7 +301,12 @@ ssh_bind_config_parse_line(ssh_bind bind, return -1; } - if ((line == NULL) || (parser_flags == NULL)) { + /* Ignore empty lines */ + if (line == NULL || *line == '\0') { + return 0; + } + + if (parser_flags == NULL) { ssh_set_error_invalid(bind); return -1; } @@ -333,9 +351,9 @@ ssh_bind_config_parse_line(ssh_bind bind, p = ssh_config_get_str_tok(&s, NULL); if (p && (*parser_flags & PARSING)) { #if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) - local_parse_glob(bind, p, parser_flags, seen); + local_parse_glob(bind, p, parser_flags, seen, depth + 1); #else - local_parse_file(bind, p, parser_flags, seen); + local_parse_file(bind, p, parser_flags, seen, depth + 1); #endif /* HAVE_GLOB */ } break; @@ -345,7 +363,7 @@ ssh_bind_config_parse_line(ssh_bind bind, if (p && (*parser_flags & PARSING)) { rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set Hostkey value '%s'", count, p); } @@ -356,7 +374,7 @@ ssh_bind_config_parse_line(ssh_bind bind, if (p && (*parser_flags & PARSING)) { rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDADDR, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set ListenAddress value '%s'", count, p); } @@ -367,7 +385,7 @@ ssh_bind_config_parse_line(ssh_bind bind, if (p && (*parser_flags & PARSING)) { rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set Port value '%s'", count, p); } @@ -378,7 +396,7 @@ ssh_bind_config_parse_line(ssh_bind bind, if (p && (*parser_flags & PARSING)) { rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_C_S, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set C->S Ciphers value '%s'", count, p); break; @@ -386,7 +404,7 @@ ssh_bind_config_parse_line(ssh_bind bind, rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_S_C, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set S->C Ciphers value '%s'", count, p); } @@ -397,7 +415,7 @@ ssh_bind_config_parse_line(ssh_bind bind, if (p && (*parser_flags & PARSING)) { rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_C_S, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set C->S MAC value '%s'", count, p); break; @@ -405,7 +423,7 @@ ssh_bind_config_parse_line(ssh_bind bind, rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_S_C, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set S->C MAC value '%s'", count, p); } @@ -419,10 +437,10 @@ ssh_bind_config_parse_line(ssh_bind bind, if (strcasecmp(p, "quiet") == 0) { value = SSH_LOG_NONE; } else if (strcasecmp(p, "fatal") == 0 || - strcasecmp(p, "error")== 0 || - strcasecmp(p, "info") == 0) { + strcasecmp(p, "error")== 0) { value = SSH_LOG_WARN; - } else if (strcasecmp(p, "verbose") == 0) { + } else if (strcasecmp(p, "verbose") == 0 || + strcasecmp(p, "info") == 0) { value = SSH_LOG_INFO; } else if (strcasecmp(p, "DEBUG") == 0 || strcasecmp(p, "DEBUG1") == 0) { @@ -435,7 +453,7 @@ ssh_bind_config_parse_line(ssh_bind bind, rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY, &value); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set LogLevel value '%s'", count, p); } @@ -447,7 +465,7 @@ ssh_bind_config_parse_line(ssh_bind bind, if (p && (*parser_flags & PARSING)) { rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_KEY_EXCHANGE, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set KexAlgorithms value '%s'", count, p); } @@ -522,13 +540,13 @@ ssh_bind_config_parse_line(ssh_bind bind, /* Skip one argument */ p = ssh_config_get_str_tok(&s, NULL); if (p == NULL || p[0] == '\0') { - SSH_LOG(SSH_LOG_WARN, "line %d: Match keyword " + SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword " "'%s' requires argument\n", count, p2); SAFE_FREE(x); return -1; } args++; - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_DEBUG, "line %d: Unsupported Match keyword '%s', ignoring\n", count, p2); @@ -558,7 +576,7 @@ ssh_bind_config_parse_line(ssh_bind bind, rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set PubKeyAcceptedKeyTypes value '%s'", count, p); } @@ -570,26 +588,26 @@ ssh_bind_config_parse_line(ssh_bind bind, rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, p); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "line %d: Failed to set HostkeyAlgorithms value '%s'", count, p); } } break; case BIND_CFG_NOT_ALLOWED_IN_MATCH: - SSH_LOG(SSH_LOG_WARN, "Option not allowed in Match block: %s, line: %d", + SSH_LOG(SSH_LOG_DEBUG, "Option not allowed in Match block: %s, line: %d", keyword, count); break; case BIND_CFG_UNKNOWN: - SSH_LOG(SSH_LOG_WARN, "Unknown option: %s, line: %d", + SSH_LOG(SSH_LOG_TRACE, "Unknown option: %s, line: %d", keyword, count); break; case BIND_CFG_UNSUPPORTED: - SSH_LOG(SSH_LOG_WARN, "Unsupported option: %s, line: %d", + SSH_LOG(SSH_LOG_TRACE, "Unsupported option: %s, line: %d", keyword, count); break; case BIND_CFG_NA: - SSH_LOG(SSH_LOG_WARN, "Option not applicable: %s, line: %d", + SSH_LOG(SSH_LOG_TRACE, "Option not applicable: %s, line: %d", keyword, count); break; default: @@ -628,7 +646,7 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename) parser_flags = PARSING; while (fgets(line, sizeof(line), f)) { count++; - rv = ssh_bind_config_parse_line(bind, line, count, &parser_flags, seen); + rv = ssh_bind_config_parse_line(bind, line, count, &parser_flags, seen, 0); if (rv) { fclose(f); return -1; @@ -638,3 +656,64 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename) fclose(f); return 0; } + +/* @brief Parse configuration string and set the options to the given bind session + * + * @params[in] bind The ssh bind session + * @params[in] input Null terminated string containing the configuration + * + * @returns SSH_OK on successful parsing the configuration string, + * SSH_ERROR on error + */ +int ssh_bind_config_parse_string(ssh_bind bind, const char *input) +{ + char line[MAX_LINE_SIZE] = {0}; + const char *c = input, *line_start = input; + unsigned int line_num = 0, line_len; + uint32_t parser_flags; + int rv; + + /* This local table is used during the parsing of the current file (and + * files included recursively in this file) to prevent an option to be + * redefined, i.e. the first value set is kept. But this DO NOT prevent the + * option to be redefined later by another file. */ + uint8_t seen[BIND_CFG_MAX] = {0}; + + SSH_LOG(SSH_LOG_DEBUG, "Reading bind configuration data from string:"); + SSH_LOG(SSH_LOG_DEBUG, "START\n%s\nEND", input); + + parser_flags = PARSING; + while (1) { + line_num++; + line_start = c; + c = strchr(line_start, '\n'); + if (c == NULL) { + /* if there is no newline at the end of the string */ + c = strchr(line_start, '\0'); + } + if (c == NULL) { + /* should not happen, would mean a string without trailing '\0' */ + SSH_LOG(SSH_LOG_WARN, "No trailing '\\0' in config string"); + return SSH_ERROR; + } + line_len = c - line_start; + if (line_len > MAX_LINE_SIZE - 1) { + SSH_LOG(SSH_LOG_WARN, "Line %u too long: %u characters", + line_num, line_len); + return SSH_ERROR; + } + memcpy(line, line_start, line_len); + line[line_len] = '\0'; + SSH_LOG(SSH_LOG_DEBUG, "Line %u: %s", line_num, line); + rv = ssh_bind_config_parse_line(bind, line, line_num, &parser_flags, seen, 0); + if (rv < 0) { + return SSH_ERROR; + } + if (*c == '\0') { + break; + } + c++; + } + + return SSH_OK; +} diff --git a/src/buffer.c b/src/buffer.c index f309215a..299eac54 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -26,6 +26,7 @@ #include <limits.h> #include <stdarg.h> #include <stdbool.h> +#include <stdio.h> #ifndef _WIN32 #include <netinet/in.h> @@ -46,9 +47,9 @@ */ struct ssh_buffer_struct { bool secure; - size_t used; - size_t allocated; - size_t pos; + uint32_t used; + uint32_t allocated; + uint32_t pos; uint8_t *data; }; @@ -56,7 +57,7 @@ struct ssh_buffer_struct { #define BUFFER_SIZE_MAX 0x10000000 /** - * @defgroup libssh_buffer The SSH buffer functions. + * @defgroup libssh_buffer The SSH buffer functions * @ingroup libssh * * Functions to handle SSH buffers. @@ -83,21 +84,21 @@ static void buffer_verify(ssh_buffer buf) if (buf->used > buf->allocated) { fprintf(stderr, - "BUFFER ERROR: allocated %zu, used %zu\n", + "BUFFER ERROR: allocated %u, used %u\n", buf->allocated, buf->used); do_abort = true; } if (buf->pos > buf->used) { fprintf(stderr, - "BUFFER ERROR: position %zu, used %zu\n", + "BUFFER ERROR: position %u, used %u\n", buf->pos, buf->used); do_abort = true; } if (buf->pos > buf->allocated) { fprintf(stderr, - "BUFFER ERROR: position %zu, allocated %zu\n", + "BUFFER ERROR: position %u, allocated %u\n", buf->pos, buf->allocated); do_abort = true; @@ -129,7 +130,7 @@ struct ssh_buffer_struct *ssh_buffer_new(void) /* * Always preallocate 64 bytes. * - * -1 for ralloc_buffer magic. + * -1 for realloc_buffer magic. */ rc = ssh_buffer_allocate_size(buf, 64 - 1); if (rc != 0) { @@ -178,9 +179,9 @@ void ssh_buffer_set_secure(ssh_buffer buffer) buffer->secure = true; } -static int realloc_buffer(struct ssh_buffer_struct *buffer, size_t needed) +static int realloc_buffer(struct ssh_buffer_struct *buffer, uint32_t needed) { - size_t smallest = 1; + uint32_t smallest = 1; uint8_t *new = NULL; buffer_verify(buffer); @@ -693,7 +694,7 @@ uint32_t ssh_buffer_get_data(struct ssh_buffer_struct *buffer, void *data, uint3 /** * @internal * - * @brief Get a 8 bits unsigned int out of the buffer and adjusts the read + * @brief Get a 8 bits unsigned int out of the buffer and adjust the read * pointer. * * @param[in] buffer The buffer to read. @@ -702,7 +703,7 @@ uint32_t ssh_buffer_get_data(struct ssh_buffer_struct *buffer, void *data, uint3 * * @returns 0 if there is not enough data in buffer, 1 otherwise. */ -int ssh_buffer_get_u8(struct ssh_buffer_struct *buffer, uint8_t *data){ +uint32_t ssh_buffer_get_u8(struct ssh_buffer_struct *buffer, uint8_t *data){ return ssh_buffer_get_data(buffer,data,sizeof(uint8_t)); } @@ -717,7 +718,7 @@ int ssh_buffer_get_u8(struct ssh_buffer_struct *buffer, uint8_t *data){ * * @returns 0 if there is not enough data in buffer, 4 otherwise. */ -int ssh_buffer_get_u32(struct ssh_buffer_struct *buffer, uint32_t *data){ +uint32_t ssh_buffer_get_u32(struct ssh_buffer_struct *buffer, uint32_t *data){ return ssh_buffer_get_data(buffer,data,sizeof(uint32_t)); } /** @@ -732,7 +733,7 @@ int ssh_buffer_get_u32(struct ssh_buffer_struct *buffer, uint32_t *data){ * * @returns 0 if there is not enough data in buffer, 8 otherwise. */ -int ssh_buffer_get_u64(struct ssh_buffer_struct *buffer, uint64_t *data){ +uint32_t ssh_buffer_get_u64(struct ssh_buffer_struct *buffer, uint64_t *data){ return ssh_buffer_get_data(buffer,data,sizeof(uint64_t)); } @@ -747,7 +748,8 @@ int ssh_buffer_get_u64(struct ssh_buffer_struct *buffer, uint64_t *data){ */ int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len) { - if (buffer->pos + len < len || buffer->pos + len > buffer->used) { + if (buffer == NULL || buffer->pos + len < len || + buffer->pos + len > buffer->used) { return SSH_ERROR; } @@ -757,7 +759,7 @@ int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len) /** * @internal * - * @brief Get a SSH String out of the buffer and adjusts the read pointer. + * @brief Get an SSH String out of the buffer and adjust the read pointer. * * @param[in] buffer The buffer to read. * @@ -868,7 +870,7 @@ static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer, va_arg(ap, bignum); /* * Use a fixed size for a bignum - * (they should normaly be around 32) + * (they should normally be around 32) */ needed_size += 64; break; @@ -878,7 +880,7 @@ static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer, cstring = NULL; break; default: - SSH_LOG(SSH_LOG_WARN, "Invalid buffer format %c", *p); + SSH_LOG(SSH_LOG_TRACE, "Invalid buffer format %c", *p); rc = SSH_ERROR; } if (rc != SSH_OK){ @@ -900,7 +902,7 @@ static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer, } } - rc = ssh_buffer_allocate_size(buffer, needed_size); + rc = ssh_buffer_allocate_size(buffer, (uint32_t)needed_size); if (rc != 0) { return SSH_ERROR; } @@ -1007,7 +1009,7 @@ int ssh_buffer_pack_va(struct ssh_buffer_struct *buffer, cstring = NULL; break; default: - SSH_LOG(SSH_LOG_WARN, "Invalid buffer format %c", *p); + SSH_LOG(SSH_LOG_TRACE, "Invalid buffer format %c", *p); rc = SSH_ERROR; } if (rc != SSH_OK){ @@ -1102,7 +1104,8 @@ int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, bignum *bignum; void **data; } o; - size_t len, rlen, max_len; + size_t len; + uint32_t rlen, max_len; ssh_string tmp_string = NULL; va_list ap_copy; size_t count; @@ -1238,7 +1241,7 @@ int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, rc = SSH_OK; break; default: - SSH_LOG(SSH_LOG_WARN, "Invalid buffer format %c", *p); + SSH_LOG(SSH_LOG_TRACE, "Invalid buffer format %c", *p); } if (rc != SSH_OK) { break; diff --git a/src/chachapoly.c b/src/chachapoly.c index c4827fd8..2cd23854 100644 --- a/src/chachapoly.c +++ b/src/chachapoly.c @@ -65,7 +65,7 @@ static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, /** * @internal * - * @brief encrypts an outgoing packet with chacha20 and authenticate it + * @brief encrypts an outgoing packet with chacha20 and authenticates it * with poly1305. */ static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, diff --git a/src/channels.c b/src/channels.c index 112a38f9..9e613715 100644 --- a/src/channels.c +++ b/src/channels.c @@ -29,6 +29,7 @@ #include <errno.h> #include <time.h> #include <stdbool.h> +#include <string.h> #ifdef HAVE_SYS_TIME_H #include <sys/time.h> #endif /* HAVE_SYS_TIME_H */ @@ -51,16 +52,19 @@ #include "libssh/server.h" #endif -#define WINDOWBASE 1280000 -#define WINDOWLIMIT (WINDOWBASE/2) - /* * All implementations MUST be able to process packets with an * uncompressed payload length of 32768 bytes or less and a total packet * size of 35000 bytes or less. */ #define CHANNEL_MAX_PACKET 32768 -#define CHANNEL_INITIAL_WINDOW 64000 + +/* + * WINDOW_DEFAULT matches the default OpenSSH session window size. + * This controls how much data the peer can send before needing to receive + * a round-trip SSH2_MSG_CHANNEL_WINDOW_ADJUST message that increases the window. + */ +#define WINDOW_DEFAULT (64*CHANNEL_MAX_PACKET) /** * @defgroup libssh_channel The SSH channel functions @@ -79,6 +83,9 @@ static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet); * @param[in] session The ssh session to use. * * @return A pointer to a newly allocated channel, NULL on error. + * The channel needs to be freed with ssh_channel_free(). + * + * @see ssh_channel_free() */ ssh_channel ssh_channel_new(ssh_session session) { @@ -147,8 +154,9 @@ ssh_channel ssh_channel_new(ssh_session session) * * @return The new channel identifier. */ -uint32_t ssh_channel_new_id(ssh_session session) { - return ++(session->maxchannel); +uint32_t ssh_channel_new_id(ssh_session session) +{ + return ++(session->maxchannel); } /** @@ -173,7 +181,7 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf){ channel=ssh_channel_from_local(session,channelid); if(channel==NULL){ ssh_set_error(session, SSH_FATAL, - "Unknown channel id %"PRIu32, + "Unknown channel id %" PRIu32, (uint32_t) channelid); /* TODO: Set error marking in channel object */ @@ -187,8 +195,8 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf){ if (rc != SSH_OK) goto error; - SSH_LOG(SSH_LOG_PROTOCOL, - "Received a CHANNEL_OPEN_CONFIRMATION for channel %d:%d", + SSH_LOG(SSH_LOG_DEBUG, + "Received a CHANNEL_OPEN_CONFIRMATION for channel %" PRIu32 ":%" PRIu32, channel->local_channel, channel->remote_channel); @@ -200,13 +208,21 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf){ goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, - "Remote window : %"PRIu32", maxpacket : %"PRIu32, - (uint32_t) channel->remote_window, - (uint32_t) channel->remote_maxpacket); + SSH_LOG(SSH_LOG_DEBUG, + "Remote window : %" PRIu32 ", maxpacket : %" PRIu32, + channel->remote_window, + channel->remote_maxpacket); channel->state = SSH_CHANNEL_STATE_OPEN; channel->flags &= ~SSH_CHANNEL_FLAG_NOT_BOUND; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_open_response_function, + channel->session, + channel, + true /* is_success */); + return SSH_PACKET_USED; error: @@ -245,16 +261,25 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail){ "SSH2_MSG_CHANNEL_OPEN_FAILURE received in incorrect channel " "state %d", channel->state); + SAFE_FREE(error); goto error; } ssh_set_error(session, SSH_REQUEST_DENIED, - "Channel opening failure: channel %u error (%"PRIu32") %s", + "Channel opening failure: channel %" PRIu32 " error (%" PRIu32 ") %s", channel->local_channel, - (uint32_t) code, + code, error); SAFE_FREE(error); channel->state=SSH_CHANNEL_STATE_OPEN_DENIED; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_open_response_function, + channel->session, + channel, + false /* is_success */); + return SSH_PACKET_USED; error: @@ -262,7 +287,8 @@ error: return SSH_PACKET_USED; } -static int ssh_channel_open_termination(void *c){ +static int ssh_channel_open_termination(void *c) +{ ssh_channel channel = (ssh_channel) c; if (channel->state != SSH_CHANNEL_STATE_OPENING || channel->session->session_state == SSH_SESSION_STATE_ERROR) @@ -320,8 +346,8 @@ channel_open(ssh_channel channel, channel->local_maxpacket = maxpacket; channel->local_window = window; - SSH_LOG(SSH_LOG_PROTOCOL, - "Creating a channel %d with %d window and %d max packet", + SSH_LOG(SSH_LOG_DEBUG, + "Creating a channel %" PRIu32 " with %" PRIu32 " window and %" PRIu32 " max packet", channel->local_channel, window, maxpacket); rc = ssh_buffer_pack(session->out_buffer, @@ -349,7 +375,7 @@ channel_open(ssh_channel channel, } SSH_LOG(SSH_LOG_PACKET, - "Sent a SSH_MSG_CHANNEL_OPEN type %s for channel %d", + "Sent a SSH_MSG_CHANNEL_OPEN type %s for channel %" PRIu32, type, channel->local_channel); pending: @@ -369,7 +395,7 @@ end: if (channel->state == SSH_CHANNEL_STATE_OPEN) { err = SSH_OK; } else if (err != SSH_AGAIN) { - /* Messages were handled correctly, but he channel state is invalid */ + /* Messages were handled correctly, but the channel state is invalid */ err = SSH_ERROR; } @@ -396,35 +422,46 @@ ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id) { /** * @internal - * @brief grows the local window and send a packet to the other party + * @brief grows the local window and sends a packet to the other party * @param session SSH session * @param channel SSH channel - * @param minimumsize The minimum acceptable size for the new window. * @return SSH_OK if successful; SSH_ERROR otherwise. */ static int grow_window(ssh_session session, - ssh_channel channel, - uint32_t minimumsize) + ssh_channel channel) { - uint32_t new_window = minimumsize > WINDOWBASE ? minimumsize : WINDOWBASE; + uint32_t used; + uint32_t increment; int rc; - if(new_window <= channel->local_window){ - SSH_LOG(SSH_LOG_PROTOCOL, - "growing window (channel %d:%d) to %d bytes : not needed (%d bytes)", - channel->local_channel, channel->remote_channel, new_window, + /* Calculate the increment taking into account what the peer may still send + * (local_window) and what we've already buffered (stdout_buffer and + * stderr_buffer). + */ + used = channel->local_window; + if (channel->stdout_buffer != NULL) { + used += ssh_buffer_get_len(channel->stdout_buffer); + } + if (channel->stderr_buffer != NULL) { + used += ssh_buffer_get_len(channel->stderr_buffer); + } + /* Avoid a negative increment in case the peer sent more than the window allowed */ + increment = WINDOW_DEFAULT > used ? WINDOW_DEFAULT - used : 0; + /* Don't grow until we can request at least half a window */ + if (increment < (WINDOW_DEFAULT / 2)) { + SSH_LOG(SSH_LOG_DEBUG, + "growing window (channel %" PRIu32 ":%" PRIu32 ") to %" PRIu32 " bytes : not needed (%" PRIu32 " bytes)", + channel->local_channel, channel->remote_channel, WINDOW_DEFAULT, channel->local_window); return SSH_OK; } - /* WINDOW_ADJUST packet needs a relative increment rather than an absolute - * value, so we give here the missing bytes needed to reach new_window - */ + rc = ssh_buffer_pack(session->out_buffer, "bdd", SSH2_MSG_CHANNEL_WINDOW_ADJUST, channel->remote_channel, - new_window - channel->local_window); + increment); if (rc != SSH_OK) { ssh_set_error_oom(session); goto error; @@ -434,13 +471,13 @@ static int grow_window(ssh_session session, goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, - "growing window (channel %d:%d) to %d bytes", + SSH_LOG(SSH_LOG_DEBUG, + "growing window (channel %" PRIu32 ":%" PRIu32 ") by %" PRIu32 " bytes", channel->local_channel, channel->remote_channel, - new_window); + increment); - channel->local_window = new_window; + channel->local_window += increment; return SSH_OK; error: @@ -462,7 +499,8 @@ error: * @return The related ssh_channel, or NULL if the channel is * unknown or the packet is invalid. */ -static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet) { +static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet) +{ ssh_channel channel; uint32_t chan; int rc; @@ -477,7 +515,7 @@ static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet) { channel = ssh_channel_from_local(session, chan); if (channel == NULL) { ssh_set_error(session, SSH_FATAL, - "Server specified invalid channel %"PRIu32, + "Server specified invalid channel %" PRIu32, (uint32_t) chan); } @@ -488,6 +526,8 @@ SSH_PACKET_CALLBACK(channel_rcv_change_window) { ssh_channel channel; uint32_t bytes; int rc; + bool was_empty; + (void)user; (void)type; @@ -504,123 +544,153 @@ SSH_PACKET_CALLBACK(channel_rcv_change_window) { return SSH_PACKET_USED; } - SSH_LOG(SSH_LOG_PROTOCOL, - "Adding %d bytes to channel (%d:%d) (from %d bytes)", + SSH_LOG(SSH_LOG_DEBUG, + "Adding %" PRIu32 " bytes to channel (%" PRIu32 ":%" PRIu32 ") (from %" PRIu32 " bytes)", bytes, channel->local_channel, channel->remote_channel, channel->remote_window); + was_empty = channel->remote_window == 0; + channel->remote_window += bytes; + /* Writing to the channel is non-blocking until the receive window is empty. + When the receive window becomes non-zero again, call channel_write_wontblock_function. */ + if (was_empty && bytes > 0) { + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_write_wontblock_function, + session, + channel, + channel->remote_window); + } + return SSH_PACKET_USED; } /* is_stderr is set to 1 if the data are extended, ie stderr */ -SSH_PACKET_CALLBACK(channel_rcv_data){ - ssh_channel channel; - ssh_string str; - ssh_buffer buf; - size_t len; - int is_stderr; - int rest; - (void)user; +SSH_PACKET_CALLBACK(channel_rcv_data) +{ + ssh_channel channel = NULL; + ssh_string str = NULL; + ssh_buffer buf = NULL; + void *data = NULL; + uint32_t len; + int extended, is_stderr = 0; + int rest; - if(type==SSH2_MSG_CHANNEL_DATA) - is_stderr=0; - else - is_stderr=1; + (void)user; - channel = channel_from_msg(session,packet); - if (channel == NULL) { - SSH_LOG(SSH_LOG_FUNCTIONS, - "%s", ssh_get_error(session)); + if (type == SSH2_MSG_CHANNEL_DATA) { + extended = 0; + } else { /* SSH_MSG_CHANNEL_EXTENDED_DATA */ + extended = 1; + } - return SSH_PACKET_USED; - } + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); - if (is_stderr) { - uint32_t ignore; - /* uint32 data type code. we can ignore it */ - ssh_buffer_get_u32(packet, &ignore); - } + return SSH_PACKET_USED; + } - str = ssh_buffer_get_ssh_string(packet); - if (str == NULL) { - SSH_LOG(SSH_LOG_PACKET, "Invalid data packet!"); + if (extended) { + uint32_t data_type_code, rc; + rc = ssh_buffer_get_u32(packet, &data_type_code); + if (rc != sizeof(uint32_t)) { + SSH_LOG(SSH_LOG_PACKET, + "Failed to read data type code: rc = %" PRIu32, rc); - return SSH_PACKET_USED; - } - len = ssh_string_len(str); + return SSH_PACKET_USED; + } + is_stderr = 1; + data_type_code = ntohl(data_type_code); + if (data_type_code != SSH2_EXTENDED_DATA_STDERR) { + SSH_LOG(SSH_LOG_PACKET, "Invalid data type code %" PRIu32 "!", + data_type_code); + } + } - SSH_LOG(SSH_LOG_PACKET, - "Channel receiving %zu bytes data in %d (local win=%d remote win=%d)", - len, - is_stderr, - channel->local_window, - channel->remote_window); + str = ssh_buffer_get_ssh_string(packet); + if (str == NULL) { + SSH_LOG(SSH_LOG_PACKET, "Invalid data packet!"); - /* What shall we do in this case? Let's accept it anyway */ - if (len > channel->local_window) { - SSH_LOG(SSH_LOG_RARE, - "Data packet too big for our window(%zu vs %d)", - len, - channel->local_window); - } + return SSH_PACKET_USED; + } + len = ssh_string_len(str); - if (channel_default_bufferize(channel, ssh_string_data(str), len, - is_stderr) < 0) { - SSH_STRING_FREE(str); + SSH_LOG(SSH_LOG_PACKET, + "Channel receiving %" PRIu32 " bytes data%s (local win=%" PRIu32 + " remote win=%" PRIu32 ")", + len, + is_stderr ? " in stderr" : "", + channel->local_window, + channel->remote_window); - return SSH_PACKET_USED; - } + if (len > channel->local_window) { + SSH_LOG(SSH_LOG_RARE, + "Data packet too big for our window(%" PRIu32 " vs %" PRIu32 ")", + len, + channel->local_window); + + SSH_STRING_FREE(str); + + ssh_set_error(session, SSH_FATAL, "Window exceeded"); + + return SSH_PACKET_USED; + } + + data = ssh_string_data(str); + if (channel_default_bufferize(channel, data, len, is_stderr) < 0) { + SSH_STRING_FREE(str); + + return SSH_PACKET_USED; + } - if (len <= channel->local_window) { channel->local_window -= len; - } else { - channel->local_window = 0; /* buggy remote */ - } - SSH_LOG(SSH_LOG_PACKET, - "Channel windows are now (local win=%d remote win=%d)", - channel->local_window, - channel->remote_window); + SSH_LOG(SSH_LOG_PACKET, + "Channel windows are now (local win=%" PRIu32 " remote win=%" PRIu32 ")", + channel->local_window, + channel->remote_window); - SSH_STRING_FREE(str); + SSH_STRING_FREE(str); - if (is_stderr) { - buf = channel->stderr_buffer; - } else { - buf = channel->stdout_buffer; - } + if (is_stderr) { + buf = channel->stderr_buffer; + } else { + buf = channel->stdout_buffer; + } - ssh_callbacks_iterate(channel->callbacks, - ssh_channel_callbacks, - channel_data_function) { - if (ssh_buffer_get(buf) == NULL) { - break; - } - rest = ssh_callbacks_iterate_exec(channel_data_function, - channel->session, - channel, - ssh_buffer_get(buf), - ssh_buffer_get_len(buf), - is_stderr); - if (rest > 0) { - if (channel->counter != NULL) { - channel->counter->in_bytes += rest; - } - ssh_buffer_pass_bytes(buf, rest); - } - } - ssh_callbacks_iterate_end(); + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_data_function) { + if (ssh_buffer_get(buf) == NULL) { + break; + } + rest = ssh_callbacks_iterate_exec(channel_data_function, + channel->session, + channel, + ssh_buffer_get(buf), + ssh_buffer_get_len(buf), + is_stderr); + if (rest > 0) { + int rc; + if (channel->counter != NULL) { + channel->counter->in_bytes += rest; + } + ssh_buffer_pass_bytes(buf, rest); - if (channel->local_window + ssh_buffer_get_len(buf) < WINDOWLIMIT) { - if (grow_window(session, channel, 0) < 0) { - return -1; - } - } - return SSH_PACKET_USED; + rc = grow_window(session, channel); + if (rc == SSH_ERROR) { + return -1; + } + } + } + ssh_callbacks_iterate_end(); + + return SSH_PACKET_USED; } SSH_PACKET_CALLBACK(channel_rcv_eof) { @@ -636,7 +706,7 @@ SSH_PACKET_CALLBACK(channel_rcv_eof) { } SSH_LOG(SSH_LOG_PACKET, - "Received eof on channel (%d:%d)", + "Received eof on channel (%" PRIu32 ":%" PRIu32 ")", channel->local_channel, channel->remote_channel); /* channel->remote_window = 0; */ @@ -681,7 +751,7 @@ SSH_PACKET_CALLBACK(channel_rcv_close) { } SSH_LOG(SSH_LOG_PACKET, - "Received close on channel (%d:%d)", + "Received close on channel (%" PRIu32 ":%" PRIu32 ")", channel->local_channel, channel->remote_channel); @@ -717,7 +787,7 @@ SSH_PACKET_CALLBACK(channel_rcv_close) { SSH_PACKET_CALLBACK(channel_rcv_request) { ssh_channel channel; char *request=NULL; - uint8_t status; + uint8_t want_reply; int rc; (void)user; (void)type; @@ -730,7 +800,7 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { rc = ssh_buffer_unpack(packet, "sb", &request, - &status); + &want_reply); if (rc != SSH_OK) { SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); return SSH_PACKET_USED; @@ -823,7 +893,7 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { } if(strcmp(request,"keepalive@openssh.com")==0){ SAFE_FREE(request); - SSH_LOG(SSH_LOG_PROTOCOL,"Responding to Openssh's keepalive"); + SSH_LOG(SSH_LOG_DEBUG,"Responding to Openssh's keepalive"); rc = ssh_buffer_pack(session->out_buffer, "bd", @@ -838,13 +908,34 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { } if (strcmp(request, "auth-agent-req@openssh.com") == 0) { + int status; + SAFE_FREE(request); - SSH_LOG(SSH_LOG_PROTOCOL, "Received an auth-agent-req request"); - ssh_callbacks_execute_list(channel->callbacks, - ssh_channel_callbacks, - channel_auth_agent_req_function, - channel->session, - channel); + SSH_LOG(SSH_LOG_DEBUG, "Received an auth-agent-req request"); + + status = SSH2_MSG_CHANNEL_FAILURE; + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_auth_agent_req_function) { + ssh_callbacks_iterate_exec(channel_auth_agent_req_function, + channel->session, + channel); + /* in lieu of a return value, if the callback exists it's supported */ + status = SSH2_MSG_CHANNEL_SUCCESS; + break; + } + ssh_callbacks_iterate_end(); + + if (want_reply) { + rc = ssh_buffer_pack(session->out_buffer, + "bd", + status, + channel->remote_channel); + if (rc != SSH_OK) { + return SSH_PACKET_USED; + } + ssh_packet_send(session); + } return SSH_PACKET_USED; } @@ -853,11 +944,11 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { * client requests. That means we need to create a ssh message to be passed * to the user code handling ssh messages */ - ssh_message_handle_channel_request(session,channel,packet,request,status); + ssh_message_handle_channel_request(session,channel,packet,request,want_reply); #else - SSH_LOG(SSH_LOG_WARNING, "Unhandled channel request %s", request); + SSH_LOG(SSH_LOG_DEBUG, "Unhandled channel request %s", request); #endif - + SAFE_FREE(request); return SSH_PACKET_USED; @@ -870,7 +961,7 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { * FIXME is the window changed? */ int channel_default_bufferize(ssh_channel channel, - void *data, size_t len, + void *data, uint32_t len, bool is_stderr) { ssh_session session; @@ -887,7 +978,7 @@ int channel_default_bufferize(ssh_channel channel, } SSH_LOG(SSH_LOG_PACKET, - "placing %zu bytes into channel buffer (%s)", + "placing %" PRIu32 " bytes into channel buffer (%s)", len, is_stderr ? "stderr" : "stdout"); if (!is_stderr) { @@ -942,14 +1033,15 @@ int channel_default_bufferize(ssh_channel channel, * @see ssh_channel_request_shell() * @see ssh_channel_request_exec() */ -int ssh_channel_open_session(ssh_channel channel) { - if(channel == NULL) { +int ssh_channel_open_session(ssh_channel channel) +{ + if (channel == NULL) { return SSH_ERROR; } return channel_open(channel, "session", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, NULL); } @@ -969,14 +1061,15 @@ int ssh_channel_open_session(ssh_channel channel) { * * @see ssh_channel_open_forward() */ -int ssh_channel_open_auth_agent(ssh_channel channel){ - if(channel == NULL) { +int ssh_channel_open_auth_agent(ssh_channel channel) +{ + if (channel == NULL) { return SSH_ERROR; } return channel_open(channel, "auth-agent@openssh.com", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, NULL); } @@ -1005,16 +1098,17 @@ int ssh_channel_open_auth_agent(ssh_channel channel){ * * @warning This function does not bind the local port and does not automatically * forward the content of a socket to the channel. You still have to - * use channel_read and channel_write for this. + * use ssh_channel_read and ssh_channel_write for this. */ int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, - int remoteport, const char *sourcehost, int localport) { + int remoteport, const char *sourcehost, int localport) +{ ssh_session session; ssh_buffer payload = NULL; ssh_string str = NULL; int rc = SSH_ERROR; - if(channel == NULL) { + if (channel == NULL) { return rc; } @@ -1044,7 +1138,7 @@ int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, rc = channel_open(channel, "direct-tcpip", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, payload); @@ -1076,7 +1170,7 @@ error: * * @warning This function does not bind the local port and does not * automatically forward the content of a socket to the channel. - * You still have to use channel_read and channel_write for this. + * You still have to use ssh_channel_read and ssh_channel_write for this. * @warning Requires support of OpenSSH for UNIX domain socket forwarding. */ int ssh_channel_open_forward_unix(ssh_channel channel, @@ -1127,7 +1221,7 @@ int ssh_channel_open_forward_unix(ssh_channel channel, rc = channel_open(channel, "direct-streamlocal@openssh.com", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, payload); @@ -1181,7 +1275,7 @@ void ssh_channel_free(ssh_channel channel) channel->flags |= SSH_CHANNEL_FLAG_FREED_LOCAL; /* The idea behind the flags is the following : it is well possible - * that a client closes a channel that stills exists on the server side. + * that a client closes a channel that still exists on the server side. * We definitively close the channel when we receive a close message *and* * the user closed it. */ @@ -1274,7 +1368,7 @@ int ssh_channel_send_eof(ssh_channel channel) rc = ssh_packet_send(session); SSH_LOG(SSH_LOG_PACKET, - "Sent a EOF on client channel (%d:%d)", + "Sent a EOF on client channel (%" PRIu32 ":%" PRIu32 ")", channel->local_channel, channel->remote_channel); if (rc != SSH_OK) { @@ -1339,7 +1433,7 @@ int ssh_channel_close(ssh_channel channel) rc = ssh_packet_send(session); SSH_LOG(SSH_LOG_PACKET, - "Sent a close on client channel (%d:%d)", + "Sent a close on client channel (%" PRIu32 ":%" PRIu32 ")", channel->local_channel, channel->remote_channel); @@ -1361,7 +1455,8 @@ error: } /* this termination function waits for a window growing condition */ -static int ssh_channel_waitwindow_termination(void *c){ +static int ssh_channel_waitwindow_termination(void *c) +{ ssh_channel channel = (ssh_channel) c; if (channel->remote_window > 0 || channel->session->session_state == SSH_SESSION_STATE_ERROR || @@ -1374,7 +1469,8 @@ static int ssh_channel_waitwindow_termination(void *c){ /* This termination function waits until the session is not in blocked status * anymore, e.g. because of a key re-exchange. */ -static int ssh_waitsession_unblocked(void *s){ +static int ssh_waitsession_unblocked(void *s) +{ ssh_session session = (ssh_session)s; switch (session->session_state){ case SSH_SESSION_STATE_DH: @@ -1394,8 +1490,9 @@ static int ssh_waitsession_unblocked(void *s){ * SSH_ERROR On error. * SSH_AGAIN Timeout elapsed (or in nonblocking mode). */ -int ssh_channel_flush(ssh_channel channel){ - return ssh_blocking_flush(channel->session, SSH_TIMEOUT_DEFAULT); +int ssh_channel_flush(ssh_channel channel) +{ + return ssh_blocking_flush(channel->session, SSH_TIMEOUT_DEFAULT); } static int channel_write_common(ssh_channel channel, @@ -1405,7 +1502,6 @@ static int channel_write_common(ssh_channel channel, ssh_session session; uint32_t origlen = len; size_t effectivelen; - size_t maxpacketlen; int rc; if(channel == NULL) { @@ -1418,20 +1514,14 @@ static int channel_write_common(ssh_channel channel, } if (len > INT_MAX) { - SSH_LOG(SSH_LOG_PROTOCOL, - "Length (%u) is bigger than INT_MAX", len); + SSH_LOG(SSH_LOG_TRACE, + "Length (%" PRIu32 ") is bigger than INT_MAX", len); return SSH_ERROR; } - /* - * Handle the max packet len from remote side, be nice - * 10 bytes for the headers - */ - maxpacketlen = channel->remote_maxpacket - 10; - if (channel->local_eof) { ssh_set_error(session, SSH_REQUEST_DENIED, - "Can't write to channel %d:%d after EOF was sent", + "Can't write to channel %" PRIu32 ":%" PRIu32 " after EOF was sent", channel->local_channel, channel->remote_channel); return -1; @@ -1455,14 +1545,14 @@ static int channel_write_common(ssh_channel channel, } while (len > 0) { if (channel->remote_window < len) { - SSH_LOG(SSH_LOG_PROTOCOL, - "Remote window is %d bytes. going to write %d bytes", + SSH_LOG(SSH_LOG_DEBUG, + "Remote window is %" PRIu32 " bytes. going to write %" PRIu32 " bytes", channel->remote_window, len); - /* What happens when the channel window is zero? */ + /* When the window is zero, wait for it to grow */ if(channel->remote_window == 0) { /* nothing can be written */ - SSH_LOG(SSH_LOG_PROTOCOL, + SSH_LOG(SSH_LOG_DEBUG, "Wait for a growing window message..."); rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_DEFAULT, ssh_channel_waitwindow_termination,channel); @@ -1473,12 +1563,17 @@ static int channel_write_common(ssh_channel channel, goto out; continue; } + /* When the window is non-zero, accept data up to the window size */ effectivelen = MIN(len, channel->remote_window); } else { effectivelen = len; } - effectivelen = MIN(effectivelen, maxpacketlen);; + /* + * Like OpenSSH, don't subtract bytes for the header fields + * and allow to send a payload of remote_maxpacket length. + */ + effectivelen = MIN(effectivelen, channel->remote_maxpacket); rc = ssh_buffer_pack(session->out_buffer, "bd", @@ -1516,7 +1611,7 @@ static int channel_write_common(ssh_channel channel, } SSH_LOG(SSH_LOG_PACKET, - "channel_write wrote %ld bytes", (long int) effectivelen); + "ssh_channel_write wrote %ld bytes", (long int) effectivelen); channel->remote_window -= effectivelen; len -= effectivelen; @@ -1544,7 +1639,7 @@ error: /** * @brief Get the remote window size. * - * This is the maximum amounts of bytes the remote side expects us to send + * This is the maximum amount of bytes the remote side expects us to send * before growing the window again. * * @param[in] channel The channel to query. @@ -1558,7 +1653,8 @@ error: * @warning A zero return value means ssh_channel_write (default settings) * will block until the window grows back. */ -uint32_t ssh_channel_window_size(ssh_channel channel) { +uint32_t ssh_channel_window_size(ssh_channel channel) +{ return channel->remote_window; } @@ -1575,8 +1671,9 @@ uint32_t ssh_channel_window_size(ssh_channel channel) { * * @see ssh_channel_read() */ -int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len) { - return channel_write_common(channel, data, len, 0); +int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len) +{ + return channel_write_common(channel, data, len, 0); } /** @@ -1588,8 +1685,9 @@ int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len) { * * @see ssh_channel_is_closed() */ -int ssh_channel_is_open(ssh_channel channel) { - if(channel == NULL) { +int ssh_channel_is_open(ssh_channel channel) +{ + if (channel == NULL) { return 0; } return (channel->state == SSH_CHANNEL_STATE_OPEN && channel->session->alive != 0); @@ -1604,8 +1702,9 @@ int ssh_channel_is_open(ssh_channel channel) { * * @see ssh_channel_is_open() */ -int ssh_channel_is_closed(ssh_channel channel) { - if(channel == NULL) { +int ssh_channel_is_closed(ssh_channel channel) +{ + if (channel == NULL) { return SSH_ERROR; } return (channel->state != SSH_CHANNEL_STATE_OPEN || channel->session->alive == 0); @@ -1618,15 +1717,16 @@ int ssh_channel_is_closed(ssh_channel channel) { * * @return 0 if there is no EOF, nonzero otherwise. */ -int ssh_channel_is_eof(ssh_channel channel) { - if(channel == NULL) { - return SSH_ERROR; - } - if (ssh_channel_has_unread_data(channel)) { - return 0; - } +int ssh_channel_is_eof(ssh_channel channel) +{ + if (channel == NULL) { + return SSH_ERROR; + } + if (ssh_channel_has_unread_data(channel)) { + return 0; + } - return (channel->remote_eof != 0); + return (channel->remote_eof != 0); } /** @@ -1640,11 +1740,12 @@ int ssh_channel_is_eof(ssh_channel channel) { * in non-blocking mode. * @see ssh_set_blocking() */ -void ssh_channel_set_blocking(ssh_channel channel, int blocking) { - if(channel == NULL) { - return; - } - ssh_set_blocking(channel->session,blocking); +void ssh_channel_set_blocking(ssh_channel channel, int blocking) +{ + if (channel == NULL) { + return; + } + ssh_set_blocking(channel->session, blocking); } /** @@ -1664,7 +1765,7 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_success){ } SSH_LOG(SSH_LOG_PACKET, - "Received SSH_CHANNEL_SUCCESS on channel (%d:%d)", + "Received SSH_CHANNEL_SUCCESS on channel (%" PRIu32 ":%" PRIu32 ")", channel->local_channel, channel->remote_channel); if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING){ @@ -1672,6 +1773,12 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_success){ channel->request_state); } else { channel->request_state=SSH_CHANNEL_REQ_STATE_ACCEPTED; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_request_response_function, + channel->session, + channel); } return SSH_PACKET_USED; @@ -1695,7 +1802,7 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_failure){ } SSH_LOG(SSH_LOG_PACKET, - "Received SSH_CHANNEL_FAILURE on channel (%d:%d)", + "Received SSH_CHANNEL_FAILURE on channel (%" PRIu32 ":%" PRIu32 ")", channel->local_channel, channel->remote_channel); if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING){ @@ -1703,12 +1810,19 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_failure){ channel->request_state); } else { channel->request_state=SSH_CHANNEL_REQ_STATE_DENIED; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_request_response_function, + channel->session, + channel); } return SSH_PACKET_USED; } -static int ssh_channel_request_termination(void *c){ +static int ssh_channel_request_termination(void *c) +{ ssh_channel channel = (ssh_channel)c; if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING || channel->session->session_state == SSH_SESSION_STATE_ERROR) @@ -1718,7 +1832,8 @@ static int ssh_channel_request_termination(void *c){ } static int channel_request(ssh_channel channel, const char *request, - ssh_buffer buffer, int reply) { + ssh_buffer buffer, int reply) +{ ssh_session session = channel->session; int rc = SSH_ERROR; int ret; @@ -1779,7 +1894,7 @@ pending: rc=SSH_ERROR; break; case SSH_CHANNEL_REQ_STATE_ACCEPTED: - SSH_LOG(SSH_LOG_PROTOCOL, + SSH_LOG(SSH_LOG_DEBUG, "Channel request %s success",request); rc=SSH_OK; break; @@ -1804,7 +1919,7 @@ error: /** * @brief Request a pty with a specific type and size. * - * @param[in] channel The channel to sent the request. + * @param[in] channel The channel to send the request. * * @param[in] terminal The terminal type ("vt100, xterm,..."). * @@ -1812,13 +1927,18 @@ error: * * @param[in] row The number of rows. * + * @param[in] modes Encoded SSH terminal modes for the PTY + * + * @param[in] modes_len Number of bytes in 'modes' + * * @return SSH_OK on success, * SSH_ERROR if an error occurred, * SSH_AGAIN if in nonblocking mode and call has * to be done again. */ -int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, - int col, int row) { +int ssh_channel_request_pty_size_modes(ssh_channel channel, const char *terminal, + int col, int row, const unsigned char* modes, size_t modes_len) +{ ssh_session session; ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -1847,14 +1967,14 @@ int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, } rc = ssh_buffer_pack(buffer, - "sdddddb", + "sdddddP", terminal, col, row, 0, /* pix */ 0, /* pix */ - 1, /* add a 0byte string */ - 0); + (uint32_t)modes_len, + modes_len, modes); if (rc != SSH_OK) { ssh_set_error_oom(session); @@ -1868,6 +1988,23 @@ error: return rc; } +int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, + int col, int row) +{ + /* use modes from the current TTY */ + unsigned char modes_buf[SSH_TTY_MODES_MAX_BUFSIZE]; + int rc = encode_current_tty_opts(modes_buf, sizeof(modes_buf)); + if (rc < 0) { + return rc; + } + return ssh_channel_request_pty_size_modes(channel, + terminal, + col, + row, + modes_buf, + (size_t)rc); +} + /** * @brief Request a PTY. * @@ -1880,7 +2017,8 @@ error: * * @see ssh_channel_request_pty_size() */ -int ssh_channel_request_pty(ssh_channel channel) { +int ssh_channel_request_pty(ssh_channel channel) +{ return ssh_channel_request_pty_size(channel, "xterm", 80, 24); } @@ -1896,10 +2034,11 @@ int ssh_channel_request_pty(ssh_channel channel) { * @return SSH_OK on success, SSH_ERROR if an error occurred. * * @warning Do not call it from a signal handler if you are not sure any other - * libssh function using the same channel/session is running at same - * time (not 100% threadsafe). + * libssh function using the same channel/session is running at the + * same time (not 100% threadsafe). */ -int ssh_channel_change_pty_size(ssh_channel channel, int cols, int rows) { +int ssh_channel_change_pty_size(ssh_channel channel, int cols, int rows) +{ ssh_session session = channel->session; ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -1938,8 +2077,9 @@ error: * SSH_AGAIN if in nonblocking mode and call has * to be done again. */ -int ssh_channel_request_shell(ssh_channel channel) { - if(channel == NULL) { +int ssh_channel_request_shell(ssh_channel channel) +{ + if (channel == NULL) { return SSH_ERROR; } @@ -1960,7 +2100,8 @@ int ssh_channel_request_shell(ssh_channel channel) { * * @warning You normally don't have to call it for sftp, see sftp_new(). */ -int ssh_channel_request_subsystem(ssh_channel channel, const char *subsys) { +int ssh_channel_request_subsystem(ssh_channel channel, const char *subsys) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -2009,14 +2150,16 @@ error: * * @note You should use sftp_new() which does this for you. */ -int ssh_channel_request_sftp( ssh_channel channel){ +int ssh_channel_request_sftp( ssh_channel channel) +{ if(channel == NULL) { return SSH_ERROR; } return ssh_channel_request_subsystem(channel, "sftp"); } -static char *generate_cookie(void) { +static char *generate_cookie(void) +{ static const char *hex = "0123456789abcdef"; char s[36]; unsigned char rnd[16]; @@ -2040,7 +2183,7 @@ static char *generate_cookie(void) { * @brief Sends the "x11-req" channel request over an existing session channel. * * This will enable redirecting the display of the remote X11 applications to - * local X server over an secure tunnel. + * local X server over a secure tunnel. * * @param[in] channel An existing session channel where the remote X11 * applications are going to be executed. @@ -2062,7 +2205,8 @@ static char *generate_cookie(void) { * to be done again. */ int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, - const char *cookie, int screen_number) { + const char *cookie, int screen_number) +{ ssh_buffer buffer = NULL; char *c = NULL; int rc = SSH_ERROR; @@ -2113,7 +2257,8 @@ error: } static ssh_channel ssh_channel_accept(ssh_session session, int channeltype, - int timeout_ms, int *destination_port) { + int timeout_ms, int *destination_port, char **originator, int *originator_port) +{ #ifndef _WIN32 static const struct timespec ts = { .tv_sec = 0, @@ -2147,6 +2292,12 @@ static ssh_channel ssh_channel_accept(ssh_session session, int channeltype, if(destination_port) { *destination_port=msg->channel_request_open.destination_port; } + if(originator) { + *originator=strdup(msg->channel_request_open.originator); + } + if(originator_port) { + *originator_port=msg->channel_request_open.originator_port; + } ssh_message_free(msg); return channel; @@ -2177,8 +2328,9 @@ static ssh_channel ssh_channel_accept(ssh_session session, int channeltype, * @return A newly created channel, or NULL if no X11 request from * the server. */ -ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms) { - return ssh_channel_accept(channel->session, SSH_CHANNEL_X11, timeout_ms, NULL); +ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms) +{ + return ssh_channel_accept(channel->session, SSH_CHANNEL_X11, timeout_ms, NULL, NULL, NULL); } /** @@ -2247,7 +2399,8 @@ SSH_PACKET_CALLBACK(ssh_request_denied){ } -static int ssh_global_request_termination(void *s){ +static int ssh_global_request_termination(void *s) +{ ssh_session session = (ssh_session) s; if (session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING || session->session_state == SSH_SESSION_STATE_ERROR) @@ -2336,7 +2489,7 @@ pending: } switch(session->global_req_state){ case SSH_CHANNEL_REQ_STATE_ACCEPTED: - SSH_LOG(SSH_LOG_PROTOCOL, "Global request %s success",request); + SSH_LOG(SSH_LOG_DEBUG, "Global request %s success",request); rc=SSH_OK; break; case SSH_CHANNEL_REQ_STATE_DENIED: @@ -2426,18 +2579,21 @@ error: } /* DEPRECATED */ -int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port) { +int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port) +{ return ssh_channel_listen_forward(session, address, port, bound_port); } /* DEPRECATED */ -ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms) { - return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, NULL); +ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms) +{ + return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, NULL, NULL, NULL); } /** - * @brief Accept an incoming TCP/IP forwarding channel and get information - * about incomming connection + * @brief Accept an incoming TCP/IP forwarding channel and get some information + * about incoming connection + * * @param[in] session The ssh session to use. * * @param[in] timeout_ms A timeout in milliseconds. @@ -2448,7 +2604,31 @@ ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms) { * the server */ ssh_channel ssh_channel_accept_forward(ssh_session session, int timeout_ms, int* destination_port) { - return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, destination_port); + return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, destination_port, NULL, NULL); +} + +/** + * @brief Accept an incoming TCP/IP forwarding channel and get information + * about incoming connection + * + * @param[in] session The ssh session to use. + * + * @param[in] timeout_ms A timeout in milliseconds. + * + * @param[out] destination_port A pointer to destination port or NULL. + * + * @param[out] originator A pointer to a pointer to a string of originator host or NULL. + * That the caller is responsible for to ssh_string_free_char(). + * + * @param[out] originator_port A pointer to originator port or NULL. + * + * @return Newly created channel, or NULL if no incoming channel request from + * the server + * + * @see ssh_string_free_char() + */ +ssh_channel ssh_channel_open_forward_port(ssh_session session, int timeout_ms, int *destination_port, char **originator, int *originator_port) { + return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, destination_port, originator, originator_port); } /** @@ -2498,7 +2678,8 @@ error: } /* DEPRECATED */ -int ssh_forward_cancel(ssh_session session, const char *address, int port) { +int ssh_forward_cancel(ssh_session session, const char *address, int port) +{ return ssh_channel_cancel_forward(session, address, port); } @@ -2517,7 +2698,8 @@ int ssh_forward_cancel(ssh_session session, const char *address, int port) { * to be done again. * @warning Some environment variables may be refused by security reasons. */ -int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value) { +int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -2587,7 +2769,8 @@ error: * * @see ssh_channel_request_shell() */ -int ssh_channel_request_exec(ssh_channel channel, const char *cmd) { +int ssh_channel_request_exec(ssh_channel channel, const char *cmd) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -2631,10 +2814,6 @@ error: * Sends a signal 'sig' to the remote process. * Note, that remote system may not support signals concept. * In such a case this request will be silently ignored. - * Only SSH-v2 is supported (I'm not sure about SSH-v1). - * - * OpenSSH doesn't support signals yet, see: - * https://bugzilla.mindrot.org/show_bug.cgi?id=1424 * * @param[in] channel The channel to send signal. * @@ -2654,17 +2833,17 @@ error: * SIGUSR1 -> USR1 \n * SIGUSR2 -> USR2 \n * - * @return SSH_OK on success, SSH_ERROR if an error occurred - * (including attempts to send signal via SSH-v1 session). + * @return SSH_OK on success, SSH_ERROR if an error occurred. */ -int ssh_channel_request_send_signal(ssh_channel channel, const char *sig) { +int ssh_channel_request_send_signal(ssh_channel channel, const char *sig) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; - if(channel == NULL) { + if (channel == NULL) { return SSH_ERROR; } - if(sig == NULL) { + if (sig == NULL) { ssh_set_error_invalid(channel->session); return rc; } @@ -2694,16 +2873,15 @@ error: * Sends a break signal to the remote process. * Note, that remote system may not support breaks. * In such a case this request will be silently ignored. - * Only SSH-v2 is supported. * * @param[in] channel The channel to send the break to. * * @param[in] length The break-length in milliseconds to send. * * @return SSH_OK on success, SSH_ERROR if an error occurred - * (including attempts to send signal via SSH-v1 session). */ -int ssh_channel_request_send_break(ssh_channel channel, uint32_t length) { +int ssh_channel_request_send_break(ssh_channel channel, uint32_t length) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -2736,7 +2914,7 @@ error: * * @param[in] channel The channel to read from. * - * @param[in] buffer The buffer which will get the data. + * @param[out] buffer The buffer which will get the data. * * @param[in] count The count of bytes to be read. If it is bigger than 0, * the exact size will be read, else (bytes=0) it will @@ -2751,7 +2929,8 @@ error: * @see ssh_channel_read */ int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, - int is_stderr) { + int is_stderr) +{ ssh_session session; char *buffer_tmp = NULL; int r; @@ -2822,13 +3001,13 @@ int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, struct ssh_channel_read_termination_struct { ssh_channel channel; - uint32_t count; ssh_buffer buffer; }; -static int ssh_channel_read_termination(void *s){ +static int ssh_channel_read_termination(void *s) +{ struct ssh_channel_read_termination_struct *ctx = s; - if (ssh_buffer_get_len(ctx->buffer) >= ctx->count || + if (ssh_buffer_get_len(ctx->buffer) >= 1 || ctx->channel->remote_eof || ctx->channel->session->session_state == SSH_SESSION_STATE_ERROR) return 1; @@ -2836,27 +3015,25 @@ static int ssh_channel_read_termination(void *s){ return 0; } -/* TODO FIXME Fix the blocking behaviours */ +/* TODO: FIXME Fix the blocking behaviours */ /** * @brief Reads data from a channel. * * @param[in] channel The channel to read from. * - * @param[in] dest The destination buffer which will get the data. + * @param[out] dest The destination buffer which will get the data. * * @param[in] count The count of bytes to be read. * * @param[in] is_stderr A boolean value to mark reading from the stderr flow. * * @return The number of bytes read, 0 on end of file or SSH_ERROR - * on error. In nonblocking mode it Can return 0 if no data + * on error. In nonblocking mode it can return 0 if no data * is available or SSH_AGAIN. * * @warning This function may return less than count bytes of data, and won't * block until count bytes have been read. - * @warning The read function using a buffer has been renamed to - * channel_read_buffer(). */ int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr) { @@ -2872,7 +3049,7 @@ int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_std * * @param[in] channel The channel to read from. * - * @param[in] dest The destination buffer which will get the data. + * @param[out] dest The destination buffer which will get the data. * * @param[in] count The count of bytes to be read. * @@ -2887,8 +3064,6 @@ int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_std * * @warning This function may return less than count bytes of data, and won't * block until count bytes have been read. - * @warning The read function using a buffer has been renamed to - * channel_read_buffer(). */ int ssh_channel_read_timeout(ssh_channel channel, void *dest, @@ -2921,28 +3096,17 @@ int ssh_channel_read_timeout(ssh_channel channel, stdbuf=channel->stderr_buffer; } - /* - * We may have problem if the window is too small to accept as much data - * as asked - */ SSH_LOG(SSH_LOG_PACKET, - "Read (%d) buffered : %d bytes. Window: %d", + "Read (%" PRIu32 ") buffered : %" PRIu32 " bytes. Window: %" PRIu32, count, ssh_buffer_get_len(stdbuf), channel->local_window); - if (count > ssh_buffer_get_len(stdbuf) + channel->local_window) { - if (grow_window(session, channel, count - ssh_buffer_get_len(stdbuf)) < 0) { - return -1; - } - } - /* block reading until at least one byte has been read * and ignore the trivial case count=0 */ ctx.channel = channel; ctx.buffer = stdbuf; - ctx.count = 1; if (timeout_ms < SSH_TIMEOUT_DEFAULT) { timeout_ms = SSH_TIMEOUT_INFINITE; @@ -2984,11 +3148,10 @@ int ssh_channel_read_timeout(ssh_channel channel, if (channel->delayed_close && !ssh_channel_has_unread_data(channel)) { channel->state = SSH_CHANNEL_STATE_CLOSED; } - /* Authorize some buffering while userapp is busy */ - if (channel->local_window < WINDOWLIMIT) { - if (grow_window(session, channel, 0) < 0) { - return -1; - } + + rc = grow_window(session, channel); + if (rc == SSH_ERROR) { + return -1; } return len; @@ -2998,20 +3161,18 @@ int ssh_channel_read_timeout(ssh_channel channel, * @brief Do a nonblocking read on the channel. * * A nonblocking read on the specified channel. it will return <= count bytes of - * data read atomically. + * data read atomically. It will also trigger any callbacks set on the channel. * * @param[in] channel The channel to read from. * - * @param[in] dest A pointer to a destination buffer. + * @param[out] dest A pointer to a destination buffer. * * @param[in] count The count of bytes of data to be read. * * @param[in] is_stderr A boolean to select the stderr stream. * - * @return The number of bytes read, 0 if nothing is available or - * SSH_ERROR on error. - * - * @warning Don't forget to check for EOF as it would return 0 here. + * @return The number of bytes read (0 if nothing is available), + * SSH_ERROR on error, and SSH_EOF if the channel is EOF. * * @see ssh_channel_is_eof() */ @@ -3021,7 +3182,7 @@ int ssh_channel_read_nonblocking(ssh_channel channel, int is_stderr) { ssh_session session; - ssize_t to_read; + uint32_t to_read; int rc; int blocking; @@ -3035,22 +3196,24 @@ int ssh_channel_read_nonblocking(ssh_channel channel, session = channel->session; - to_read = ssh_channel_poll(channel, is_stderr); + rc = ssh_channel_poll(channel, is_stderr); - if (to_read <= 0) { + if (rc <= 0) { if (session->session_state == SSH_SESSION_STATE_ERROR){ return SSH_ERROR; } - return to_read; /* may be an error code */ + return rc; /* may be an error code */ } - if ((size_t)to_read > count) { - to_read = (ssize_t)count; + to_read = (unsigned int)rc; + + if (to_read > count) { + to_read = count; } blocking = ssh_is_blocking(session); ssh_set_blocking(session, 0); - rc = ssh_channel_read(channel, dest, (uint32_t)to_read, is_stderr); + rc = ssh_channel_read(channel, dest, to_read, is_stderr); ssh_set_blocking(session,blocking); return rc; @@ -3065,15 +3228,18 @@ int ssh_channel_read_nonblocking(ssh_channel channel, * * @return The number of bytes available for reading, 0 if nothing * is available or SSH_ERROR on error. + * When a channel is freed the function returns + * SSH_ERROR immediately. * * @warning When the channel is in EOF state, the function returns SSH_EOF. * * @see ssh_channel_is_eof() */ -int ssh_channel_poll(ssh_channel channel, int is_stderr){ +int ssh_channel_poll(ssh_channel channel, int is_stderr) +{ ssh_buffer stdbuf; - if(channel == NULL) { + if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { return SSH_ERROR; } @@ -3119,6 +3285,7 @@ int ssh_channel_poll(ssh_channel channel, int is_stderr){ * SSH_ERROR on error. * * @warning When the channel is in EOF state, the function returns SSH_EOF. + * When a channel is freed the function returns SSH_ERROR immediately. * * @see ssh_channel_is_eof() */ @@ -3130,7 +3297,7 @@ int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr) size_t len; int rc; - if (channel == NULL) { + if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { return SSH_ERROR; } @@ -3142,7 +3309,6 @@ int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr) } ctx.buffer = stdbuf; ctx.channel = channel; - ctx.count = 1; rc = ssh_handle_packets_termination(channel->session, timeout, ssh_channel_read_termination, @@ -3182,15 +3348,17 @@ out: * * @return The session pointer. */ -ssh_session ssh_channel_get_session(ssh_channel channel) { - if(channel == NULL) { +ssh_session ssh_channel_get_session(ssh_channel channel) +{ + if (channel == NULL) { return NULL; } return channel->session; } -static int ssh_channel_exit_status_termination(void *c){ +static int ssh_channel_exit_status_termination(void *c) +{ ssh_channel channel = c; if(channel->exit_status != -1 || /* When a channel is closed, no exit status message can @@ -3212,15 +3380,18 @@ static int ssh_channel_exit_status_termination(void *c){ * (yet), or SSH_ERROR on error. * @warning This function may block until a timeout (or never) * if the other side is not willing to close the channel. + * When a channel is freed the function returns + * SSH_ERROR immediately. * * If you're looking for an async handling of this register a callback for the * exit status. * * @see ssh_channel_exit_status_callback */ -int ssh_channel_get_exit_status(ssh_channel channel) { +int ssh_channel_get_exit_status(ssh_channel channel) +{ int rc; - if(channel == NULL) { + if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { return SSH_ERROR; } rc = ssh_handle_packets_termination(channel->session, @@ -3242,8 +3413,11 @@ int ssh_channel_get_exit_status(ssh_channel channel) { * This is made in two parts: protocol select and network select. The protocol * select does not use the network functions at all */ -static int channel_protocol_select(ssh_channel *rchans, ssh_channel *wchans, - ssh_channel *echans, ssh_channel *rout, ssh_channel *wout, ssh_channel *eout) { +static int +channel_protocol_select(ssh_channel *rchans, ssh_channel *wchans, + ssh_channel *echans, ssh_channel *rout, + ssh_channel *wout, ssh_channel *eout) +{ ssh_channel chan; int i; int j = 0; @@ -3323,7 +3497,8 @@ static size_t count_ptrs(ssh_channel *ptrs) * function, or SSH_ERROR on error. */ int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, - ssh_channel *exceptchans, struct timeval * timeout) { + ssh_channel *exceptchans, struct timeval * timeout) +{ ssh_channel *rchans, *wchans, *echans; ssh_channel dummy = NULL; ssh_event event = NULL; @@ -3427,9 +3602,15 @@ int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, firstround=0; } while(1); - memcpy(readchans, rchans, (count_ptrs(rchans) + 1) * sizeof(ssh_channel )); - memcpy(writechans, wchans, (count_ptrs(wchans) + 1) * sizeof(ssh_channel )); - memcpy(exceptchans, echans, (count_ptrs(echans) + 1) * sizeof(ssh_channel )); + if (readchans != &dummy) { + memcpy(readchans, rchans, (count_ptrs(rchans) + 1) * sizeof(ssh_channel)); + } + if (writechans != &dummy) { + memcpy(writechans, wchans, (count_ptrs(wchans) + 1) * sizeof(ssh_channel)); + } + if (exceptchans != &dummy) { + memcpy(exceptchans, echans, (count_ptrs(echans) + 1) * sizeof(ssh_channel)); + } SAFE_FREE(rchans); SAFE_FREE(wchans); SAFE_FREE(echans); @@ -3457,7 +3638,8 @@ int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, * @param[in] counter Counter for bytes handled by the channel. */ void ssh_channel_set_counter(ssh_channel channel, - ssh_counter counter) { + ssh_counter counter) +{ if (channel != NULL) { channel->counter = counter; } @@ -3476,8 +3658,9 @@ void ssh_channel_set_counter(ssh_channel channel, * * @see ssh_channel_read() */ -int ssh_channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) { - return channel_write_common(channel, data, len, 1); +int ssh_channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) +{ + return channel_write_common(channel, data, len, 1); } #if WITH_SERVER @@ -3504,10 +3687,11 @@ int ssh_channel_write_stderr(ssh_channel channel, const void *data, uint32_t len * * @warning This function does not bind the local port and does not automatically * forward the content of a socket to the channel. You still have to - * use channel_read and channel_write for this. + * use ssh_channel_read and ssh_channel_write for this. */ int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost, - int remoteport, const char *sourcehost, int localport) { + int remoteport, const char *sourcehost, int localport) +{ ssh_session session; ssh_buffer payload = NULL; int rc = SSH_ERROR; @@ -3542,7 +3726,7 @@ int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost pending: rc = channel_open(channel, "forwarded-tcpip", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, payload); @@ -3567,10 +3751,11 @@ error: * to be done again. * @warning This function does not bind the local port and does not automatically * forward the content of a socket to the channel. You still have to - * use channel_read and channel_write for this. + * use shh_channel_read and ssh_channel_write for this. */ -int ssh_channel_open_x11(ssh_channel channel, - const char *orig_addr, int orig_port) { +int ssh_channel_open_x11(ssh_channel channel, + const char *orig_addr, int orig_port) +{ ssh_session session; ssh_buffer payload = NULL; int rc = SSH_ERROR; @@ -3604,7 +3789,7 @@ int ssh_channel_open_x11(ssh_channel channel, pending: rc = channel_open(channel, "x11", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, payload); @@ -3619,16 +3804,15 @@ error: * * Sends the exit status to the remote process (as described in RFC 4254, * section 6.10). - * Only SSH-v2 is supported (I'm not sure about SSH-v1). * * @param[in] channel The channel to send exit status. * * @param[in] exit_status The exit status to send * * @return SSH_OK on success, SSH_ERROR if an error occurred. - * (including attempts to send exit status via SSH-v1 session). */ -int ssh_channel_request_send_exit_status(ssh_channel channel, int exit_status) { +int ssh_channel_request_send_exit_status(ssh_channel channel, int exit_status) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -3660,7 +3844,6 @@ error: * This sends the exit status of the remote process. * Note, that remote system may not support signals concept. * In such a case this request will be silently ignored. - * Only SSH-v2 is supported (I'm not sure about SSH-v1). * * @param[in] channel The channel to send signal. * @@ -3671,10 +3854,10 @@ error: * @param[in] lang The language used in the message (format: RFC 3066) * * @return SSH_OK on success, SSH_ERROR if an error occurred - * (including attempts to send signal via SSH-v1 session). */ int ssh_channel_request_send_exit_signal(ssh_channel channel, const char *sig, - int core, const char *errmsg, const char *lang) { + int core, const char *errmsg, const char *lang) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -3711,4 +3894,4 @@ error: #endif -/* @} */ +/** @} */ diff --git a/src/client.c b/src/client.c index 292c919e..b3d0fe62 100644 --- a/src/client.c +++ b/src/client.c @@ -60,7 +60,8 @@ * @param code one of SSH_SOCKET_CONNECTED_OK or SSH_SOCKET_CONNECTED_ERROR * @param user is a pointer to session */ -static void socket_callback_connected(int code, int errno_code, void *user){ +static void socket_callback_connected(int code, int errno_code, void *user) +{ ssh_session session=(ssh_session)user; if (session->session_state != SSH_SESSION_STATE_CONNECTING && @@ -72,12 +73,14 @@ static void socket_callback_connected(int code, int errno_code, void *user){ return; } - SSH_LOG(SSH_LOG_RARE,"Socket connection callback: %d (%d)",code, errno_code); + SSH_LOG(SSH_LOG_TRACE,"Socket connection callback: %d (%d)",code, errno_code); if(code == SSH_SOCKET_CONNECTED_OK) session->session_state=SSH_SESSION_STATE_SOCKET_CONNECTED; else { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; session->session_state=SSH_SESSION_STATE_ERROR; - ssh_set_error(session,SSH_FATAL,"%s",strerror(errno_code)); + ssh_set_error(session,SSH_FATAL,"%s", + ssh_strerror(errno_code, err_msg, SSH_ERRNO_MSG_MAX)); } session->ssh_connection_callback(session); } @@ -93,12 +96,12 @@ static void socket_callback_connected(int code, int errno_code, void *user){ * @param user is a pointer to session * @returns Number of bytes processed, or zero if the banner is not complete. */ -static int callback_receive_banner(const void *data, size_t len, void *user) +static size_t callback_receive_banner(const void *data, size_t len, void *user) { char *buffer = (char *)data; - ssh_session session=(ssh_session) user; + ssh_session session = (ssh_session) user; char *str = NULL; - size_t i; + uint32_t i; int ret=0; if (session->session_state != SSH_SESSION_STATE_SOCKET_CONNECTED) { @@ -106,7 +109,7 @@ static int callback_receive_banner(const void *data, size_t len, void *user) "Wrong state in callback_receive_banner : %d", session->session_state); - return SSH_ERROR; + return 0; } for (i = 0; i < len; ++i) { #ifdef WITH_PCAP @@ -243,10 +246,13 @@ end: * @warning this function returning is no proof that DH handshake is * completed */ -static int dh_handshake(ssh_session session) { - +int dh_handshake(ssh_session session) +{ int rc = SSH_AGAIN; + SSH_LOG(SSH_LOG_TRACE, "dh_handshake_state = %d, kex_type = %d", + session->dh_handshake_state, session->next_crypto->kex_type); + switch (session->dh_handshake_state) { case DH_STATE_INIT: switch(session->next_crypto->kex_type){ @@ -299,18 +305,25 @@ static int dh_handshake(ssh_session session) { return rc; } -static int ssh_service_request_termination(void *s){ - ssh_session session = (ssh_session)s; - if(session->session_state == SSH_SESSION_STATE_ERROR || - session->auth.service_state != SSH_AUTH_SERVICE_SENT) - return 1; - else - return 0; +static int ssh_service_request_termination(void *s) +{ + ssh_session session = (ssh_session)s; + + if (session->session_state == SSH_SESSION_STATE_ERROR || + session->auth.service_state != SSH_AUTH_SERVICE_SENT) + return 1; + else + return 0; } /** - * @internal + * @addtogroup libssh_session * + * @{ + */ + +/** + * @internal * @brief Request a service from the SSH server. * * Service requests are for example: ssh-userauth, ssh-connection, etc. @@ -323,8 +336,9 @@ static int ssh_service_request_termination(void *s){ * @return SSH_AGAIN No response received yet * @bug actually only works with ssh-userauth */ -int ssh_service_request(ssh_session session, const char *service) { - int rc=SSH_ERROR; +int ssh_service_request(ssh_session session, const char *service) +{ + int rc = SSH_ERROR; if(session->auth.service_state != SSH_AUTH_SERVICE_NONE) goto pending; @@ -371,12 +385,6 @@ pending: } /** - * @addtogroup libssh_session - * - * @{ - */ - -/** * @internal * * @brief A function to be called each time a step has been done in the @@ -386,111 +394,117 @@ static void ssh_client_connection_callback(ssh_session session) { int rc; - switch(session->session_state) { - case SSH_SESSION_STATE_NONE: - case SSH_SESSION_STATE_CONNECTING: - break; - case SSH_SESSION_STATE_SOCKET_CONNECTED: - ssh_set_fd_towrite(session); - ssh_send_banner(session, 0); + SSH_LOG(SSH_LOG_DEBUG, "session_state=%d", session->session_state); - break; - case SSH_SESSION_STATE_BANNER_RECEIVED: - if (session->serverbanner == NULL) { - goto error; - } - set_status(session, 0.4f); - SSH_LOG(SSH_LOG_PROTOCOL, - "SSH server banner: %s", session->serverbanner); + switch (session->session_state) { + case SSH_SESSION_STATE_NONE: + case SSH_SESSION_STATE_CONNECTING: + break; + case SSH_SESSION_STATE_SOCKET_CONNECTED: + ssh_set_fd_towrite(session); + ssh_send_banner(session, 0); - /* Here we analyze the different protocols the server allows. */ - rc = ssh_analyze_banner(session, 0); - if (rc < 0) { - ssh_set_error(session, SSH_FATAL, - "No version of SSH protocol usable (banner: %s)", - session->serverbanner); - goto error; - } + break; + case SSH_SESSION_STATE_BANNER_RECEIVED: + if (session->serverbanner == NULL) { + goto error; + } + set_status(session, 0.4f); + SSH_LOG(SSH_LOG_DEBUG, "SSH server banner: %s", session->serverbanner); + + /* 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_register_socket_callback(session, session->socket); - ssh_packet_set_default_callbacks(session); - session->session_state = SSH_SESSION_STATE_INITIAL_KEX; + ssh_packet_set_default_callbacks(session); + session->session_state = SSH_SESSION_STATE_INITIAL_KEX; + rc = ssh_set_client_kex(session); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_send_kex(session); + if (rc < 0) { + goto error; + } + set_status(session, 0.5f); + + break; + case SSH_SESSION_STATE_INITIAL_KEX: + /* TODO: This state should disappear in favor of get_key handle */ + break; + case SSH_SESSION_STATE_KEXINIT_RECEIVED: + set_status(session, 0.6f); + ssh_list_kex(&session->next_crypto->server_kex); + if ((session->flags & SSH_SESSION_FLAG_KEXINIT_SENT) == 0) { + /* in rekeying state if next_crypto client_kex might be empty */ rc = ssh_set_client_kex(session); if (rc != SSH_OK) { goto error; } - rc = ssh_send_kex(session, 0); + rc = ssh_send_kex(session); if (rc < 0) { goto error; } - set_status(session, 0.5f); - - break; - case SSH_SESSION_STATE_INITIAL_KEX: - /* TODO: This state should disappear in favor of get_key handle */ - break; - case SSH_SESSION_STATE_KEXINIT_RECEIVED: - set_status(session,0.6f); - ssh_list_kex(&session->next_crypto->server_kex); - if (session->next_crypto->client_kex.methods[0] == NULL) { - /* in rekeying state if next_crypto client_kex is empty */ - rc = ssh_set_client_kex(session); - if (rc != SSH_OK) { - goto error; - } - rc = ssh_send_kex(session, 0); - if (rc < 0) { - goto error; - } - } - if (ssh_kex_select_methods(session) == SSH_ERROR) - goto error; - set_status(session,0.8f); - session->session_state=SSH_SESSION_STATE_DH; - if (dh_handshake(session) == SSH_ERROR) { - goto error; - } - FALL_THROUGH; - case SSH_SESSION_STATE_DH: - if(session->dh_handshake_state==DH_STATE_FINISHED){ - set_status(session,1.0f); - session->connected = 1; - if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) - session->session_state = SSH_SESSION_STATE_AUTHENTICATED; - else - session->session_state=SSH_SESSION_STATE_AUTHENTICATING; - } - break; - case SSH_SESSION_STATE_AUTHENTICATING: - break; - case SSH_SESSION_STATE_ERROR: + } + if (ssh_kex_select_methods(session) == SSH_ERROR) goto error; - default: - ssh_set_error(session,SSH_FATAL,"Invalid state %d",session->session_state); + set_status(session, 0.8f); + session->session_state = SSH_SESSION_STATE_DH; + + /* If the init packet was already sent in previous step, this will be no + * operation */ + if (dh_handshake(session) == SSH_ERROR) { + goto error; + } + FALL_THROUGH; + case SSH_SESSION_STATE_DH: + if (session->dh_handshake_state == DH_STATE_FINISHED) { + set_status(session, 1.0f); + session->connected = 1; + if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) { + session->session_state = SSH_SESSION_STATE_AUTHENTICATED; + } else { + session->session_state = SSH_SESSION_STATE_AUTHENTICATING; + } + } + break; + case SSH_SESSION_STATE_AUTHENTICATING: + break; + case SSH_SESSION_STATE_ERROR: + goto error; + default: + ssh_set_error(session, SSH_FATAL, "Invalid state %d", + session->session_state); } return; error: - ssh_socket_close(session->socket); - session->alive = 0; - session->session_state=SSH_SESSION_STATE_ERROR; - + ssh_session_socket_close(session); + SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(session)); } /** @internal * @brief describe under which conditions the ssh_connect function may stop */ -static int ssh_connect_termination(void *user){ - ssh_session session = (ssh_session)user; - switch(session->session_state){ +static int ssh_connect_termination(void *user) +{ + ssh_session session = (ssh_session)user; + + switch (session->session_state) { case SSH_SESSION_STATE_ERROR: case SSH_SESSION_STATE_AUTHENTICATING: case SSH_SESSION_STATE_DISCONNECTED: - return 1; + return 1; default: - return 0; - } + return 0; + } } /** @@ -558,7 +572,7 @@ int ssh_connect(ssh_session session) return SSH_ERROR; } - SSH_LOG(SSH_LOG_PROTOCOL, + SSH_LOG(SSH_LOG_DEBUG, "libssh %s, using threading %s", ssh_copyright(), ssh_threads_get_type()); @@ -593,7 +607,7 @@ int ssh_connect(ssh_session session) set_status(session, 0.2f); session->alive = 1; - SSH_LOG(SSH_LOG_PROTOCOL, + SSH_LOG(SSH_LOG_DEBUG, "Socket connecting, now waiting for the callbacks to work"); pending: @@ -649,12 +663,13 @@ pending: * * @return A newly allocated string with the banner, NULL on error. */ -char *ssh_get_issue_banner(ssh_session session) { - if (session == NULL || session->banner == NULL) { - return NULL; - } +char *ssh_get_issue_banner(ssh_session session) +{ + if (session == NULL || session->banner == NULL) { + return NULL; + } - return ssh_string_to_char(session->banner); + return ssh_string_to_char(session->banner); } /** @@ -675,18 +690,79 @@ char *ssh_get_issue_banner(ssh_session session) { * } * @endcode */ -int ssh_get_openssh_version(ssh_session session) { - if (session == NULL) { - return 0; - } +int ssh_get_openssh_version(ssh_session session) +{ + if (session == NULL) { + return 0; + } - return session->openssh; + return session->openssh; } /** + * @brief Most SSH connections will only ever request a single session, but an + * attacker may abuse a running ssh client to surreptitiously open + * additional sessions under their control. OpenSSH provides a global + * request "no-more-sessions@openssh.com" to mitigate this attack. + * + * @param[in] session The SSH session to use. + * + * @returns SSH_OK on success, SSH_ERROR on error. + * @returns SSH_AGAIN, if the session is in nonblocking mode, + * and call must be done again. + */ +int ssh_request_no_more_sessions(ssh_session session) +{ + if (session == NULL) { + return SSH_ERROR; + } + + return ssh_global_request(session, "no-more-sessions@openssh.com", NULL, 1); +} + +/** + * @brief Add disconnect message when ssh_session is disconnected + * To add a disconnect message to give peer a better hint. + * @param session The SSH session to use. + * @param message The message to send after the session is disconnected. + * If no message is passed then a default message i.e + * "Bye Bye" will be sent. + */ +int +ssh_session_set_disconnect_message(ssh_session session, const char *message) +{ + if (session == NULL) { + return SSH_ERROR; + } + + if (message == NULL || strlen(message) == 0) { + SAFE_FREE(session->disconnect_message); //To free any message set earlier. + session->disconnect_message = strdup("Bye Bye") ; + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + return SSH_OK; + } + SAFE_FREE(session->disconnect_message); //To free any message set earlier. + session->disconnect_message = strdup(message); + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + return SSH_OK; +} + + +/** * @brief Disconnect from a session (client or server). + * * The session can then be reused to open a new session. * + * @note Note that this function won't close the socket if it was set with + * ssh_options_set and SSH_OPTIONS_FD. You're responsible for closing the + * socket. This is new behavior in libssh 0.10. + * * @param[in] session The SSH session to use. */ void @@ -699,12 +775,20 @@ ssh_disconnect(ssh_session session) return; } + if (session->disconnect_message == NULL) { + session->disconnect_message = strdup("Bye Bye") ; + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + goto error; + } + } + if (session->socket != NULL && ssh_socket_is_open(session->socket)) { rc = ssh_buffer_pack(session->out_buffer, "bdss", SSH2_MSG_DISCONNECT, SSH2_DISCONNECT_BY_APPLICATION, - "Bye Bye", + session->disconnect_message, ""); /* language tag */ if (rc != SSH_OK) { ssh_set_error_oom(session); @@ -712,7 +796,7 @@ ssh_disconnect(ssh_session session) } ssh_packet_send(session); - ssh_socket_close(session->socket); + ssh_session_socket_close(session); } error: @@ -756,6 +840,7 @@ error: session->auth.supported_methods = 0; SAFE_FREE(session->serverbanner); SAFE_FREE(session->clientbanner); + SAFE_FREE(session->disconnect_message); if (session->ssh_message_list) { ssh_message msg = NULL; @@ -774,8 +859,16 @@ error: } } -const char *ssh_copyright(void) { - return SSH_STRINGIFY(LIBSSH_VERSION) " (c) 2003-2021 " +/** + * @brief Copyright information + * + * Returns copyright information + * + * @returns SSH_STRING copyright + */ +const char *ssh_copyright(void) +{ + return SSH_STRINGIFY(LIBSSH_VERSION) " (c) 2003-2024 " "Aris Adamantiadis, Andreas Schneider " "and libssh contributors. " "Distributed under the LGPL, please refer to COPYING " diff --git a/src/config.c b/src/config.c index a082e994..7135c3b1 100644 --- a/src/config.c +++ b/src/config.c @@ -68,7 +68,6 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "macs", SOC_MACS }, { "compression", SOC_COMPRESSION }, { "connecttimeout", SOC_TIMEOUT }, - { "protocol", SOC_PROTOCOL }, { "stricthostkeychecking", SOC_STRICTHOSTKEYCHECK }, { "userknownhostsfile", SOC_KNOWNHOSTS }, { "proxycommand", SOC_PROXYCOMMAND }, @@ -81,7 +80,6 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "loglevel", SOC_LOGLEVEL}, { "hostkeyalgorithms", SOC_HOSTKEYALGORITHMS}, { "kexalgorithms", SOC_KEXALGORITHMS}, - { "mac", SOC_UNSUPPORTED}, /* SSHv1 */ { "gssapiauthentication", SOC_GSSAPIAUTHENTICATION}, { "kbdinteractiveauthentication", SOC_KBDINTERACTIVEAUTHENTICATION}, { "passwordauthentication", SOC_PASSWORDAUTHENTICATION}, @@ -94,24 +92,19 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "canonicalizehostname", SOC_UNSUPPORTED}, { "canonicalizemaxdots", SOC_UNSUPPORTED}, { "canonicalizepermittedcnames", SOC_UNSUPPORTED}, - { "certificatefile", SOC_UNSUPPORTED}, - { "challengeresponseauthentication", SOC_UNSUPPORTED}, + { "certificatefile", SOC_CERTIFICATE}, + { "kbdinteractiveauthentication", SOC_UNSUPPORTED}, { "checkhostip", SOC_UNSUPPORTED}, - { "cipher", SOC_UNSUPPORTED}, /* SSHv1 */ - { "compressionlevel", SOC_UNSUPPORTED}, /* SSHv1 */ { "connectionattempts", SOC_UNSUPPORTED}, { "enablesshkeysign", SOC_UNSUPPORTED}, { "fingerprinthash", SOC_UNSUPPORTED}, { "forwardagent", SOC_UNSUPPORTED}, - { "gssapikeyexchange", SOC_UNSUPPORTED}, - { "gssapirenewalforcesrekey", SOC_UNSUPPORTED}, - { "gssapitrustdns", SOC_UNSUPPORTED}, { "hashknownhosts", SOC_UNSUPPORTED}, { "hostbasedauthentication", SOC_UNSUPPORTED}, - { "hostbasedkeytypes", SOC_UNSUPPORTED}, + { "hostbasedacceptedalgorithms", SOC_UNSUPPORTED}, { "hostkeyalias", SOC_UNSUPPORTED}, - { "identitiesonly", SOC_UNSUPPORTED}, - { "identityagent", SOC_UNSUPPORTED}, + { "identitiesonly", SOC_IDENTITIESONLY}, + { "identityagent", SOC_IDENTITYAGENT}, { "ipqos", SOC_UNSUPPORTED}, { "kbdinteractivedevices", SOC_UNSUPPORTED}, { "nohostauthenticationforlocalhost", SOC_UNSUPPORTED}, @@ -120,12 +113,10 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "preferredauthentications", SOC_UNSUPPORTED}, { "proxyjump", SOC_PROXYJUMP}, { "proxyusefdpass", SOC_UNSUPPORTED}, - { "pubkeyacceptedtypes", SOC_PUBKEYACCEPTEDTYPES}, + { "pubkeyacceptedalgorithms", SOC_PUBKEYACCEPTEDKEYTYPES}, { "rekeylimit", SOC_REKEYLIMIT}, { "remotecommand", SOC_UNSUPPORTED}, { "revokedhostkeys", SOC_UNSUPPORTED}, - { "rhostsrsaauthentication", SOC_UNSUPPORTED}, - { "rsaauthentication", SOC_UNSUPPORTED}, /* SSHv1 */ { "serveralivecountmax", SOC_UNSUPPORTED}, { "serveraliveinterval", SOC_UNSUPPORTED}, { "streamlocalbindmask", SOC_UNSUPPORTED}, @@ -133,13 +124,12 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "syslogfacility", SOC_UNSUPPORTED}, { "tcpkeepalive", SOC_UNSUPPORTED}, { "updatehostkeys", SOC_UNSUPPORTED}, - { "useprivilegedport", SOC_UNSUPPORTED}, { "verifyhostkeydns", SOC_UNSUPPORTED}, { "visualhostkey", SOC_UNSUPPORTED}, { "clearallforwardings", SOC_NA}, - { "controlmaster", SOC_NA}, + { "controlmaster", SOC_CONTROLMASTER}, { "controlpersist", SOC_NA}, - { "controlpath", SOC_NA}, + { "controlpath", SOC_CONTROLPATH}, { "dynamicforward", SOC_NA}, { "escapechar", SOC_NA}, { "exitonforwardfailure", SOC_NA}, @@ -157,7 +147,7 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "tunnel", SOC_NA}, { "tunneldevice", SOC_NA}, { "xauthlocation", SOC_NA}, - { "pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDTYPES}, + { "pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDKEYTYPES}, { NULL, SOC_UNKNOWN } }; @@ -191,7 +181,7 @@ static struct ssh_config_match_keyword_table_s ssh_config_match_keyword_table[] }; static int ssh_config_parse_line(ssh_session session, const char *line, - unsigned int count, int *parsing); + unsigned int count, int *parsing, unsigned int depth, bool global); static enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) { int i; @@ -205,16 +195,26 @@ static enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) { return SOC_UNKNOWN; } +#define LIBSSH_CONF_MAX_DEPTH 16 static void local_parse_file(ssh_session session, const char *filename, - int *parsing) + int *parsing, + unsigned int depth, + bool global) { FILE *f; char line[MAX_LINE_SIZE] = {0}; unsigned int count = 0; int rv; + if (depth > LIBSSH_CONF_MAX_DEPTH) { + ssh_set_error(session, SSH_FATAL, + "ERROR - Too many levels of configuration includes " + "when processing file '%s'", filename); + return; + } + f = fopen(filename, "r"); if (f == NULL) { SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", @@ -225,7 +225,7 @@ local_parse_file(ssh_session session, SSH_LOG(SSH_LOG_PACKET, "Reading additional configuration data from %s", filename); while (fgets(line, sizeof(line), f)) { count++; - rv = ssh_config_parse_line(session, line, count, parsing); + rv = ssh_config_parse_line(session, line, count, parsing, depth, global); if (rv < 0) { fclose(f); return; @@ -239,7 +239,9 @@ local_parse_file(ssh_session session, #if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) static void local_parse_glob(ssh_session session, const char *fileglob, - int *parsing) + int *parsing, + unsigned int depth, + bool global) { glob_t globbuf = { .gl_flags = 0, @@ -259,7 +261,7 @@ static void local_parse_glob(ssh_session session, } for (i = 0; i < globbuf.gl_pathc; i++) { - local_parse_file(session, globbuf.gl_pathv[i], parsing); + local_parse_file(session, globbuf.gl_pathv[i], parsing, depth, global); } globfree(&globbuf); @@ -317,6 +319,7 @@ ssh_exec_shell(char *cmd) char *shell = NULL; pid_t pid; int status, devnull, rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; shell = getenv("SHELL"); if (shell == NULL || shell[0] == '\0') { @@ -332,7 +335,8 @@ ssh_exec_shell(char *cmd) /* Need this to redirect subprocess stdin/out */ devnull = open("/dev/null", O_RDWR); if (devnull == -1) { - SSH_LOG(SSH_LOG_WARN, "Failed to open(/dev/null): %s", strerror(errno)); + SSH_LOG(SSH_LOG_WARN, "Failed to open(/dev/null): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return -1; } @@ -344,13 +348,15 @@ ssh_exec_shell(char *cmd) /* Redirect child stdin and stdout. Leave stderr */ rc = dup2(devnull, STDIN_FILENO); if (rc == -1) { - SSH_LOG(SSH_LOG_WARN, "dup2: %s", strerror(errno)); + SSH_LOG(SSH_LOG_WARN, "dup2: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); exit(1); } rc = dup2(devnull, STDOUT_FILENO); if (rc == -1) { - SSH_LOG(SSH_LOG_WARN, "dup2: %s", strerror(errno)); - exit(1); + SSH_LOG(SSH_LOG_WARN, "dup2: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + exit(1); } if (devnull > STDERR_FILENO) { close(devnull); @@ -364,7 +370,7 @@ ssh_exec_shell(char *cmd) rc = execv(argv[0], argv); if (rc == -1) { SSH_LOG(SSH_LOG_WARN, "Failed to execute command '%s': %s", cmd, - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); /* Die with signal to make this error apparent to parent. */ signal(SIGTERM, SIG_DFL); kill(getpid(), SIGTERM); @@ -375,19 +381,21 @@ ssh_exec_shell(char *cmd) /* Parent */ close(devnull); if (pid == -1) { /* Error */ - SSH_LOG(SSH_LOG_WARN, "Failed to fork child: %s", strerror(errno)); + SSH_LOG(SSH_LOG_WARN, "Failed to fork child: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return -1; } while (waitpid(pid, &status, 0) == -1) { if (errno != EINTR) { - SSH_LOG(SSH_LOG_WARN, "waitpid failed: %s", strerror(errno)); + SSH_LOG(SSH_LOG_WARN, "waitpid failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return -1; } } if (!WIFEXITED(status)) { - SSH_LOG(SSH_LOG_WARN, "Command %s exitted abnormally", cmd); + SSH_LOG(SSH_LOG_WARN, "Command %s exited abnormally", cmd); return -1; } SSH_LOG(SSH_LOG_TRACE, "Command '%s' returned %d", cmd, WEXITSTATUS(status)); @@ -456,7 +464,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) } if (parse_entry) { /* We actually care only about the first item */ - rv = ssh_config_parse_uri(cp, &username, &hostname, &port); + rv = ssh_config_parse_uri(cp, &username, &hostname, &port, false); /* The rest of the list needs to be passed on */ if (endp != NULL) { next = strdup(endp + 1); @@ -467,7 +475,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) } } else { /* The rest is just sanity-checked to avoid failures later */ - rv = ssh_config_parse_uri(cp, NULL, NULL, NULL); + rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false); } if (rv != SSH_OK) { goto out; @@ -483,7 +491,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) if (hostname != NULL && do_parsing) { char com[512] = {0}; - rv = snprintf(com, sizeof(com), "ssh%s%s%s%s%s%s -W [%%h]:%%p %s", + rv = snprintf(com, sizeof(com), "ssh%s%s%s%s%s%s -W '[%%h]:%%p' %s", username ? " -l " : "", username ? username : "", port ? " -p " : "", @@ -492,7 +500,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) next ? next : "", hostname); if (rv < 0 || rv >= (int)sizeof(com)) { - SSH_LOG(SSH_LOG_WARN, "Too long ProxyJump configuration line"); + SSH_LOG(SSH_LOG_TRACE, "Too long ProxyJump configuration line"); rv = SSH_ERROR; goto out; } @@ -509,11 +517,68 @@ out: return rv; } +static char * +ssh_config_make_absolute(ssh_session session, + const char *path, + bool global) +{ + size_t outlen = 0; + char *out = NULL; + int rv; + + /* Looks like absolute path */ + if (path[0] == '/') { + return strdup(path); + } + + /* relative path */ + if (global) { + /* Parsing global config */ + outlen = strlen(path) + strlen("/etc/ssh/") + 1; + out = malloc(outlen); + if (out == NULL) { + ssh_set_error_oom(session); + return NULL; + } + rv = snprintf(out, outlen, "/etc/ssh/%s", path); + if (rv < 1) { + free(out); + return NULL; + } + return out; + } + + /* paths starting with tilde are already absolute */ + if (path[0] == '~') { + return ssh_path_expand_tilde(path); + } + + /* Parsing user config relative to home directory (generally ~/.ssh) */ + if (session->opts.sshdir == NULL) { + ssh_set_error_invalid(session); + return NULL; + } + outlen = strlen(path) + strlen(session->opts.sshdir) + 1 + 1; + out = malloc(outlen); + if (out == NULL) { + ssh_set_error_oom(session); + return NULL; + } + rv = snprintf(out, outlen, "%s/%s", session->opts.sshdir, path); + if (rv < 1) { + free(out); + return NULL; + } + return out; +} + static int ssh_config_parse_line(ssh_session session, const char *line, unsigned int count, - int *parsing) + int *parsing, + unsigned int depth, + bool global) { enum ssh_config_opcode_e opcode; const char *p = NULL, *p2 = NULL; @@ -558,7 +623,9 @@ ssh_config_parse_line(ssh_session session, opcode != SOC_MATCH && opcode != SOC_INCLUDE && opcode != SOC_IDENTITY && - opcode > SOC_UNSUPPORTED) { /* Ignore all unknown types here */ + opcode != SOC_CERTIFICATE && + opcode > SOC_UNSUPPORTED && + opcode < SOC_MAX) { /* Ignore all unknown types here */ /* Skip all the options that were already applied */ if (seen[opcode] != 0) { SAFE_FREE(x); @@ -572,11 +639,19 @@ ssh_config_parse_line(ssh_session session, p = ssh_config_get_str_tok(&s, NULL); if (p && *parsing) { + char *path = ssh_config_make_absolute(session, p, global); + if (path == NULL) { + SSH_LOG(SSH_LOG_WARN, "line %d: Failed to allocate memory " + "for the include path expansion", count); + SAFE_FREE(x); + return -1; + } #if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) - local_parse_glob(session, p, parsing); + local_parse_glob(session, path, parsing, depth + 1, global); #else - local_parse_file(session, p, parsing); + local_parse_file(session, path, parsing, depth + 1, global); #endif /* HAVE_GLOB */ + free(path); } break; @@ -594,7 +669,7 @@ ssh_config_parse_line(ssh_session session, break; } args++; - SSH_LOG(SSH_LOG_TRACE, "line %d: Processing Match keyword '%s'", + SSH_LOG(SSH_LOG_DEBUG, "line %d: Processing Match keyword '%s'", count, p); /* If the option is prefixed with ! the result should be negated */ @@ -626,7 +701,7 @@ ssh_config_parse_line(ssh_session session, case MATCH_FINAL: case MATCH_CANONICAL: - SSH_LOG(SSH_LOG_INFO, + SSH_LOG(SSH_LOG_DEBUG, "line %d: Unsupported Match keyword '%s', skipping", count, p); @@ -638,13 +713,13 @@ ssh_config_parse_line(ssh_session session, /* Skip one argument (including in quotes) */ p = ssh_config_get_token(&s); if (p == NULL || p[0] == '\0') { - SSH_LOG(SSH_LOG_WARN, "line %d: Match keyword " + SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword " "'%s' requires argument", count, p2); SAFE_FREE(x); return -1; } if (result != 1) { - SSH_LOG(SSH_LOG_INFO, "line %d: Skipped match exec " + SSH_LOG(SSH_LOG_DEBUG, "line %d: Skipped match exec " "'%s' as previous conditions already failed.", count, p2); continue; @@ -665,7 +740,7 @@ ssh_config_parse_line(ssh_session session, } localuser = ssh_get_local_username(); if (localuser == NULL) { - SSH_LOG(SSH_LOG_WARN, "line %d: Can not get local username " + SSH_LOG(SSH_LOG_TRACE, "line %d: Can not get local username " "for conditional matching.", count); SAFE_FREE(x); return -1; @@ -679,13 +754,13 @@ ssh_config_parse_line(ssh_session session, /* Skip one argument */ p = ssh_config_get_str_tok(&s, NULL); if (p == NULL || p[0] == '\0') { - SSH_LOG(SSH_LOG_WARN, "line %d: Match keyword " + SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword " "'%s' requires argument", count, p2); SAFE_FREE(x); return -1; } args++; - SSH_LOG(SSH_LOG_INFO, + SSH_LOG(SSH_LOG_TRACE, "line %d: Unsupported Match keyword '%s', ignoring", count, p2); @@ -815,34 +890,6 @@ ssh_config_parse_line(ssh_session session, } } break; - case SOC_PROTOCOL: - p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { - char *a, *b; - b = strdup(p); - if (b == NULL) { - SAFE_FREE(x); - ssh_set_error_oom(session); - return -1; - } - i = 0; - ssh_options_set(session, SSH_OPTIONS_SSH2, &i); - - for (a = strtok(b, ","); a; a = strtok(NULL, ",")) { - switch (atoi(a)) { - case 1: - break; - case 2: - i = 1; - ssh_options_set(session, SSH_OPTIONS_SSH2, &i); - break; - default: - break; - } - } - SAFE_FREE(b); - } - break; case SOC_TIMEOUT: l = ssh_config_get_long(&s, -1); if (l >= 0 && *parsing) { @@ -920,10 +967,10 @@ ssh_config_parse_line(ssh_session session, if (strcasecmp(p, "quiet") == 0) { value = SSH_LOG_NONE; } else if (strcasecmp(p, "fatal") == 0 || - strcasecmp(p, "error")== 0 || - strcasecmp(p, "info") == 0) { + strcasecmp(p, "error")== 0) { value = SSH_LOG_WARN; - } else if (strcasecmp(p, "verbose") == 0) { + } else if (strcasecmp(p, "verbose") == 0 || + strcasecmp(p, "info") == 0) { value = SSH_LOG_INFO; } else if (strcasecmp(p, "DEBUG") == 0 || strcasecmp(p, "DEBUG1") == 0) { @@ -943,7 +990,7 @@ ssh_config_parse_line(ssh_session session, ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, p); } break; - case SOC_PUBKEYACCEPTEDTYPES: + case SOC_PUBKEYACCEPTEDKEYTYPES: p = ssh_config_get_str_tok(&s, NULL); if (p && *parsing) { ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, p); @@ -961,20 +1008,20 @@ ssh_config_parse_line(ssh_session session, if (p == NULL) { break; } else if (strcmp(p, "default") == 0) { - /* Default rekey limits enforced automaticaly */ + /* Default rekey limits enforced automatically */ ll = 0; } else { char *endp = NULL; ll = strtoll(p, &endp, 10); if (p == endp || ll < 0) { /* No number or negative */ - SSH_LOG(SSH_LOG_WARN, "Invalid argument to rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Invalid argument to rekey limit"); break; } switch (*endp) { case 'G': if (ll > LLONG_MAX / 1024) { - SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); ll = -1; break; } @@ -982,7 +1029,7 @@ ssh_config_parse_line(ssh_session session, FALL_THROUGH; case 'M': if (ll > LLONG_MAX / 1024) { - SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); ll = -1; break; } @@ -990,7 +1037,7 @@ ssh_config_parse_line(ssh_session session, FALL_THROUGH; case 'K': if (ll > LLONG_MAX / 1024) { - SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); ll = -1; break; } @@ -1006,7 +1053,7 @@ ssh_config_parse_line(ssh_session session, break; } if (*endp != ' ' && *endp != '\0') { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Invalid trailing characters after the rekey limit: %s", endp); break; @@ -1027,14 +1074,14 @@ ssh_config_parse_line(ssh_session session, ll = strtoll(p, &endp, 10); if (p == endp || ll < 0) { /* No number or negative */ - SSH_LOG(SSH_LOG_WARN, "Invalid argument to rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Invalid argument to rekey limit"); break; } switch (*endp) { case 'w': case 'W': if (ll > LLONG_MAX / 7) { - SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); ll = -1; break; } @@ -1043,7 +1090,7 @@ ssh_config_parse_line(ssh_session session, case 'd': case 'D': if (ll > LLONG_MAX / 24) { - SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); ll = -1; break; } @@ -1052,7 +1099,7 @@ ssh_config_parse_line(ssh_session session, case 'h': case 'H': if (ll > LLONG_MAX / 60) { - SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); ll = -1; break; } @@ -1061,7 +1108,7 @@ ssh_config_parse_line(ssh_session session, case 'm': case 'M': if (ll > LLONG_MAX / 60) { - SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit"); + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); ll = -1; break; } @@ -1080,7 +1127,7 @@ ssh_config_parse_line(ssh_session session, break; } if (*endp != '\0') { - SSH_LOG(SSH_LOG_WARN, "Invalid trailing characters after the" + SSH_LOG(SSH_LOG_TRACE, "Invalid trailing characters after the" " rekey limit: %s", endp); break; } @@ -1116,17 +1163,68 @@ ssh_config_parse_line(ssh_session session, } break; case SOC_NA: - SSH_LOG(SSH_LOG_INFO, "Unapplicable option: %s, line: %d", + SSH_LOG(SSH_LOG_TRACE, "Unapplicable option: %s, line: %d", keyword, count); break; case SOC_UNSUPPORTED: - SSH_LOG(SSH_LOG_INFO, "Unsupported option: %s, line: %d", + SSH_LOG(SSH_LOG_RARE, "Unsupported option: %s, line: %d", keyword, count); break; case SOC_UNKNOWN: - SSH_LOG(SSH_LOG_INFO, "Unknown option: %s, line: %d", + SSH_LOG(SSH_LOG_TRACE, "Unknown option: %s, line: %d", keyword, count); break; + case SOC_IDENTITYAGENT: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_IDENTITY_AGENT, p); + } + break; + case SOC_IDENTITIESONLY: + i = ssh_config_get_yesno(&s, -1); + if (i >= 0 && *parsing) { + bool b = i; + ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &b); + } + break; + case SOC_CONTROLMASTER: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + int value = -1; + + if (strcasecmp(p, "auto") == 0) { + value = SSH_CONTROL_MASTER_AUTO; + } else if (strcasecmp(p, "yes") == 0) { + value = SSH_CONTROL_MASTER_YES; + } else if (strcasecmp(p, "no") == 0) { + value = SSH_CONTROL_MASTER_NO; + } else if (strcasecmp(p, "autoask") == 0) { + value = SSH_CONTROL_MASTER_AUTOASK; + } else if (strcasecmp(p, "ask") == 0) { + value = SSH_CONTROL_MASTER_ASK; + } + + if (value != -1) { + ssh_options_set(session, SSH_OPTIONS_CONTROL_MASTER, &value); + } + } + break; + case SOC_CONTROLPATH: + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL) { + SAFE_FREE(x); + return -1; + } + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_CONTROL_PATH, p); + } + break; + case SOC_CERTIFICATE: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, p); + } + break; default: ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d", opcode); @@ -1152,18 +1250,24 @@ int ssh_config_parse_file(ssh_session session, const char *filename) unsigned int count = 0; FILE *f; int parsing, rv; + bool global = 0; f = fopen(filename, "r"); if (f == NULL) { return 0; } + rv = strcmp(filename, GLOBAL_CLIENT_CONFIG); + if (rv == 0) { + global = true; + } + SSH_LOG(SSH_LOG_PACKET, "Reading configuration data from %s", filename); parsing = 1; while (fgets(line, sizeof(line), f)) { count++; - rv = ssh_config_parse_line(session, line, count, &parsing); + rv = ssh_config_parse_line(session, line, count, &parsing, 0, global); if (rv < 0) { fclose(f); return -1; @@ -1198,24 +1302,24 @@ int ssh_config_parse_string(ssh_session session, const char *input) line_start = c; c = strchr(line_start, '\n'); if (c == NULL) { - /* if there is no newline in the end of the string */ + /* if there is no newline at the end of the string */ c = strchr(line_start, '\0'); } if (c == NULL) { /* should not happen, would mean a string without trailing '\0' */ - SSH_LOG(SSH_LOG_WARN, "No trailing '\\0' in config string"); + SSH_LOG(SSH_LOG_TRACE, "No trailing '\\0' in config string"); return SSH_ERROR; } line_len = c - line_start; if (line_len > MAX_LINE_SIZE - 1) { - SSH_LOG(SSH_LOG_WARN, "Line %u too long: %u characters", + SSH_LOG(SSH_LOG_TRACE, "Line %u too long: %u characters", line_num, line_len); return SSH_ERROR; } memcpy(line, line_start, line_len); line[line_len] = '\0'; SSH_LOG(SSH_LOG_DEBUG, "Line %u: %s", line_num, line); - rv = ssh_config_parse_line(session, line, line_num, &parsing); + rv = ssh_config_parse_line(session, line, line_num, &parsing, 0, false); if (rv < 0) { return SSH_ERROR; } diff --git a/src/config_parser.c b/src/config_parser.c index 2f91d39f..bd6ab9d7 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -30,9 +30,10 @@ #include "libssh/config_parser.h" #include "libssh/priv.h" +#include "libssh/misc.h" /* Returns the original string after skipping the leading whitespace - * and optional quotes. + * until finding LF. * This is useful in case we need to get the rest of the line (for example * external command). */ @@ -48,15 +49,6 @@ char *ssh_config_get_cmd(char **str) } } - if (*c == '\"') { - for (r = ++c; *c; c++) { - if (*c == '\"') { - *c = '\0'; - goto out; - } - } - } - for (r = c; *c; c++) { if (*c == '\n') { *c = '\0'; @@ -170,12 +162,14 @@ int ssh_config_get_yesno(char **str, int notfound) } int ssh_config_parse_uri(const char *tok, - char **username, - char **hostname, - char **port) + char **username, + char **hostname, + char **port, + bool ignore_port) { char *endp = NULL; long port_n; + int rc; /* Sanitize inputs */ if (username != NULL) { @@ -189,7 +183,7 @@ int ssh_config_parse_uri(const char *tok, } /* Username part (optional) */ - endp = strchr(tok, '@'); + endp = strrchr(tok, '@'); if (endp != NULL) { /* Zero-length username is not valid */ if (tok == endp) { @@ -200,6 +194,10 @@ int ssh_config_parse_uri(const char *tok, if (*username == NULL) { goto error; } + rc = ssh_check_username_syntax(*username); + if (rc != SSH_OK) { + goto error; + } } tok = endp + 1; /* If there is second @ character, this does not look like our URI */ @@ -217,12 +215,17 @@ int ssh_config_parse_uri(const char *tok, if (endp == NULL) { goto error; } - } else { - /* Hostnames or aliases expand to the last colon or to the end */ + } else if (!ignore_port) { + /* Hostnames or aliases expand to the last colon (if port is requested) + * or to the end */ endp = strrchr(tok, ':'); if (endp == NULL) { endp = strchr(tok, '\0'); } + } else { + /* If no port is requested, expand to the end of line + * (to accommodate the IPv6 addresses) */ + endp = strchr(tok, '\0'); } if (tok == endp) { /* Zero-length hostnames are not valid */ @@ -233,6 +236,14 @@ int ssh_config_parse_uri(const char *tok, if (*hostname == NULL) { goto error; } + /* if not an ip, check syntax */ + rc = ssh_is_ipaddr(*hostname); + if (rc == 0) { + rc = ssh_check_hostname_syntax(*hostname); + if (rc != SSH_OK) { + goto error; + } + } } /* Skip also the closing bracket */ if (*endp == ']') { @@ -246,7 +257,7 @@ int ssh_config_parse_uri(const char *tok, /* Verify the port is valid positive number */ port_n = strtol(endp + 1, &port_end, 10); if (port_n < 1 || *port_end != '\0') { - SSH_LOG(SSH_LOG_WARN, "Failed to parse port number." + SSH_LOG(SSH_LOG_TRACE, "Failed to parse port number." " The value '%ld' is invalid or there are some" " trailing characters: '%s'", port_n, port_end); goto error; diff --git a/src/connect.c b/src/connect.c index ce4d58df..dd3bbcf5 100644 --- a/src/connect.c +++ b/src/connect.c @@ -54,11 +54,6 @@ #define NTDDI_VERSION 0x05010000 /* NTDDI_WINXP */ #endif -#if _MSC_VER >= 1400 -#include <io.h> -#undef close -#define close _close -#endif /* _MSC_VER */ #include <winsock2.h> #include <ws2tcpip.h> @@ -136,7 +131,7 @@ static int getai(const char *host, int port, struct addrinfo **ai) #endif } - if (ssh_is_ipaddr(host)) { + if (ssh_is_ipaddr(host) == 1) { /* this is an IP address */ SSH_LOG(SSH_LOG_PACKET, "host %s matches an IP address", host); hints.ai_flags |= AI_NUMERICHOST; @@ -168,7 +163,7 @@ static int set_tcp_nodelay(socket_t socket) socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, const char *bind_addr, int port) { - socket_t s = -1; + socket_t s = -1, first = -1; int rc; struct addrinfo *ai = NULL; struct addrinfo *itr = NULL; @@ -183,11 +178,13 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, } for (itr = ai; itr != NULL; itr = itr->ai_next) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; /* create socket */ s = socket(itr->ai_family, itr->ai_socktype, itr->ai_protocol); if (s < 0) { ssh_set_error(session, SSH_FATAL, - "Socket create failed: %s", strerror(errno)); + "Socket create failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); continue; } @@ -214,7 +211,8 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, { if (bind(s, bind_itr->ai_addr, bind_itr->ai_addrlen) < 0) { ssh_set_error(session, SSH_FATAL, - "Binding local address: %s", strerror(errno)); + "Binding local address: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); continue; } else { break; @@ -246,7 +244,7 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, if (rc < 0) { ssh_set_error(session, SSH_FATAL, "Failed to set TCP_NODELAY on socket: %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); ssh_connect_socket_close(s); s = -1; continue; @@ -255,11 +253,22 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, errno = 0; rc = connect(s, itr->ai_addr, itr->ai_addrlen); - if (rc == -1 && (errno != 0) && (errno != EINPROGRESS)) { - ssh_set_error(session, SSH_FATAL, - "Failed to connect: %s", strerror(errno)); - ssh_connect_socket_close(s); - s = -1; + if (rc == -1) { + if ((errno != 0) && (errno != EINPROGRESS)) { + ssh_set_error(session, SSH_FATAL, + "Failed to connect: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + ssh_connect_socket_close(s); + s = -1; + } else { + if (first == -1) { + first = s; + } else { /* errno == EINPROGRESS */ + /* save only the first "working" socket */ + ssh_connect_socket_close(s); + s = -1; + } + } continue; } @@ -268,6 +277,12 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, freeaddrinfo(ai); + /* first let's go through all the addresses looking for immediate + * connection, otherwise return the first address without error or error */ + if (s == -1) { + s = first; + } + return s; } @@ -289,14 +304,14 @@ static int ssh_select_cb (socket_t fd, int revents, void *userdata) /** * @brief A wrapper for the select syscall * - * This functions acts more or less like the select(2) syscall.\n + * This function acts more or less like the select(2) syscall.\n * There is no support for writing or exceptions.\n * * @param[in] channels Arrays of channels pointers terminated by a NULL. * It is never rewritten. * - * @param[out] outchannels Arrays of same size that "channels", there is no need - * to initialize it. + * @param[out] outchannels Arrays of the same size as "channels", there is no + * need to initialize it. * * @param[in] maxfd Maximum +1 file descriptor from readfds. * diff --git a/src/connector.c b/src/connector.c index 4e96b200..21c4d79b 100644 --- a/src/connector.c +++ b/src/connector.c @@ -34,19 +34,7 @@ #define CHUNKSIZE 4096 #endif -#ifdef _WIN32 -# ifdef HAVE_IO_H -# include <io.h> -# undef open -# define open _open -# undef close -# define close _close -# undef read -# define read _read -# undef unlink -# define unlink _unlink -# endif /* HAVE_IO_H */ -#else +#ifndef _WIN32 # include <sys/types.h> # include <sys/socket.h> #endif @@ -85,7 +73,7 @@ static int ssh_connector_channel_data_cb(ssh_session session, void *userdata); static int ssh_connector_channel_write_wontblock_cb(ssh_session session, ssh_channel channel, - size_t bytes, + uint32_t bytes, void *userdata); static ssize_t ssh_connector_fd_read(ssh_connector connector, void *buffer, @@ -216,7 +204,7 @@ static void ssh_connector_except_channel(ssh_connector connector, /** * @internal * - * @brief Reset the poll events to be followed for each file descriptors. + * @brief Reset the poll events to be followed for each file descriptor. */ static void ssh_connector_reset_pollevents(ssh_connector connector) { @@ -248,14 +236,14 @@ static void ssh_connector_fd_in_cb(ssh_connector connector) uint32_t toread = CHUNKSIZE; ssize_t r; ssize_t w; - int total = 0; + ssize_t total = 0; int rc; SSH_LOG(SSH_LOG_TRACE, "connector POLLIN event for fd %d", connector->in_fd); if (connector->out_wontblock) { if (connector->out_channel != NULL) { - size_t size = ssh_channel_window_size(connector->out_channel); + uint32_t size = ssh_channel_window_size(connector->out_channel); /* Don't attempt reading more than the window */ toread = MIN(size, CHUNKSIZE); @@ -326,35 +314,40 @@ static void ssh_connector_fd_in_cb(ssh_connector connector) /** @internal * @brief Callback called when a poll event is received on an output fd */ -static void ssh_connector_fd_out_cb(ssh_connector connector){ +static void +ssh_connector_fd_out_cb(ssh_connector connector) +{ unsigned char buffer[CHUNKSIZE]; - int r; - int w; - int total = 0; - SSH_LOG(SSH_LOG_TRACE, "connector POLLOUT event for fd %d", connector->out_fd); + ssize_t r; + ssize_t w; + ssize_t total = 0; + SSH_LOG(SSH_LOG_TRACE, "connector POLLOUT event for fd %d", + connector->out_fd); - if(connector->in_available){ - if (connector->in_channel != NULL){ - r = ssh_channel_read_nonblocking(connector->in_channel, buffer, CHUNKSIZE, 0); - if(r == SSH_ERROR){ + if (connector->in_available) { + if (connector->in_channel != NULL) { + r = ssh_channel_read_nonblocking(connector->in_channel, buffer, + CHUNKSIZE, 0); + if (r == SSH_ERROR) { ssh_connector_except_channel(connector, connector->in_channel); return; - } else if(r == 0 && ssh_channel_is_eof(connector->in_channel)){ + } else if (r == 0 && ssh_channel_is_eof(connector->in_channel)) { close(connector->out_fd); connector->out_fd = SSH_INVALID_SOCKET; return; - } else if(r>0) { + } else if (r > 0) { /* loop around write in case the write blocks even for CHUNKSIZE bytes */ - while (total != r){ - w = ssh_connector_fd_write(connector, buffer + total, r - total); - if (w < 0){ + while (total != r) { + w = ssh_connector_fd_write(connector, buffer + total, + r - total); + if (w < 0) { ssh_connector_except(connector, connector->out_fd); return; } total += w; } } - } else if (connector->in_fd != SSH_INVALID_SOCKET){ + } else if (connector->in_fd != SSH_INVALID_SOCKET) { /* fallback on the socket input callback */ connector->out_wontblock = 1; ssh_connector_fd_in_cb(connector); @@ -432,7 +425,7 @@ static int ssh_connector_channel_data_cb(ssh_session session, { ssh_connector connector = userdata; int w; - size_t window; + uint32_t window; (void) session; (void) channel; @@ -453,7 +446,7 @@ static int ssh_connector_channel_data_cb(ssh_session session, if (connector->out_wontblock) { if (connector->out_channel != NULL) { - int window_len; + uint32_t window_len; window = ssh_channel_window_size(connector->out_channel); window_len = MIN(window, len); @@ -517,7 +510,7 @@ static int ssh_connector_channel_data_cb(ssh_session session, */ static int ssh_connector_channel_write_wontblock_cb(ssh_session session, ssh_channel channel, - size_t bytes, + uint32_t bytes, void *userdata) { ssh_connector connector = userdata; @@ -529,7 +522,7 @@ static int ssh_connector_channel_write_wontblock_cb(ssh_session session, SSH_LOG(SSH_LOG_TRACE, "Channel write won't block"); if (connector->in_available) { if (connector->in_channel != NULL) { - size_t len = MIN(CHUNKSIZE, bytes); + uint32_t len = MIN(CHUNKSIZE, bytes); r = ssh_channel_read_nonblocking(connector->in_channel, buffer, diff --git a/src/crypto_common.c b/src/crypto_common.c index 8213ddb9..5dc883f6 100644 --- a/src/crypto_common.c +++ b/src/crypto_common.c @@ -23,12 +23,14 @@ 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++; + size_t i; + uint8_t status = 0; + const uint8_t *p1 = s1; + const uint8_t *p2 = s2; + + for (i = 0; i < n; i++) { + status |= (p1[i] ^ p2[i]); } - return (rc != 0); -} + return (status != 0); +} diff --git a/src/curve25519.c b/src/curve25519.c index d2517551..3f57f25d 100644 --- a/src/curve25519.c +++ b/src/curve25519.c @@ -39,7 +39,7 @@ #include "libssh/pki.h" #include "libssh/bignum.h" -#ifdef HAVE_OPENSSL_X25519 +#ifdef HAVE_LIBCRYPTO #include <openssl/err.h> #endif @@ -59,7 +59,7 @@ static struct ssh_packet_callbacks_struct ssh_curve25519_client_callbacks = { static int ssh_curve25519_init(ssh_session session) { int rc; -#ifdef HAVE_OPENSSL_X25519 +#ifdef HAVE_LIBCRYPTO EVP_PKEY_CTX *pctx = NULL; EVP_PKEY *pkey = NULL; size_t pubkey_len = CURVE25519_PUBKEY_SIZE; @@ -136,7 +136,7 @@ static int ssh_curve25519_init(ssh_session session) crypto_scalarmult_base(session->next_crypto->curve25519_client_pubkey, session->next_crypto->curve25519_privkey); } -#endif /* HAVE_OPENSSL_X25519 */ +#endif /* HAVE_LIBCRYPTO */ return SSH_OK; } @@ -172,11 +172,16 @@ int ssh_client_curve25519_init(ssh_session session) return rc; } +void ssh_client_curve25519_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_curve25519_client_callbacks); +} + static int ssh_curve25519_build_k(ssh_session session) { ssh_curve25519_pubkey k; -#ifdef HAVE_OPENSSL_X25519 +#ifdef HAVE_LIBCRYPTO EVP_PKEY_CTX *pctx = NULL; EVP_PKEY *pkey = NULL, *pubkey = NULL; size_t shared_key_len = sizeof(k); @@ -255,7 +260,7 @@ out: crypto_scalarmult(k, session->next_crypto->curve25519_privkey, session->next_crypto->curve25519_server_pubkey); } -#endif /* HAVE_OPENSSL_X25519 */ +#endif /* HAVE_LIBCRYPTO */ bignum_bin2bn(k, CURVE25519_PUBKEY_SIZE, &session->next_crypto->shared_secret); if (session->next_crypto->shared_secret == NULL) { @@ -285,7 +290,7 @@ static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply){ (void)type; (void)user; - ssh_packet_remove_callbacks(session, &ssh_curve25519_client_callbacks); + ssh_client_curve25519_remove_callbacks(session); pubkey_blob = ssh_buffer_get_ssh_string(packet); if (pubkey_blob == NULL) { @@ -330,16 +335,10 @@ static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply){ } /* Send the MSG_NEWKEYS */ - if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { - goto error; - } - - rc=ssh_packet_send(session); + rc = ssh_packet_send_newkeys(session); if (rc == SSH_ERROR) { goto error; } - - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; return SSH_PACKET_USED; @@ -381,7 +380,7 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init){ ssh_string q_s_string = NULL; ssh_string server_pubkey_blob = NULL; - /* SSH host keys (rsa,dsa,ecdsa) */ + /* SSH host keys (rsa, ed25519 and ecdsa) */ ssh_key privkey = NULL; enum ssh_digest_e digest = SSH_DIGEST_AUTO; ssh_string sig_blob = NULL; @@ -408,8 +407,8 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init){ memcpy(session->next_crypto->curve25519_client_pubkey, ssh_string_data(q_c_string), CURVE25519_PUBKEY_SIZE); SSH_STRING_FREE(q_c_string); - /* Build server's keypair */ + /* Build server's key pair */ rc = ssh_curve25519_init(session); if (rc != SSH_OK) { ssh_set_error(session, SSH_FATAL, "Failed to generate curve25519 keys"); @@ -491,24 +490,19 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init){ goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEX_ECDH_REPLY sent"); + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_ECDH_REPLY sent"); rc = ssh_packet_send(session); if (rc == SSH_ERROR) { return SSH_ERROR; } - /* Send the MSG_NEWKEYS */ - rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); - if (rc < 0) { - goto error; - } - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; - rc = ssh_packet_send(session); + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); if (rc == SSH_ERROR) { goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); return SSH_PACKET_USED; error: diff --git a/src/dh-gex.c b/src/dh-gex.c index c6295f8f..4e59073f 100644 --- a/src/dh-gex.c +++ b/src/dh-gex.c @@ -37,7 +37,7 @@ #include "libssh/buffer.h" #include "libssh/session.h" -/* Minimum, recommanded and maximum size of DH group */ +/* Minimum, recommended and maximum size of DH group */ #define DH_PMIN 2048 #define DH_PREQ 2048 #define DH_PMAX 8192 @@ -108,11 +108,15 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group) bignum pmin1 = NULL, one = NULL; bignum_CTX ctx = bignum_ctx_new(); bignum modulus = NULL, generator = NULL; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum pubkey; +#else + bignum pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ (void) type; (void) user; - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEX_DH_GEX_GROUP received"); + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_DH_GEX_GROUP received"); if (bignum_ctx_invalid(ctx)) { goto error; @@ -212,6 +216,9 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group) if (rc != SSH_OK) { goto error; } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ session->dh_handshake_state = DH_STATE_INIT_SENT; @@ -229,6 +236,9 @@ error: bignum_safe_free(generator); bignum_safe_free(one); bignum_safe_free(pmin1); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ if(!bignum_ctx_invalid(ctx)) { bignum_ctx_free(ctx); } @@ -238,6 +248,11 @@ error: return SSH_PACKET_USED; } +void ssh_client_dhgex_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_dhgex_client_callbacks); +} + static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply) { struct ssh_crypto_struct *crypto=session->next_crypto; @@ -246,9 +261,9 @@ static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply) bignum server_pubkey = NULL; (void)type; (void)user; - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEX_DH_GEX_REPLY received"); + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_DH_GEX_REPLY received"); - ssh_packet_remove_callbacks(session, &ssh_dhgex_client_callbacks); + ssh_client_dhgex_remove_callbacks(session); rc = ssh_buffer_unpack(packet, "SBS", &pubkey_blob, &server_pubkey, @@ -282,15 +297,10 @@ static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply) } /* Send the MSG_NEWKEYS */ - if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { - goto error; - } - - rc = ssh_packet_send(session); + rc = ssh_packet_send_newkeys(session); if (rc == SSH_ERROR) { goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; return SSH_PACKET_USED; @@ -367,9 +377,9 @@ static bool dhgroup_better_size(uint32_t pmin, * @brief returns 1 with 1/n probability * @returns 1 on with P(1/n), 0 with P(n-1/n). */ -static bool invn_chance(int n) +static bool invn_chance(size_t n) { - uint32_t nounce = 0; + size_t nounce = 0; int ok; ok = ssh_get_random(&nounce, sizeof(nounce), 0); @@ -425,7 +435,7 @@ static int ssh_retrieve_dhgroup_file(FILE *moduli, if (rc == EOF) { break; } - SSH_LOG(SSH_LOG_INFO, "Invalid moduli entry line %zu", line); + SSH_LOG(SSH_LOG_DEBUG, "Invalid moduli entry line %zu", line); do { firstbyte = getc(moduli); } while(firstbyte != '\n' && firstbyte != EOF); @@ -463,14 +473,14 @@ static int ssh_retrieve_dhgroup_file(FILE *moduli, } } if (*best_size != 0) { - SSH_LOG(SSH_LOG_INFO, + SSH_LOG(SSH_LOG_DEBUG, "Selected %zu bits modulus out of %zu candidates in %zu lines", *best_size, best_nlines - 1, line); } else { - SSH_LOG(SSH_LOG_WARNING, - "No moduli found for [%u:%u:%u]", + SSH_LOG(SSH_LOG_DEBUG, + "No moduli found for [%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", pmin, pn, pmax); @@ -515,10 +525,11 @@ static int ssh_retrieve_dhgroup(char *moduli_file, moduli = fopen(MODULI_FILE, "r"); if (moduli == NULL) { - SSH_LOG(SSH_LOG_WARNING, + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + SSH_LOG(SSH_LOG_DEBUG, "Unable to open moduli file: %s", - strerror(errno)); - return ssh_fallback_group(pmax, p, g); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return ssh_fallback_group(pmax, p, g); } *size = 0; @@ -610,12 +621,12 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request) ssh_set_error_invalid(session); goto error; } - SSH_LOG(SSH_LOG_INFO, "dh-gex: DHGEX_REQUEST[%u:%u:%u]", pmin, pn, pmax); + SSH_LOG(SSH_LOG_DEBUG, "dh-gex: DHGEX_REQUEST[%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", pmin, pn, pmax); if (pmin > pn || pn > pmax || pn > DH_PMAX || pmax < DH_PMIN) { ssh_set_error(session, SSH_FATAL, - "Invalid dh-gex arguments [%u:%u:%u]", + "Invalid dh-gex arguments [%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", pmin, pn, pmax); @@ -642,7 +653,7 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request) if (rc == SSH_ERROR) { ssh_set_error(session, SSH_FATAL, - "Couldn't find DH group for [%u:%u:%u]", + "Couldn't find DH group for [%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", pmin, pn, pmax); @@ -25,6 +25,8 @@ #include "config.h" +#include <stdio.h> + #include "libssh/priv.h" #include "libssh/crypto.h" #include "libssh/buffer.h" @@ -309,7 +311,11 @@ static struct ssh_packet_callbacks_struct ssh_dh_client_callbacks = { */ int ssh_client_dh_init(ssh_session session){ struct ssh_crypto_struct *crypto = session->next_crypto; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum pubkey; +#else + bignum pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ int rc; rc = ssh_dh_init_common(crypto); @@ -330,6 +336,9 @@ int ssh_client_dh_init(ssh_session session){ if (rc != SSH_OK) { goto error; } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif /* register the packet callbacks */ ssh_packet_set_callbacks(session, &ssh_dh_client_callbacks); @@ -338,10 +347,18 @@ int ssh_client_dh_init(ssh_session session){ rc = ssh_packet_send(session); return rc; error: +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif ssh_dh_cleanup(crypto); return SSH_ERROR; } +void ssh_client_dh_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_dh_client_callbacks); +} + SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ struct ssh_crypto_struct *crypto=session->next_crypto; ssh_string pubkey_blob = NULL; @@ -351,7 +368,7 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ (void)type; (void)user; - ssh_packet_remove_callbacks(session, &ssh_dh_client_callbacks); + ssh_client_dh_remove_callbacks(session); rc = ssh_buffer_unpack(packet, "SBS", &pubkey_blob, &server_pubkey, &crypto->dh_server_signature); @@ -370,7 +387,7 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ if (rc != 0) { goto error; } - + rc = ssh_dh_compute_shared_secret(session->next_crypto->dh_ctx, DH_CLIENT_KEYPAIR, DH_SERVER_KEYPAIR, &session->next_crypto->shared_secret); @@ -381,16 +398,10 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ } /* Send the MSG_NEWKEYS */ - if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { - goto error; - } - - rc=ssh_packet_send(session); + rc = ssh_packet_send_newkeys(session); if (rc == SSH_ERROR) { goto error; } - - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; return SSH_PACKET_USED; error: @@ -436,7 +447,11 @@ int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet) ssh_string sig_blob = NULL; ssh_string pubkey_blob = NULL; bignum client_pubkey; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum server_pubkey; +#else + bignum server_pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ int packet_type; int rc; @@ -516,6 +531,9 @@ int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet) sig_blob); SSH_STRING_FREE(sig_blob); SSH_STRING_FREE(pubkey_blob); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(server_pubkey); +#endif if(rc != SSH_OK) { ssh_set_error_oom(session); ssh_buffer_reinit(session->out_buffer); @@ -527,20 +545,20 @@ int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet) } SSH_LOG(SSH_LOG_DEBUG, "Sent KEX_DH_[GEX]_REPLY"); - if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { - ssh_buffer_reinit(session->out_buffer); - goto error; - } session->dh_handshake_state=DH_STATE_NEWKEYS_SENT; - if (ssh_packet_send(session) == SSH_ERROR) { + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { goto error; } - SSH_LOG(SSH_LOG_PACKET, "SSH_MSG_NEWKEYS sent"); return SSH_OK; error: SSH_STRING_FREE(sig_blob); SSH_STRING_FREE(pubkey_blob); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(server_pubkey); +#endif session->session_state = SSH_SESSION_STATE_ERROR; ssh_dh_cleanup(session->next_crypto); @@ -640,7 +658,7 @@ ssh_key ssh_dh_get_current_server_publickey(ssh_session session) return session->current_crypto->server_pubkey; } -/* Caller need to free the blob */ +/* Caller needs to free the blob */ int ssh_dh_get_current_server_publickey_blob(ssh_session session, ssh_string *pubkey_blob) { @@ -654,7 +672,7 @@ ssh_key ssh_dh_get_next_server_publickey(ssh_session session) return session->next_crypto->server_pubkey; } -/* Caller need to free the blob */ +/* Caller needs to free the blob */ int ssh_dh_get_next_server_publickey_blob(ssh_session session, ssh_string *pubkey_blob) { @@ -683,7 +701,7 @@ static char *ssh_get_b64_unpadded(const unsigned char *hash, size_t len) char *b64_unpadded = NULL; size_t k; - b64_padded = (char *)bin_to_base64(hash, (int)len); + b64_padded = (char *)bin_to_base64(hash, len); if (b64_padded == NULL) { return NULL; } @@ -699,7 +717,7 @@ static char *ssh_get_b64_unpadded(const unsigned char *hash, size_t len) * @brief Get a hash as a human-readable hex- or base64-string. * * This gets an allocated fingerprint hash. If it is a SHA sum, it will - * return an unpadded base64 strings. If it is a MD5 sum, it will return hex + * return an unpadded base64 string. If it is a MD5 sum, it will return a hex * string. Either way, the output is prepended by the hash-type. * * @warning Do NOT use MD5 or SHA1! Those hash functions are being deprecated. @@ -711,7 +729,8 @@ static char *ssh_get_b64_unpadded(const unsigned char *hash, size_t len) * * @param len Length of the buffer to convert. * - * @return Returns the allocated fingerprint hash or NULL on error. + * @return Returns the allocated fingerprint hash or NULL on error. The caller + * needs to free the memory using ssh_string_free_char(). * * @see ssh_string_free_char() */ diff --git a/src/dh_crypto.c b/src/dh_crypto.c index 3b3495c1..9ff7ad3c 100644 --- a/src/dh_crypto.c +++ b/src/dh_crypto.c @@ -30,6 +30,13 @@ #include "openssl/crypto.h" #include "openssl/dh.h" #include "libcrypto-compat.h" +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include <openssl/evp.h> +#include <openssl/params.h> +#include <openssl/core_names.h> +#include <openssl/param_build.h> +#include <openssl/err.h> +#endif /* OPENSSL_VERSION_NUMBER */ extern bignum ssh_dh_generator; extern bignum ssh_dh_group1; @@ -38,13 +45,21 @@ extern bignum ssh_dh_group16; extern bignum ssh_dh_group18; struct dh_ctx { +#if OPENSSL_VERSION_NUMBER < 0x30000000L DH *keypair[2]; +#else + EVP_PKEY *keypair[2]; +#endif /* OPENSSL_VERSION_NUMBER */ }; void ssh_dh_debug_crypto(struct ssh_crypto_struct *c) { #ifdef DEBUG_CRYPTO +#if OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum x = NULL, y = NULL, e = NULL, f = NULL; +#else + bignum x = NULL, y = NULL, e = NULL, f = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ ssh_dh_keypair_get_keys(c->dh_ctx, DH_CLIENT_KEYPAIR, &x, &e); ssh_dh_keypair_get_keys(c->dh_ctx, DH_SERVER_KEYPAIR, &y, &f); @@ -52,6 +67,12 @@ void ssh_dh_debug_crypto(struct ssh_crypto_struct *c) ssh_print_bignum("y", y); ssh_print_bignum("e", e); ssh_print_bignum("f", f); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(x); + bignum_safe_free(y); + bignum_safe_free(e); + bignum_safe_free(f); +#endif /* OPENSSL_VERSION_NUMBER */ ssh_log_hexdump("Session server cookie", c->server_kex.cookie, 16); ssh_log_hexdump("Session client cookie", c->client_kex.cookie, 16); @@ -59,9 +80,10 @@ void ssh_dh_debug_crypto(struct ssh_crypto_struct *c) #else (void)c; /* UNUSED_PARAM */ -#endif +#endif /* DEBUG_CRYPTO */ } +#if OPENSSL_VERSION_NUMBER < 0x30000000L int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, const_bignum *priv, const_bignum *pub) { @@ -70,22 +92,76 @@ int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, (ctx->keypair[peer] == NULL)) { return SSH_ERROR; } + DH_get0_key(ctx->keypair[peer], pub, priv); + + if (priv && (*priv == NULL || bignum_num_bits(*priv) == 0)) { + return SSH_ERROR; + } + if (pub && (*pub == NULL || bignum_num_bits(*pub) == 0)) { + return SSH_ERROR; + } + + return SSH_OK; +} + +#else +/* If set *priv and *pub should be initialized + * to NULL before calling this function*/ +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + bignum *priv, bignum *pub) +{ + int rc; + if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || + ((priv == NULL) && (pub == NULL)) || (ctx == NULL) || + (ctx->keypair[peer] == NULL)) { + return SSH_ERROR; + } + + if (priv) { + rc = EVP_PKEY_get_bn_param(ctx->keypair[peer], + OSSL_PKEY_PARAM_PRIV_KEY, + priv); + if (rc != 1) { + return SSH_ERROR; + } + } + if (pub) { + rc = EVP_PKEY_get_bn_param(ctx->keypair[peer], + OSSL_PKEY_PARAM_PUB_KEY, + pub); + if (rc != 1) { + return SSH_ERROR; + } + } if (priv && (*priv == NULL || bignum_num_bits(*priv) == 0)) { + if (pub && (*pub != NULL && bignum_num_bits(*pub) != 0)) { + bignum_safe_free(*pub); + *pub = NULL; + } return SSH_ERROR; } if (pub && (*pub == NULL || bignum_num_bits(*pub) == 0)) { + if (priv) { + bignum_safe_free(*priv); + *priv = NULL; + } return SSH_ERROR; } return SSH_OK; } +#endif /* OPENSSL_VERSION_NUMBER */ int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, - const bignum priv, const bignum pub) + bignum priv, bignum pub) { - bignum priv_key = NULL; - bignum pub_key = NULL; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + int rc; + OSSL_PARAM *params = NULL, *out_params = NULL, *merged_params = NULL; + OSSL_PARAM_BLD *param_bld = NULL; + EVP_PKEY_CTX *evp_ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || ((priv == NULL) && (pub == NULL)) || (ctx == NULL) || @@ -93,17 +169,85 @@ int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, return SSH_ERROR; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + (void)DH_set0_key(ctx->keypair[peer], pub, priv); + + return SSH_OK; +#else + rc = EVP_PKEY_todata(ctx->keypair[peer], EVP_PKEY_KEYPAIR, &out_params); + if (rc != 1) { + return SSH_ERROR; + } + + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) { + rc = SSH_ERROR; + goto out; + } + + evp_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, ctx->keypair[peer], NULL); + if (evp_ctx == NULL) { + rc = SSH_ERROR; + goto out; + } + + rc = EVP_PKEY_fromdata_init(evp_ctx); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } + if (priv) { - priv_key = priv; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PRIV_KEY, priv); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } } if (pub) { - pub_key = pub; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PUB_KEY, pub); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } } - (void)DH_set0_key(ctx->keypair[peer], pub_key, priv_key); - return SSH_OK; + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + rc = SSH_ERROR; + goto out; + } + OSSL_PARAM_BLD_free(param_bld); + + merged_params = OSSL_PARAM_merge(out_params, params); + if (merged_params == NULL) { + rc = SSH_ERROR; + goto out; + } + + rc = EVP_PKEY_fromdata(evp_ctx, + &(ctx->keypair[peer]), + EVP_PKEY_PUBLIC_KEY, + merged_params); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } + + rc = SSH_OK; +out: + bignum_safe_free(priv); + bignum_safe_free(pub); + EVP_PKEY_CTX_free(evp_ctx); + OSSL_PARAM_free(out_params); + OSSL_PARAM_free(params); + OSSL_PARAM_free(merged_params); + + return rc; +#endif /* OPENSSL_VERSION_NUMBER */ } +#if OPENSSL_VERSION_NUMBER < 0x30000000L int ssh_dh_get_parameters(struct dh_ctx *ctx, const_bignum *modulus, const_bignum *generator) { @@ -113,18 +257,51 @@ int ssh_dh_get_parameters(struct dh_ctx *ctx, DH_get0_pqg(ctx->keypair[0], modulus, NULL, generator); return SSH_OK; } +#else +int ssh_dh_get_parameters(struct dh_ctx *ctx, + bignum *modulus, bignum *generator) +{ + int rc; + + if (ctx == NULL || ctx->keypair[0] == NULL) { + return SSH_ERROR; + } + + rc = EVP_PKEY_get_bn_param(ctx->keypair[0], OSSL_PKEY_PARAM_FFC_P, (BIGNUM**)modulus); + if (rc != 1) { + return SSH_ERROR; + } + rc = EVP_PKEY_get_bn_param(ctx->keypair[0], OSSL_PKEY_PARAM_FFC_G, (BIGNUM**)generator); + if (rc != 1) { + bignum_safe_free(*modulus); + return SSH_ERROR; + } + + return SSH_OK; +} +#endif /* OPENSSL_VERSION_NUMBER */ int ssh_dh_set_parameters(struct dh_ctx *ctx, const bignum modulus, const bignum generator) { size_t i; int rc; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM *params = NULL; + OSSL_PARAM_BLD *param_bld = NULL; + EVP_PKEY_CTX *evp_ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ if ((ctx == NULL) || (modulus == NULL) || (generator == NULL)) { return SSH_ERROR; } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + evp_ctx = EVP_PKEY_CTX_new_from_name(NULL, "DHX", NULL); +#endif + for (i = 0; i < 2; i++) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L bignum p = NULL; bignum g = NULL; @@ -146,16 +323,78 @@ int ssh_dh_set_parameters(struct dh_ctx *ctx, rc = SSH_ERROR; goto done; } +#else + param_bld = OSSL_PARAM_BLD_new(); + + if (param_bld == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_P, modulus); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_G, generator); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + OSSL_PARAM_BLD_free(param_bld); + rc = SSH_ERROR; + goto done; + } + OSSL_PARAM_BLD_free(param_bld); + + rc = EVP_PKEY_fromdata_init(evp_ctx); + if (rc != 1) { + OSSL_PARAM_free(params); + rc = SSH_ERROR; + goto done; + } + + /* make sure to invalidate existing keys */ + EVP_PKEY_free(ctx->keypair[i]); + ctx->keypair[i] = NULL; + + rc = EVP_PKEY_fromdata(evp_ctx, + &(ctx->keypair[i]), + EVP_PKEY_KEY_PARAMETERS, + params); + if (rc != 1) { + OSSL_PARAM_free(params); + rc = SSH_ERROR; + goto done; + } + + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ } rc = SSH_OK; +#if OPENSSL_VERSION_NUMBER < 0x30000000L done: if (rc != SSH_OK) { DH_free(ctx->keypair[0]); DH_free(ctx->keypair[1]); + } +#else +done: + EVP_PKEY_CTX_free(evp_ctx); + + if (rc != SSH_OK) { + EVP_PKEY_free(ctx->keypair[0]); + EVP_PKEY_free(ctx->keypair[1]); + } +#endif /* OPENSSL_VERSION_NUMBER */ + if (rc != SSH_OK) { ctx->keypair[0] = NULL; ctx->keypair[1] = NULL; } + return rc; } @@ -202,8 +441,13 @@ int ssh_dh_init_common(struct ssh_crypto_struct *crypto) void ssh_dh_cleanup(struct ssh_crypto_struct *crypto) { if (crypto->dh_ctx != NULL) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L DH_free(crypto->dh_ctx->keypair[0]); DH_free(crypto->dh_ctx->keypair[1]); +#else + EVP_PKEY_free(crypto->dh_ctx->keypair[0]); + EVP_PKEY_free(crypto->dh_ctx->keypair[1]); +#endif /* OPENSSL_VERSION_NUMBER */ free(crypto->dh_ctx); crypto->dh_ctx = NULL; } @@ -212,7 +456,8 @@ void ssh_dh_cleanup(struct ssh_crypto_struct *crypto) /** @internal * @brief generates a secret DH parameter of at least DH_SECURITY_BITS * security as well as the corresponding public key. - * @param[out] parms a dh_ctx that will hold the new keys. + * + * @param[out] params a dh_ctx that will hold the new keys. * @param peer Select either client or server key storage. Valid values are: * DH_CLIENT_KEYPAIR or DH_SERVER_KEYPAIR * @@ -221,14 +466,43 @@ void ssh_dh_cleanup(struct ssh_crypto_struct *crypto) int ssh_dh_keypair_gen_keys(struct dh_ctx *dh_ctx, int peer) { int rc; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_CTX *evp_ctx = NULL; +#endif if ((dh_ctx == NULL) || (dh_ctx->keypair[peer] == NULL)) { return SSH_ERROR; } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L rc = DH_generate_key(dh_ctx->keypair[peer]); if (rc != 1) { return SSH_ERROR; } +#else + evp_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, dh_ctx->keypair[peer], NULL); + if (evp_ctx == NULL) { + return SSH_ERROR; + } + + rc = EVP_PKEY_keygen_init(evp_ctx); + if (rc != 1) { + EVP_PKEY_CTX_free(evp_ctx); + return SSH_ERROR; + } + + rc = EVP_PKEY_generate(evp_ctx, &(dh_ctx->keypair[peer])); + if (rc != 1) { + EVP_PKEY_CTX_free(evp_ctx); + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate DH: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + EVP_PKEY_CTX_free(evp_ctx); +#endif /* OPENSSL_VERSION_NUMBER */ + return SSH_OK; } @@ -247,8 +521,14 @@ int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, bignum *dest) { unsigned char *kstring = NULL; + int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum pub_key = NULL; - int klen, rc; + int klen; +#else + size_t klen; + EVP_PKEY_CTX *evp_ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ if ((dh_ctx == NULL) || (dh_ctx->keypair[local] == NULL) || @@ -256,6 +536,7 @@ int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, return SSH_ERROR; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L kstring = malloc(DH_size(dh_ctx->keypair[local])); if (kstring == NULL) { rc = SSH_ERROR; @@ -273,6 +554,43 @@ int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, rc = SSH_ERROR; goto done; } +#else + evp_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, dh_ctx->keypair[local], NULL); + + rc = EVP_PKEY_derive_init(evp_ctx); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + + rc = EVP_PKEY_derive_set_peer(evp_ctx, dh_ctx->keypair[remote]); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to set peer key: %s", + ERR_error_string(ERR_get_error(), NULL)); + rc = SSH_ERROR; + goto done; + } + + /* getting the size of the secret */ + rc = EVP_PKEY_derive(evp_ctx, kstring, &klen); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + + kstring = malloc(klen); + if (kstring == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = EVP_PKEY_derive(evp_ctx, kstring, &klen); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } +#endif /* OPENSSL_VERSION_NUMBER */ *dest = BN_bin2bn(kstring, klen, NULL); if (*dest == NULL) { @@ -282,6 +600,9 @@ int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, rc = SSH_OK; done: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_CTX_free(evp_ctx); +#endif free(kstring); return rc; } diff --git a/src/dh_key.c b/src/dh_key.c index bda54b17..20d24a31 100644 --- a/src/dh_key.c +++ b/src/dh_key.c @@ -289,8 +289,10 @@ void ssh_dh_cleanup(struct ssh_crypto_struct *crypto) /** @internal * @brief generates a secret DH parameter of at least DH_SECURITY_BITS * security as well as the corresponding public key. - * @param[out] parms a dh_kex paramters structure with preallocated bignum + * + * @param[out] params a dh_kex parameters structure with preallocated bignum * where to store the parameters + * * @return SSH_OK on success, SSH_ERROR on error */ int ssh_dh_keypair_gen_keys(struct dh_ctx *dh_ctx, int peer) @@ -43,6 +43,11 @@ struct ssh_packet_callbacks_struct ssh_ecdh_client_callbacks = { .user = NULL }; +void ssh_client_ecdh_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_ecdh_client_callbacks); +} + /** @internal * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back * a SSH_MSG_NEWKEYS @@ -55,7 +60,7 @@ SSH_PACKET_CALLBACK(ssh_packet_client_ecdh_reply){ (void)type; (void)user; - ssh_packet_remove_callbacks(session, &ssh_ecdh_client_callbacks); + ssh_client_ecdh_remove_callbacks(session); pubkey_blob = ssh_buffer_get_ssh_string(packet); if (pubkey_blob == NULL) { ssh_set_error(session,SSH_FATAL, "No public key in packet"); @@ -88,16 +93,10 @@ SSH_PACKET_CALLBACK(ssh_packet_client_ecdh_reply){ } /* Send the MSG_NEWKEYS */ - if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { - goto error; - } - - rc=ssh_packet_send(session); + rc = ssh_packet_send_newkeys(session); if (rc == SSH_ERROR) { goto error; } - - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; return SSH_PACKET_USED; diff --git a/src/ecdh_crypto.c b/src/ecdh_crypto.c index a1de27fd..817f066a 100644 --- a/src/ecdh_crypto.c +++ b/src/ecdh_crypto.c @@ -30,15 +30,27 @@ #ifdef HAVE_ECDH #include <openssl/ecdh.h> - +#if OPENSSL_VERSION_NUMBER < 0x30000000L #define NISTP256 NID_X9_62_prime256v1 #define NISTP384 NID_secp384r1 #define NISTP521 NID_secp521r1 +#else +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/param_build.h> +#include <openssl/params.h> +#include <openssl/core_names.h> +#include "libcrypto-compat.h" +#endif /* OPENSSL_VERSION_NUMBER */ /** @internal * @brief Map the given key exchange enum value to its curve name. */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L static int ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { +#else +static const char *ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { +#endif /* OPENSSL_VERSION_NUMBER */ if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256) { return NISTP256; } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP384) { @@ -46,88 +58,191 @@ static int ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP521) { return NISTP521; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L return SSH_ERROR; +#else + return NULL; +#endif } -/** @internal - * @brief Starts ecdh-sha2-nistp256 key exchange +/* @internal + * @brief Generate ECDH key pair for ecdh key exchange and store it in the + * session->next_crypto structure */ -int ssh_client_ecdh_init(ssh_session session){ - EC_KEY *key; - const EC_GROUP *group; - const EC_POINT *pubkey; - ssh_string client_pubkey; - int curve; - int len; - int rc; - bignum_CTX ctx = BN_CTX_new(); +static ssh_string ssh_ecdh_generate(ssh_session session) +{ + ssh_string pubkey_string = NULL; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const EC_POINT *point = NULL; + const EC_GROUP *group = NULL; + EC_KEY *key = NULL; + int curve; +#else + EC_POINT *point = NULL; + EC_GROUP *group = NULL; + const char *curve = NULL; + EVP_PKEY *key = NULL; + OSSL_PARAM *out_params = NULL; + const OSSL_PARAM *pubkey_param = NULL; + const void *pubkey = NULL; + size_t pubkey_len; + int nid; + int rc; +#endif /* OPENSSL_VERSION_NUMBER */ - rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT); - if (rc < 0) { - BN_CTX_free(ctx); - return SSH_ERROR; - } + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + if (curve == SSH_ERROR) { + SSH_LOG(SSH_LOG_TRACE, "Failed to get curve name"); + return NULL; + } - curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); - if (curve == SSH_ERROR) { - BN_CTX_free(ctx); - return SSH_ERROR; - } + key = EC_KEY_new_by_curve_name(curve); +#else + if (curve == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to get curve name"); + return NULL; + } - key = EC_KEY_new_by_curve_name(curve); - if (key == NULL) { - BN_CTX_free(ctx); - return SSH_ERROR; - } - group = EC_KEY_get0_group(key); + key = EVP_EC_gen(curve); +#endif /* OPENSSL_VERSION_NUMBER */ + if (key == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to generate key"); + return NULL; + } - EC_KEY_generate_key(key); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + group = EC_KEY_get0_group(key); - pubkey=EC_KEY_get0_public_key(key); - len = EC_POINT_point2oct(group,pubkey,POINT_CONVERSION_UNCOMPRESSED, - NULL,0,ctx); + EC_KEY_generate_key(key); - client_pubkey = ssh_string_new(len); - if (client_pubkey == NULL) { - BN_CTX_free(ctx); - EC_KEY_free(key); - return SSH_ERROR; - } + point = EC_KEY_get0_public_key(key); - EC_POINT_point2oct(group,pubkey,POINT_CONVERSION_UNCOMPRESSED, - ssh_string_data(client_pubkey),len,ctx); - BN_CTX_free(ctx); + pubkey_string = pki_key_make_ecpoint_string(group, point); +#else + rc = EVP_PKEY_todata(key, EVP_PKEY_PUBLIC_KEY, &out_params); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to export public key"); + EVP_PKEY_free(key); + return NULL; + } - rc = ssh_buffer_add_ssh_string(session->out_buffer,client_pubkey); - if (rc < 0) { - EC_KEY_free(key); - SSH_STRING_FREE(client_pubkey); - return SSH_ERROR; - } + pubkey_param = OSSL_PARAM_locate_const(out_params, OSSL_PKEY_PARAM_PUB_KEY); + if (pubkey_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to find public key"); + EVP_PKEY_free(key); + OSSL_PARAM_free(out_params); + return NULL; + } - session->next_crypto->ecdh_privkey = key; - session->next_crypto->ecdh_client_pubkey = client_pubkey; + rc = OSSL_PARAM_get_octet_string_ptr(pubkey_param, + (const void**)&pubkey, + &pubkey_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to read public key"); + OSSL_PARAM_free(out_params); + EVP_PKEY_free(key); + return NULL; + } - /* register the packet callbacks */ - ssh_packet_set_callbacks(session, &ssh_ecdh_client_callbacks); - session->dh_handshake_state = DH_STATE_INIT_SENT; + /* Convert the data to low-level representation */ + nid = pki_key_ecgroup_name_to_nid(curve); + group = EC_GROUP_new_by_curve_name_ex(NULL, NULL, nid); + if (group == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Could not create group: %s", + ERR_error_string(ERR_get_error(), NULL)); + OSSL_PARAM_free(out_params); + EVP_PKEY_free(key); + return NULL; + } + point = EC_POINT_new(group); + if (point == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Could not create point: %s", + ERR_error_string(ERR_get_error(), NULL)); + EC_GROUP_free(group); + OSSL_PARAM_free(out_params); + EVP_PKEY_free(key); + return NULL; + } + rc = EC_POINT_oct2point(group, point, pubkey, pubkey_len, NULL); + OSSL_PARAM_free(out_params); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to export public key"); + EC_GROUP_free(group); + EC_POINT_free(point); + EVP_PKEY_free(key); + return NULL; + } - rc = ssh_packet_send(session); + pubkey_string = pki_key_make_ecpoint_string(group, point); + EC_GROUP_free(group); + EC_POINT_free(point); +#endif /* OPENSSL_VERSION_NUMBER */ + if (pubkey_string == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to convert public key"); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(key); +#else + EVP_PKEY_free(key); +#endif /* OPENSSL_VERSION_NUMBER */ + return NULL; + } + session->next_crypto->ecdh_privkey = key; + return pubkey_string; +} - return rc; +/** @internal + * @brief Starts ecdh-sha2-nistp256 key exchange + */ +int ssh_client_ecdh_init(ssh_session session) +{ + ssh_string client_pubkey = NULL; + int rc; + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT); + if (rc < 0) { + return SSH_ERROR; + } + + client_pubkey = ssh_ecdh_generate(session); + if (client_pubkey == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, client_pubkey); + if (rc < 0) { + ssh_string_free(client_pubkey); + return SSH_ERROR; + } + + session->next_crypto->ecdh_client_pubkey = client_pubkey; + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_ecdh_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + + return rc; } -int ecdh_build_k(ssh_session session) { - const EC_GROUP *group = EC_KEY_get0_group(session->next_crypto->ecdh_privkey); - EC_POINT *pubkey; - void *buffer; +int ecdh_build_k(ssh_session session) +{ + struct ssh_crypto_struct *next_crypto = session->next_crypto; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const EC_GROUP *group = EC_KEY_get0_group(next_crypto->ecdh_privkey); + EC_POINT *pubkey = NULL; + void *buffer = NULL; int rc; int len = (EC_GROUP_get_degree(group) + 7) / 8; bignum_CTX ctx = bignum_ctx_new(); if (ctx == NULL) { return -1; } - pubkey = EC_POINT_new(group); if (pubkey == NULL) { bignum_ctx_free(ctx); @@ -137,14 +252,14 @@ int ecdh_build_k(ssh_session session) { if (session->server) { rc = EC_POINT_oct2point(group, pubkey, - ssh_string_data(session->next_crypto->ecdh_client_pubkey), - ssh_string_len(session->next_crypto->ecdh_client_pubkey), + ssh_string_data(next_crypto->ecdh_client_pubkey), + ssh_string_len(next_crypto->ecdh_client_pubkey), ctx); } else { rc = EC_POINT_oct2point(group, pubkey, - ssh_string_data(session->next_crypto->ecdh_server_pubkey), - ssh_string_len(session->next_crypto->ecdh_server_pubkey), + ssh_string_data(next_crypto->ecdh_server_pubkey), + ssh_string_len(next_crypto->ecdh_server_pubkey), ctx); } bignum_ctx_free(ctx); @@ -162,7 +277,7 @@ int ecdh_build_k(ssh_session session) { rc = ECDH_compute_key(buffer, len, pubkey, - session->next_crypto->ecdh_privkey, + next_crypto->ecdh_privkey, NULL); EC_POINT_clear_free(pubkey); if (rc <= 0) { @@ -170,23 +285,149 @@ int ecdh_build_k(ssh_session session) { return -1; } - bignum_bin2bn(buffer, len, &session->next_crypto->shared_secret); + bignum_bin2bn(buffer, len, &next_crypto->shared_secret); free(buffer); - if (session->next_crypto->shared_secret == NULL) { - EC_KEY_free(session->next_crypto->ecdh_privkey); - session->next_crypto->ecdh_privkey = NULL; +#else + const char *curve = NULL; + EVP_PKEY *pubkey = NULL; + void *secret = NULL; + size_t secret_len; + int rc; + ssh_string peer_pubkey = NULL; + OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); + EVP_PKEY_CTX *dh_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, + next_crypto->ecdh_privkey, + NULL); + + if (dh_ctx == NULL || param_bld == NULL) { + ssh_set_error_oom(session); + EVP_PKEY_CTX_free(dh_ctx); + OSSL_PARAM_BLD_free(param_bld); + return -1; + } + + rc = EVP_PKEY_derive_init(dh_ctx); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not init PKEY derive: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + OSSL_PARAM_BLD_free(param_bld); return -1; } - EC_KEY_free(session->next_crypto->ecdh_privkey); - session->next_crypto->ecdh_privkey = NULL; + + if (session->server) { + peer_pubkey = next_crypto->ecdh_client_pubkey; + } else { + peer_pubkey = next_crypto->ecdh_server_pubkey; + } + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, + OSSL_PKEY_PARAM_PUB_KEY, + ssh_string_data(peer_pubkey), + ssh_string_len(peer_pubkey)); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not push the pub key: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + OSSL_PARAM_BLD_free(param_bld); + return -1; + } + curve = ecdh_kex_type_to_curve(next_crypto->kex_type); + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, + OSSL_PKEY_PARAM_GROUP_NAME, + (char *)curve, + strlen(curve)); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not push the group name: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + OSSL_PARAM_BLD_free(param_bld); + return -1; + } + + rc = evp_build_pkey("EC", param_bld, &pubkey, EVP_PKEY_PUBLIC_KEY); + OSSL_PARAM_BLD_free(param_bld); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Could not build the pkey: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + rc = EVP_PKEY_derive_set_peer(dh_ctx, pubkey); + EVP_PKEY_free(pubkey); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not set peer pubkey: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + /* get the max length of the secret */ + rc = EVP_PKEY_derive(dh_ctx, NULL, &secret_len); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not set peer pubkey: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + secret = malloc(secret_len); + if (secret == NULL) { + ssh_set_error_oom(session); + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + rc = EVP_PKEY_derive(dh_ctx, secret, &secret_len); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not derive shared key: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + EVP_PKEY_CTX_free(dh_ctx); + + bignum_bin2bn(secret, secret_len, &next_crypto->shared_secret); + free(secret); +#endif /* OPENSSL_VERSION_NUMBER */ + if (next_crypto->shared_secret == NULL) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(next_crypto->ecdh_privkey); +#else + EVP_PKEY_free(next_crypto->ecdh_privkey); +#endif /* OPENSSL_VERSION_NUMBER */ + next_crypto->ecdh_privkey = NULL; + return -1; + } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(next_crypto->ecdh_privkey); +#else + EVP_PKEY_free(next_crypto->ecdh_privkey); +#endif /* OPENSSL_VERSION_NUMBER */ + next_crypto->ecdh_privkey = NULL; #ifdef DEBUG_CRYPTO ssh_log_hexdump("Session server cookie", - session->next_crypto->server_kex.cookie, 16); + next_crypto->server_kex.cookie, 16); ssh_log_hexdump("Session client cookie", - session->next_crypto->client_kex.cookie, 16); - ssh_print_bignum("Shared secret key", session->next_crypto->shared_secret); -#endif + next_crypto->client_kex.cookie, 16); + ssh_print_bignum("Shared secret key", next_crypto->shared_secret); +#endif /* DEBUG_CRYPTO */ return 0; } @@ -196,78 +437,36 @@ int ecdh_build_k(ssh_session session) { /** @brief Handle a SSH_MSG_KEXDH_INIT packet (server) and send a * SSH_MSG_KEXDH_REPLY */ -SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ +SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init) +{ /* ECDH keys */ - ssh_string q_c_string; - ssh_string q_s_string; - EC_KEY *ecdh_key; - const EC_GROUP *group; - const EC_POINT *ecdh_pubkey; - bignum_CTX ctx; - /* SSH host keys (rsa,dsa,ecdsa) */ + ssh_string q_c_string = NULL; + ssh_string q_s_string = NULL; + /* SSH host keys (rsa, ed25519 and ecdsa) */ ssh_key privkey; enum ssh_digest_e digest = SSH_DIGEST_AUTO; ssh_string sig_blob = NULL; ssh_string pubkey_blob = NULL; - int curve; - int len; int rc; (void)type; (void)user; + SSH_LOG(SSH_LOG_TRACE, "Processing SSH_MSG_KEXDH_INIT"); + ssh_packet_remove_callbacks(session, &ssh_ecdh_server_callbacks); /* Extract the client pubkey from the init packet */ q_c_string = ssh_buffer_get_ssh_string(packet); if (q_c_string == NULL) { - ssh_set_error(session,SSH_FATAL, "No Q_C ECC point in packet"); + ssh_set_error(session, SSH_FATAL, "No Q_C ECC point in packet"); goto error; } session->next_crypto->ecdh_client_pubkey = q_c_string; - /* Build server's keypair */ - - ctx = BN_CTX_new(); - - curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); - if (curve == SSH_ERROR) { - BN_CTX_free(ctx); - return SSH_ERROR; - } - - ecdh_key = EC_KEY_new_by_curve_name(curve); - if (ecdh_key == NULL) { - ssh_set_error_oom(session); - BN_CTX_free(ctx); - goto error; - } - - group = EC_KEY_get0_group(ecdh_key); - EC_KEY_generate_key(ecdh_key); - - ecdh_pubkey = EC_KEY_get0_public_key(ecdh_key); - len = EC_POINT_point2oct(group, - ecdh_pubkey, - POINT_CONVERSION_UNCOMPRESSED, - NULL, - 0, - ctx); - - q_s_string = ssh_string_new(len); + q_s_string = ssh_ecdh_generate(session); if (q_s_string == NULL) { - EC_KEY_free(ecdh_key); - BN_CTX_free(ctx); goto error; } - EC_POINT_point2oct(group, - ecdh_pubkey, - POINT_CONVERSION_UNCOMPRESSED, - ssh_string_data(q_s_string), - len, - ctx); - BN_CTX_free(ctx); - - session->next_crypto->ecdh_privkey = ecdh_key; session->next_crypto->ecdh_server_pubkey = q_s_string; /* build k and session_id */ @@ -317,24 +516,18 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEXDH_REPLY sent"); + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEXDH_REPLY sent"); rc = ssh_packet_send(session); if (rc == SSH_ERROR) { goto error; } - /* Send the MSG_NEWKEYS */ - rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); - if (rc < 0) { - goto error; - } - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; - rc = ssh_packet_send(session); - if (rc == SSH_ERROR){ + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); return SSH_PACKET_USED; error: diff --git a/src/ecdh_gcrypt.c b/src/ecdh_gcrypt.c index d9c41bf9..86d15f72 100644 --- a/src/ecdh_gcrypt.c +++ b/src/ecdh_gcrypt.c @@ -271,7 +271,7 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ ssh_string q_s_string; gcry_sexp_t param = NULL; gcry_sexp_t key = NULL; - /* SSH host keys (rsa,dsa,ecdsa) */ + /* SSH host keys (rsa, ed25519 and ecdsa) */ ssh_key privkey; enum ssh_digest_e digest = SSH_DIGEST_AUTO; ssh_string sig_blob = NULL; @@ -295,7 +295,7 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ } session->next_crypto->ecdh_client_pubkey = q_c_string; - /* Build server's keypair */ + /* Build server's key pair */ err = gcry_sexp_build(¶m, NULL, "(genkey(ecdh(curve %s) (flags transient-key)))", curve); if (err) { @@ -366,23 +366,19 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ goto out; } - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEXDH_REPLY sent"); + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEXDH_REPLY sent"); rc = ssh_packet_send(session); if (rc != SSH_OK) { goto out; } - + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; /* Send the MSG_NEWKEYS */ - rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); - if (rc != SSH_OK) { + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { goto out; } - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; - rc = ssh_packet_send(session); - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); - out: gcry_sexp_release(param); gcry_sexp_release(key); diff --git a/src/ecdh_mbedcrypto.c b/src/ecdh_mbedcrypto.c index 718f1522..1d9c8f36 100644 --- a/src/ecdh_mbedcrypto.c +++ b/src/ecdh_mbedcrypto.c @@ -34,6 +34,7 @@ #include <mbedtls/ecdh.h> #include <mbedtls/ecp.h> +#include "mbedcrypto-compat.h" #ifdef HAVE_ECDH @@ -54,6 +55,10 @@ int ssh_client_ecdh_init(ssh_session session) mbedtls_ecp_group grp; int rc; mbedtls_ecp_group_id curve; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + mbedtls_ecp_keypair *ecdh_privkey = NULL; + + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); if (curve == MBEDTLS_ECP_DP_NONE) { @@ -70,7 +75,9 @@ int ssh_client_ecdh_init(ssh_session session) return SSH_ERROR; } - mbedtls_ecp_keypair_init(session->next_crypto->ecdh_privkey); + ecdh_privkey = session->next_crypto->ecdh_privkey; + + mbedtls_ecp_keypair_init(ecdh_privkey); mbedtls_ecp_group_init(&grp); rc = mbedtls_ecp_group_load(&grp, curve); @@ -80,10 +87,10 @@ int ssh_client_ecdh_init(ssh_session session) } rc = mbedtls_ecp_gen_keypair(&grp, - &session->next_crypto->ecdh_privkey->d, - &session->next_crypto->ecdh_privkey->Q, - mbedtls_ctr_drbg_random, - ssh_get_mbedtls_ctr_drbg_context()); + &ecdh_privkey->MBEDTLS_PRIVATE(d), + &ecdh_privkey->MBEDTLS_PRIVATE(Q), + mbedtls_ctr_drbg_random, + ctr_drbg); if (rc != 0) { rc = SSH_ERROR; @@ -91,7 +98,7 @@ int ssh_client_ecdh_init(ssh_session session) } client_pubkey = make_ecpoint_string(&grp, - &session->next_crypto->ecdh_privkey->Q); + &ecdh_privkey->MBEDTLS_PRIVATE(Q)); if (client_pubkey == NULL) { rc = SSH_ERROR; goto out; @@ -124,6 +131,10 @@ int ecdh_build_k(ssh_session session) mbedtls_ecp_point pubkey; int rc; mbedtls_ecp_group_id curve; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + mbedtls_ecp_keypair *ecdh_privkey = NULL; + + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); if (curve == MBEDTLS_ECP_DP_NONE) { @@ -162,19 +173,20 @@ int ecdh_build_k(ssh_session session) mbedtls_mpi_init(session->next_crypto->shared_secret); + ecdh_privkey = session->next_crypto->ecdh_privkey; rc = mbedtls_ecdh_compute_shared(&grp, - session->next_crypto->shared_secret, - &pubkey, - &session->next_crypto->ecdh_privkey->d, - mbedtls_ctr_drbg_random, - ssh_get_mbedtls_ctr_drbg_context()); + session->next_crypto->shared_secret, + &pubkey, + &ecdh_privkey->MBEDTLS_PRIVATE(d), + mbedtls_ctr_drbg_random, + ctr_drbg); if (rc != 0) { rc = SSH_ERROR; goto out; } out: - mbedtls_ecp_keypair_free(session->next_crypto->ecdh_privkey); + mbedtls_ecp_keypair_free(ecdh_privkey); SAFE_FREE(session->next_crypto->ecdh_privkey); mbedtls_ecp_group_free(&grp); mbedtls_ecp_point_free(&pubkey); @@ -187,6 +199,8 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ ssh_string q_c_string = NULL; ssh_string q_s_string = NULL; mbedtls_ecp_group grp; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + mbedtls_ecp_keypair *ecdh_privkey = NULL; ssh_key privkey = NULL; enum ssh_digest_e digest = SSH_DIGEST_AUTO; ssh_string sig_blob = NULL; @@ -214,10 +228,14 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ return SSH_ERROR; } + ecdh_privkey = session->next_crypto->ecdh_privkey; + session->next_crypto->ecdh_client_pubkey = q_c_string; + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); + mbedtls_ecp_group_init(&grp); - mbedtls_ecp_keypair_init(session->next_crypto->ecdh_privkey); + mbedtls_ecp_keypair_init(ecdh_privkey); rc = mbedtls_ecp_group_load(&grp, curve); if (rc != 0) { @@ -226,16 +244,16 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ } rc = mbedtls_ecp_gen_keypair(&grp, - &session->next_crypto->ecdh_privkey->d, - &session->next_crypto->ecdh_privkey->Q, - mbedtls_ctr_drbg_random, - ssh_get_mbedtls_ctr_drbg_context()); + &ecdh_privkey->MBEDTLS_PRIVATE(d), + &ecdh_privkey->MBEDTLS_PRIVATE(Q), + mbedtls_ctr_drbg_random, + ctr_drbg); if (rc != 0) { rc = SSH_ERROR; goto out; } - q_s_string = make_ecpoint_string(&grp, &session->next_crypto->ecdh_privkey->Q); + q_s_string = make_ecpoint_string(&grp, &ecdh_privkey->MBEDTLS_PRIVATE(Q)); if (q_s_string == NULL) { rc = SSH_ERROR; goto out; @@ -293,23 +311,20 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ goto out; } - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEXDH_REPLY sent"); + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEXDH_REPLY sent"); rc = ssh_packet_send(session); if (rc != SSH_OK) { rc = SSH_ERROR; goto out; } - rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); - if (rc < 0) { - rc = SSH_ERROR; + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { goto out; } - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; - rc = ssh_packet_send(session); - SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); - out: mbedtls_ecp_group_free(&grp); if (rc == SSH_ERROR) { diff --git a/src/error.c b/src/error.c index 22180407..3f8d78cd 100644 --- a/src/error.c +++ b/src/error.c @@ -29,7 +29,7 @@ #include "libssh/session.h" /** - * @defgroup libssh_error The SSH error functions. + * @defgroup libssh_error The SSH error functions * @ingroup libssh * * Functions for error handling. @@ -63,8 +63,8 @@ void _ssh_set_error(void *error, va_end(va); err->error.error_code = code; - if (ssh_get_log_level() >= SSH_LOG_WARN) { - ssh_log_function(SSH_LOG_WARN, + if (ssh_get_log_level() == SSH_LOG_TRACE) { + ssh_log_function(SSH_LOG_TRACE, function, err->error.error_buffer); } @@ -142,7 +142,7 @@ const char *ssh_get_error(void *error) { * SSH_FATAL A fatal error occurred. This could be an unexpected * disconnection\n * - * Other error codes are internal but can be considered same than + * Other error codes are internal but can be considered the same as * SSH_FATAL. */ int ssh_get_error_code(void *error) { diff --git a/src/external/bcrypt_pbkdf.c b/src/external/bcrypt_pbkdf.c index 8b0b8118..85f4be47 100644 --- a/src/external/bcrypt_pbkdf.c +++ b/src/external/bcrypt_pbkdf.c @@ -42,7 +42,7 @@ * function with the following modifications: * 1. The input password and salt are preprocessed with SHA512. * 2. The output length is expanded to 256 bits. - * 3. Subsequently the magic string to be encrypted is lengthened and modifed + * 3. Subsequently the magic string to be encrypted is lengthened and modified * to "OxychromaticBlowfishSwatDynamite" * 4. The hash function is defined to perform 64 rounds of initial state * expansion. (More rounds are performed by iterating the hash.) @@ -166,7 +166,7 @@ bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltl } /* - * pbkdf2 deviation: ouput the key material non-linearly. + * pbkdf2 deviation: output the key material non-linearly. */ amt = MIN(amt, keylen); for (i = 0; i < amt; i++) { diff --git a/src/gcrypt_missing.c b/src/gcrypt_missing.c index e931ec5b..21a63a9b 100644 --- a/src/gcrypt_missing.c +++ b/src/gcrypt_missing.c @@ -55,7 +55,7 @@ char *ssh_gcry_bn2dec(bignum bn) { size = gcry_mpi_get_nbits(bn) * 3; rsize = size / 10 + size / 1000 + 2; - ret = malloc(rsize + 1); + ret = gcry_malloc(rsize + 1); if (ret == NULL) { return NULL; } diff --git a/src/getpass.c b/src/getpass.c index 99627665..6be33c77 100644 --- a/src/getpass.c +++ b/src/getpass.c @@ -44,7 +44,8 @@ * * @return 1 on success, 0 on error. */ -static int ssh_gets(const char *prompt, char *buf, size_t len, int verify) { +static int ssh_gets(const char *prompt, char *buf, size_t len, int verify) +{ char *tmp; char *ptr = NULL; int ok = 0; @@ -121,7 +122,8 @@ int ssh_getpass(const char *prompt, char *buf, size_t len, int echo, - int verify) { + int verify) +{ HANDLE h; DWORD mode = 0; int ok; @@ -213,7 +215,8 @@ int ssh_getpass(const char *prompt, char *buf, size_t len, int echo, - int verify) { + int verify) +{ struct termios attr; struct termios old_attr; int ok = 0; @@ -255,7 +258,11 @@ int ssh_getpass(const char *prompt, /* disable nonblocking I/O */ if (fd & O_NDELAY) { - fcntl(0, F_SETFL, fd & ~O_NDELAY); + ok = fcntl(0, F_SETFL, fd & ~O_NDELAY); + if (ok < 0) { + perror("fcntl"); + return -1; + } } ok = ssh_gets(prompt, buf, len, verify); @@ -267,7 +274,11 @@ int ssh_getpass(const char *prompt, /* close fd */ if (fd & O_NDELAY) { - fcntl(0, F_SETFL, fd); + ok = fcntl(0, F_SETFL, fd); + if (ok < 0) { + perror("fcntl"); + return -1; + } } if (!ok) { diff --git a/src/getrandom_crypto.c b/src/getrandom_crypto.c new file mode 100644 index 00000000..df8bd19f --- /dev/null +++ b/src/getrandom_crypto.c @@ -0,0 +1,64 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include <openssl/rand.h> + +/** + * @addtogroup libssh_misc + * + * @{ + */ + +/** + * @brief Get random bytes + * + * Make sure to always check the return code of this function! + * + * @param[in] where The buffer to fill with random bytes + * + * @param[in] len The size of the buffer to fill. + * + * @param[in] strong Use a strong or private RNG source. + * + * @return 1 on success, 0 on error. + */ +int +ssh_get_random(void *where, int len, int strong) +{ +#ifdef HAVE_OPENSSL_RAND_PRIV_BYTES + if (strong) { + /* Returns -1 when not supported, 0 on error, 1 on success */ + return !!RAND_priv_bytes(where, len); + } +#else + (void)strong; +#endif /* HAVE_RAND_PRIV_BYTES */ + + /* Returns -1 when not supported, 0 on error, 1 on success */ + return !!RAND_bytes(where, len); +} + +/** + * @} + */ diff --git a/src/getrandom_gcrypt.c b/src/getrandom_gcrypt.c new file mode 100644 index 00000000..da726405 --- /dev/null +++ b/src/getrandom_gcrypt.c @@ -0,0 +1,38 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * Copyright (C) 2016 g10 Code GmbH + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include <gcrypt.h> + +int +ssh_get_random(void *where, int len, int strong) +{ + /* variable not used in gcrypt */ + (void)strong; + + /* not using GCRY_VERY_STRONG_RANDOM which is a bit overkill */ + gcry_randomize(where, len, GCRY_STRONG_RANDOM); + + return 1; +} diff --git a/src/getrandom_mbedcrypto.c b/src/getrandom_mbedcrypto.c new file mode 100644 index 00000000..7e87b6a6 --- /dev/null +++ b/src/getrandom_mbedcrypto.c @@ -0,0 +1,52 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk <juraj.vijtiuk@sartura.hr> + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "mbedcrypto-compat.h" + +mbedtls_ctr_drbg_context ssh_mbedtls_ctr_drbg; + +int +ssh_mbedtls_random(void *where, int len, int strong) +{ + int rc = 0; + if (strong) { + mbedtls_ctr_drbg_set_prediction_resistance(&ssh_mbedtls_ctr_drbg, + MBEDTLS_CTR_DRBG_PR_ON); + rc = mbedtls_ctr_drbg_random(&ssh_mbedtls_ctr_drbg, where, len); + mbedtls_ctr_drbg_set_prediction_resistance(&ssh_mbedtls_ctr_drbg, + MBEDTLS_CTR_DRBG_PR_OFF); + } else { + rc = mbedtls_ctr_drbg_random(&ssh_mbedtls_ctr_drbg, where, len); + } + + return !rc; +} + +int +ssh_get_random(void *where, int len, int strong) +{ + return ssh_mbedtls_random(where, len, strong); +} diff --git a/src/gssapi.c b/src/gssapi.c index 488df582..5254d38d 100644 --- a/src/gssapi.c +++ b/src/gssapi.c @@ -68,15 +68,15 @@ struct ssh_gssapi_struct{ /** @internal * @initializes a gssapi context for authentication */ -static int ssh_gssapi_init(ssh_session session){ +static int ssh_gssapi_init(ssh_session session) +{ if (session->gssapi != NULL) return SSH_OK; - session->gssapi = malloc(sizeof(struct ssh_gssapi_struct)); - if(!session->gssapi){ + session->gssapi = calloc(1, sizeof(struct ssh_gssapi_struct)); + if (session->gssapi == NULL) { ssh_set_error_oom(session); return SSH_ERROR; } - ZERO_STRUCTP(session->gssapi); session->gssapi->server_creds = GSS_C_NO_CREDENTIAL; session->gssapi->client_creds = GSS_C_NO_CREDENTIAL; session->gssapi->ctx = GSS_C_NO_CONTEXT; @@ -87,7 +87,8 @@ static int ssh_gssapi_init(ssh_session session){ /** @internal * @frees a gssapi context */ -static void ssh_gssapi_free(ssh_session session){ +static void ssh_gssapi_free(ssh_session session) +{ OM_uint32 min; if (session->gssapi == NULL) return; @@ -114,7 +115,8 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token){ * @brief sends a SSH_MSG_USERAUTH_GSSAPI_RESPONSE packet * @param[in] oid the OID that was selected for authentication */ -static int ssh_gssapi_send_response(ssh_session session, ssh_string oid){ +static int ssh_gssapi_send_response(ssh_session session, ssh_string oid) +{ if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) < 0 || ssh_buffer_add_ssh_string(session->out_buffer,oid) < 0) { ssh_set_error_oom(session); @@ -184,8 +186,11 @@ out: /** @internal * @brief handles an user authentication using GSSAPI */ -int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n_oid, ssh_string *oids){ - char service_name[]="host"; +int +ssh_gssapi_handle_userauth(ssh_session session, const char *user, + uint32_t n_oid, ssh_string *oids) +{ + char service_name[] = "host"; gss_buffer_desc name_buf; gss_name_t server_name; /* local server fqdn */ OM_uint32 maj_stat, min_stat; @@ -218,11 +223,12 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n maj_stat = gss_indicate_mechs(&min_stat, &supported); if (maj_stat != GSS_S_COMPLETE) { - SSH_LOG(SSH_LOG_WARNING, "indicate mecks %d, %d", maj_stat, min_stat); - ssh_gssapi_log_error(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_DEBUG, "indicate mecks %d, %d", maj_stat, min_stat); + ssh_gssapi_log_error(SSH_LOG_DEBUG, "indicate mechs", maj_stat, min_stat); + gss_release_oid_set(&min_stat, &both_supported); return SSH_ERROR; } @@ -240,7 +246,7 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n continue; } if(len < 2 || oid_s[0] != SSH_OID_TAG || ((size_t)oid_s[1]) != len - 2){ - SSH_LOG(SSH_LOG_WARNING,"GSSAPI: received invalid OID"); + SSH_LOG(SSH_LOG_TRACE,"GSSAPI: received invalid OID"); continue; } oid.elements = &oid_s[2]; @@ -253,25 +259,28 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n } gss_release_oid_set(&min_stat, &supported); if (oid_count == 0){ - SSH_LOG(SSH_LOG_PROTOCOL,"GSSAPI: no OID match"); + SSH_LOG(SSH_LOG_DEBUG,"GSSAPI: no OID match"); ssh_auth_reply_default(session, 0); gss_release_oid_set(&min_stat, &both_supported); return SSH_OK; } /* from now we have room for context */ - if (ssh_gssapi_init(session) == SSH_ERROR) + if (ssh_gssapi_init(session) == SSH_ERROR) { + gss_release_oid_set(&min_stat, &both_supported); return SSH_ERROR; + } name_buf.value = service_name; name_buf.length = strlen(name_buf.value) + 1; maj_stat = gss_import_name(&min_stat, &name_buf, (gss_OID) GSS_C_NT_HOSTBASED_SERVICE, &server_name); if (maj_stat != GSS_S_COMPLETE) { - SSH_LOG(SSH_LOG_WARNING, "importing name %d, %d", maj_stat, min_stat); - ssh_gssapi_log_error(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_DEBUG, "importing name %d, %d", maj_stat, min_stat); + ssh_gssapi_log_error(SSH_LOG_DEBUG, "importing name", maj_stat, min_stat); + gss_release_oid_set(&min_stat, &both_supported); return -1; } @@ -282,8 +291,8 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n gss_release_oid_set(&min_stat, &both_supported); if (maj_stat != GSS_S_COMPLETE) { - SSH_LOG(SSH_LOG_WARNING, "error acquiring credentials %d, %d", maj_stat, min_stat); - ssh_gssapi_log_error(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_TRACE, "error acquiring credentials %d, %d", maj_stat, min_stat); + ssh_gssapi_log_error(SSH_LOG_TRACE, "acquiring creds", maj_stat, min_stat); @@ -291,7 +300,7 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n return SSH_ERROR; } - SSH_LOG(SSH_LOG_PROTOCOL, "acquiring credentials %d, %d", maj_stat, min_stat); + SSH_LOG(SSH_LOG_DEBUG, "acquiring credentials %d, %d", maj_stat, min_stat); /* finding which OID from client we selected */ for (i=0 ; i< n_oid ; ++i){ @@ -302,7 +311,7 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n continue; } if(len < 2 || oid_s[0] != SSH_OID_TAG || ((size_t)oid_s[1]) != len - 2){ - SSH_LOG(SSH_LOG_WARNING,"GSSAPI: received invalid OID"); + SSH_LOG(SSH_LOG_TRACE,"GSSAPI: received invalid OID"); continue; } oid.elements = &oid_s[2]; @@ -317,6 +326,7 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n session->gssapi->mech.elements = malloc(oid.length); if (session->gssapi->mech.elements == NULL){ ssh_set_error_oom(session); + gss_release_oid_set(&min_stat, &selected); return SSH_ERROR; } memcpy(session->gssapi->mech.elements, oid.elements, oid.length); @@ -327,17 +337,19 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n return ssh_gssapi_send_response(session, oids[i]); } -static char *ssh_gssapi_name_to_char(gss_name_t name){ +static char *ssh_gssapi_name_to_char(gss_name_t name) +{ gss_buffer_desc buffer; OM_uint32 maj_stat, min_stat; char *ptr; maj_stat = gss_display_name(&min_stat, name, &buffer, NULL); - ssh_gssapi_log_error(SSH_LOG_WARNING, + ssh_gssapi_log_error(SSH_LOG_DEBUG, "converting name", maj_stat, min_stat); ptr = malloc(buffer.length + 1); if (ptr == NULL) { + gss_release_buffer(&min_stat, &buffer); return NULL; } memcpy(ptr, buffer.value, buffer.length); @@ -407,7 +419,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server){ maj_stat = gss_accept_sec_context(&min_stat, &session->gssapi->ctx, session->gssapi->server_creds, &input_token, input_bindings, &client_name, NULL /*mech_oid*/, &output_token, &ret_flags, NULL /*time*/, &session->gssapi->client_creds); - ssh_gssapi_log_error(SSH_LOG_PROTOCOL, + ssh_gssapi_log_error(SSH_LOG_DEBUG, "accepting token", maj_stat, min_stat); @@ -417,10 +429,11 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server){ session->gssapi->canonic_user = ssh_gssapi_name_to_char(client_name); } if (GSS_ERROR(maj_stat)){ - ssh_gssapi_log_error(SSH_LOG_WARNING, + ssh_gssapi_log_error(SSH_LOG_DEBUG, "Gssapi error", maj_stat, min_stat); + gss_release_buffer(&min_stat, &output_token); ssh_auth_reply_default(session,0); ssh_gssapi_free(session); session->gssapi=NULL; @@ -431,13 +444,23 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server){ hexa = ssh_get_hexa(output_token.value, output_token.length); SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s",hexa); SAFE_FREE(hexa); - ssh_buffer_pack(session->out_buffer, - "bdP", - SSH2_MSG_USERAUTH_GSSAPI_TOKEN, - output_token.length, - (size_t)output_token.length, output_token.value); + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_USERAUTH_GSSAPI_TOKEN, + output_token.length, + (size_t)output_token.length, output_token.value); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + ssh_auth_reply_default(session, 0); + ssh_gssapi_free(session); + session->gssapi = NULL; + return SSH_PACKET_USED; + } ssh_packet_send(session); } + + gss_release_buffer(&min_stat, &output_token); + if(maj_stat == GSS_S_COMPLETE){ session->gssapi->state = SSH_GSSAPI_STATE_RCV_MIC; } @@ -465,8 +488,8 @@ static ssh_buffer ssh_gssapi_build_mic(ssh_session session) rc = ssh_buffer_pack(mic_buffer, "dPbsss", - crypto->digest_len, - (size_t)crypto->digest_len, crypto->session_id, + crypto->session_id_len, + crypto->session_id_len, crypto->session_id, SSH2_MSG_USERAUTH_REQUEST, session->gssapi->user, "ssh-connection", @@ -524,7 +547,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic) mic_token_buf.value = ssh_string_data(mic_token); maj_stat = gss_verify_mic(&min_stat, session->gssapi->ctx, &mic_buf, &mic_token_buf, NULL); - ssh_gssapi_log_error(SSH_LOG_PROTOCOL, + ssh_gssapi_log_error(SSH_LOG_DEBUG, "verifying MIC", maj_stat, min_stat); @@ -572,7 +595,8 @@ end: * @returns gssapi credentials handle. * @returns NULL if no forwardable token is available. */ -ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session){ +ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session) +{ if (!session || !session->gssapi || session->gssapi->client_creds == GSS_C_NO_CREDENTIAL) return NULL; return (ssh_gssapi_creds)session->gssapi->client_creds; @@ -581,10 +605,11 @@ ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session){ #endif /* SERVER */ /** - * @brief Set the forwadable ticket to be given to the server for authentication. + * @brief Set the forwardable ticket to be given to the server for authentication. * Unlike ssh_gssapi_get_creds() this is called on the client side of an ssh * connection. * + * @param[in] session The session * @param[in] creds gssapi credentials handle. */ void ssh_gssapi_set_creds(ssh_session session, const ssh_gssapi_creds creds) @@ -602,7 +627,9 @@ void ssh_gssapi_set_creds(ssh_session session, const ssh_gssapi_creds creds) session->gssapi->client.client_deleg_creds = (gss_cred_id_t)creds; } -static int ssh_gssapi_send_auth_mic(ssh_session session, ssh_string *oid_set, int n_oid){ +static int +ssh_gssapi_send_auth_mic(ssh_session session, ssh_string *oid_set, int n_oid) +{ int rc; int i; @@ -638,7 +665,7 @@ fail: static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids) { OM_uint32 maj_stat, min_stat, lifetime; - gss_OID_set actual_mechs; + gss_OID_set actual_mechs = GSS_C_NO_OID_SET; gss_buffer_desc namebuf; gss_name_t client_id = GSS_C_NO_NAME; gss_OID oid; @@ -700,6 +727,7 @@ static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids) ret = SSH_OK; end: + gss_release_oid_set(&min_stat, &actual_mechs); gss_release_name(&min_stat, &client_id); return ret; } @@ -711,9 +739,10 @@ end: * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again * later. */ -int ssh_gssapi_auth_mic(ssh_session session){ +int ssh_gssapi_auth_mic(ssh_session session) +{ size_t i; - gss_OID_set selected; /* oid selected for authentication */ + gss_OID_set selected = GSS_C_NO_OID_SET; /* oid selected for authentication */ ssh_string *oids = NULL; int rc; size_t n_oids = 0; @@ -739,8 +768,8 @@ int ssh_gssapi_auth_mic(ssh_session session){ (gss_OID)GSS_C_NT_HOSTBASED_SERVICE, &session->gssapi->client.server_name); if (maj_stat != GSS_S_COMPLETE) { - SSH_LOG(SSH_LOG_WARNING, "importing name %d, %d", maj_stat, min_stat); - ssh_gssapi_log_error(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_DEBUG, "importing name %d, %d", maj_stat, min_stat); + ssh_gssapi_log_error(SSH_LOG_DEBUG, "importing name", maj_stat, min_stat); @@ -754,7 +783,7 @@ int ssh_gssapi_auth_mic(ssh_session session){ return SSH_AUTH_ERROR; } - SSH_LOG(SSH_LOG_PROTOCOL, "Authenticating with gssapi to host %s with user %s", + SSH_LOG(SSH_LOG_DEBUG, "Authenticating with gssapi to host %s with user %s", session->opts.host, session->gssapi->user); rc = ssh_gssapi_match(session, &selected); if (rc == SSH_ERROR) { @@ -762,7 +791,7 @@ int ssh_gssapi_auth_mic(ssh_session session){ } n_oids = selected->count; - SSH_LOG(SSH_LOG_PROTOCOL, "Sending %zu oids", n_oids); + SSH_LOG(SSH_LOG_DEBUG, "Sending %zu oids", n_oids); oids = calloc(n_oids, sizeof(ssh_string)); if (oids == NULL) { @@ -790,6 +819,8 @@ out: SSH_STRING_FREE(oids[i]); } free(oids); + gss_release_oid_set(&min_stat, &selected); + if (rc != SSH_ERROR) { return SSH_AUTH_AGAIN; } @@ -834,6 +865,7 @@ static gss_OID ssh_gssapi_oid_from_string(ssh_string oid_s) } SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response){ + int rc; ssh_string oid_s; gss_uint32 maj_stat, min_stat; gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; @@ -875,7 +907,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response){ 0, NULL, &input_token, NULL, &output_token, NULL, NULL); if(GSS_ERROR(maj_stat)){ - ssh_gssapi_log_error(SSH_LOG_WARNING, + ssh_gssapi_log_error(SSH_LOG_DEBUG, "Initializing gssapi context", maj_stat, min_stat); @@ -885,14 +917,20 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response){ hexa = ssh_get_hexa(output_token.value, output_token.length); SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s", hexa); SAFE_FREE(hexa); - ssh_buffer_pack(session->out_buffer, - "bdP", - SSH2_MSG_USERAUTH_GSSAPI_TOKEN, - output_token.length, - (size_t)output_token.length, output_token.value); + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_USERAUTH_GSSAPI_TOKEN, + output_token.length, + (size_t)output_token.length, output_token.value); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } ssh_packet_send(session); session->auth.state = SSH_AUTH_STATE_GSSAPI_TOKEN; } + + gss_release_buffer(&min_stat, &output_token); return SSH_PACKET_USED; error: @@ -902,7 +940,8 @@ error: return SSH_PACKET_USED; } -static int ssh_gssapi_send_mic(ssh_session session){ +static int ssh_gssapi_send_mic(ssh_session session) +{ OM_uint32 maj_stat, min_stat; gss_buffer_desc mic_buf = GSS_C_EMPTY_BUFFER; gss_buffer_desc mic_token_buf = GSS_C_EMPTY_BUFFER; @@ -921,9 +960,11 @@ static int ssh_gssapi_send_mic(ssh_session session){ maj_stat = gss_get_mic(&min_stat,session->gssapi->ctx, GSS_C_QOP_DEFAULT, &mic_buf, &mic_token_buf); + + SSH_BUFFER_FREE(mic_buffer); + if (GSS_ERROR(maj_stat)){ - SSH_BUFFER_FREE(mic_buffer); - ssh_gssapi_log_error(SSH_LOG_PROTOCOL, + ssh_gssapi_log_error(SSH_LOG_DEBUG, "generating MIC", maj_stat, min_stat); @@ -935,8 +976,10 @@ static int ssh_gssapi_send_mic(ssh_session session){ SSH2_MSG_USERAUTH_GSSAPI_MIC, mic_token_buf.length, (size_t)mic_token_buf.length, mic_token_buf.value); + + gss_release_buffer(&min_stat, &mic_token_buf); + if (rc != SSH_OK) { - SSH_BUFFER_FREE(mic_buffer); ssh_set_error_oom(session); return SSH_ERROR; } @@ -945,6 +988,7 @@ static int ssh_gssapi_send_mic(ssh_session session){ } SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client){ + int rc; ssh_string token; char *hexa; OM_uint32 maj_stat, min_stat; @@ -980,13 +1024,13 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client){ 0, NULL, &input_token, NULL, &output_token, NULL, NULL); - ssh_gssapi_log_error(SSH_LOG_PROTOCOL, + ssh_gssapi_log_error(SSH_LOG_DEBUG, "accepting token", maj_stat, min_stat); SSH_STRING_FREE(token); if (GSS_ERROR(maj_stat)){ - ssh_gssapi_log_error(SSH_LOG_PROTOCOL, + ssh_gssapi_log_error(SSH_LOG_DEBUG, "Gssapi error", maj_stat, min_stat); @@ -997,14 +1041,20 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client){ hexa = ssh_get_hexa(output_token.value, output_token.length); SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s",hexa); SAFE_FREE(hexa); - ssh_buffer_pack(session->out_buffer, - "bdP", - SSH2_MSG_USERAUTH_GSSAPI_TOKEN, - output_token.length, - (size_t)output_token.length, output_token.value); + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_USERAUTH_GSSAPI_TOKEN, + output_token.length, + (size_t)output_token.length, output_token.value); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } ssh_packet_send(session); } + gss_release_buffer(&min_stat, &output_token); + if (maj_stat == GSS_S_COMPLETE) { ssh_gssapi_send_mic(session); session->auth.state = SSH_AUTH_STATE_GSSAPI_MIC_SENT; @@ -24,211 +24,237 @@ #include "config.h" -#include <string.h> #include <stdlib.h> +#include <string.h> #include <zlib.h> -#include "libssh/priv.h" #include "libssh/buffer.h" #include "libssh/crypto.h" +#include "libssh/priv.h" #include "libssh/session.h" #ifndef BLOCKSIZE #define BLOCKSIZE 4092 #endif -static z_stream *initcompress(ssh_session session, int level) { - z_stream *stream = NULL; - int status; +static z_stream * +initcompress(ssh_session session, int level) +{ + z_stream *stream = NULL; + int status; - stream = calloc(1, sizeof(z_stream)); - if (stream == NULL) { - return NULL; - } + stream = calloc(1, sizeof(z_stream)); + if (stream == NULL) { + return NULL; + } - status = deflateInit(stream, level); - if (status != Z_OK) { - SAFE_FREE(stream); - ssh_set_error(session, SSH_FATAL, - "status %d inititalising zlib deflate", status); - return NULL; - } + status = deflateInit(stream, level); + if (status != Z_OK) { + SAFE_FREE(stream); + ssh_set_error(session, + SSH_FATAL, + "status %d initialising zlib deflate", + status); + return NULL; + } - return stream; + return stream; } -static ssh_buffer gzip_compress(ssh_session session, ssh_buffer source, int level) +static ssh_buffer +gzip_compress(ssh_session session, ssh_buffer source, int level) { - struct ssh_crypto_struct *crypto = NULL; - z_stream *zout = NULL; - void *in_ptr = ssh_buffer_get(source); - unsigned long in_size = ssh_buffer_get_len(source); - ssh_buffer dest = NULL; - unsigned char out_buf[BLOCKSIZE] = {0}; - unsigned long len; - int status; - - crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); - if (crypto == NULL) { - return NULL; - } - zout = crypto->compress_out_ctx; - if (zout == NULL) { - zout = crypto->compress_out_ctx = initcompress(session, level); + struct ssh_crypto_struct *crypto = NULL; + z_stream *zout = NULL; + void *in_ptr = ssh_buffer_get(source); + uint32_t in_size = ssh_buffer_get_len(source); + ssh_buffer dest = NULL; + unsigned char out_buf[BLOCKSIZE] = {0}; + uint32_t len; + int status; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); + if (crypto == NULL) { + return NULL; + } + zout = crypto->compress_out_ctx; if (zout == NULL) { - return NULL; + zout = crypto->compress_out_ctx = initcompress(session, level); + if (zout == NULL) { + return NULL; + } } - } - - dest = ssh_buffer_new(); - if (dest == NULL) { - return NULL; - } - zout->next_out = out_buf; - zout->next_in = in_ptr; - zout->avail_in = in_size; - do { - zout->avail_out = BLOCKSIZE; - status = deflate(zout, Z_PARTIAL_FLUSH); - if (status != Z_OK) { - SSH_BUFFER_FREE(dest); - ssh_set_error(session, SSH_FATAL, - "status %d deflating zlib packet", status); - return NULL; - } - len = BLOCKSIZE - zout->avail_out; - if (ssh_buffer_add_data(dest, out_buf, len) < 0) { - SSH_BUFFER_FREE(dest); - return NULL; + dest = ssh_buffer_new(); + if (dest == NULL) { + return NULL; } - zout->next_out = out_buf; - } while (zout->avail_out == 0); - return dest; + zout->next_out = out_buf; + zout->next_in = in_ptr; + zout->avail_in = in_size; + do { + zout->avail_out = BLOCKSIZE; + status = deflate(zout, Z_PARTIAL_FLUSH); + if (status != Z_OK) { + SSH_BUFFER_FREE(dest); + ssh_set_error(session, + SSH_FATAL, + "status %d deflating zlib packet", + status); + return NULL; + } + len = BLOCKSIZE - zout->avail_out; + if (ssh_buffer_add_data(dest, out_buf, len) < 0) { + SSH_BUFFER_FREE(dest); + return NULL; + } + zout->next_out = out_buf; + } while (zout->avail_out == 0); + + return dest; } -int compress_buffer(ssh_session session, ssh_buffer buf) { - ssh_buffer dest = NULL; +int +compress_buffer(ssh_session session, ssh_buffer buf) +{ + ssh_buffer dest = NULL; + int rv; - dest = gzip_compress(session, buf, session->opts.compressionlevel); - if (dest == NULL) { - return -1; - } + dest = gzip_compress(session, buf, session->opts.compressionlevel); + if (dest == NULL) { + return -1; + } - if (ssh_buffer_reinit(buf) < 0) { - SSH_BUFFER_FREE(dest); - return -1; - } + if (ssh_buffer_reinit(buf) < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } - if (ssh_buffer_add_data(buf, ssh_buffer_get(dest), ssh_buffer_get_len(dest)) < 0) { - SSH_BUFFER_FREE(dest); - return -1; - } + rv = ssh_buffer_add_data(buf, + ssh_buffer_get(dest), + ssh_buffer_get_len(dest)); + if (rv < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } - SSH_BUFFER_FREE(dest); - return 0; + SSH_BUFFER_FREE(dest); + return 0; } /* decompression */ -static z_stream *initdecompress(ssh_session session) { - z_stream *stream = NULL; - int status; +static z_stream * +initdecompress(ssh_session session) +{ + z_stream *stream = NULL; + int status; - stream = calloc(1, sizeof(z_stream)); - if (stream == NULL) { - return NULL; - } + stream = calloc(1, sizeof(z_stream)); + if (stream == NULL) { + return NULL; + } - status = inflateInit(stream); - if (status != Z_OK) { - SAFE_FREE(stream); - ssh_set_error(session, SSH_FATAL, - "Status = %d initiating inflate context!", status); - return NULL; - } + status = inflateInit(stream); + if (status != Z_OK) { + SAFE_FREE(stream); + ssh_set_error(session, + SSH_FATAL, + "Status = %d initiating inflate context!", + status); + return NULL; + } - return stream; + return stream; } -static ssh_buffer gzip_decompress(ssh_session session, ssh_buffer source, size_t maxlen) +static ssh_buffer +gzip_decompress(ssh_session session, ssh_buffer source, size_t maxlen) { - struct ssh_crypto_struct *crypto = NULL; - z_stream *zin = NULL; - void *in_ptr = ssh_buffer_get(source); - unsigned long in_size = ssh_buffer_get_len(source); - unsigned char out_buf[BLOCKSIZE] = {0}; - ssh_buffer dest = NULL; - unsigned long len; - int status; - - crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); - if (crypto == NULL) { - return NULL; - } - - zin = crypto->compress_in_ctx; - if (zin == NULL) { - zin = crypto->compress_in_ctx = initdecompress(session); - if (zin == NULL) { - return NULL; + struct ssh_crypto_struct *crypto = NULL; + z_stream *zin = NULL; + void *in_ptr = ssh_buffer_get(source); + uint32_t in_size = ssh_buffer_get_len(source); + unsigned char out_buf[BLOCKSIZE] = {0}; + ssh_buffer dest = NULL; + uint32_t len; + int status; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto == NULL) { + return NULL; } - } - - dest = ssh_buffer_new(); - if (dest == NULL) { - return NULL; - } - - zin->next_out = out_buf; - zin->next_in = in_ptr; - zin->avail_in = in_size; - do { - zin->avail_out = BLOCKSIZE; - status = inflate(zin, Z_PARTIAL_FLUSH); - if (status != Z_OK && status != Z_BUF_ERROR) { - ssh_set_error(session, SSH_FATAL, - "status %d inflating zlib packet", status); - SSH_BUFFER_FREE(dest); - return NULL; + zin = crypto->compress_in_ctx; + if (zin == NULL) { + zin = crypto->compress_in_ctx = initdecompress(session); + if (zin == NULL) { + return NULL; + } } - len = BLOCKSIZE - zin->avail_out; - if (ssh_buffer_add_data(dest,out_buf,len) < 0) { - SSH_BUFFER_FREE(dest); - return NULL; - } - if (ssh_buffer_get_len(dest) > maxlen){ - /* Size of packet exceeded, avoid a denial of service attack */ - SSH_BUFFER_FREE(dest); - return NULL; + dest = ssh_buffer_new(); + if (dest == NULL) { + return NULL; } - zin->next_out = out_buf; - } while (zin->avail_out == 0); - return dest; + zin->next_out = out_buf; + zin->next_in = in_ptr; + zin->avail_in = in_size; + + do { + zin->avail_out = BLOCKSIZE; + status = inflate(zin, Z_PARTIAL_FLUSH); + if (status != Z_OK && status != Z_BUF_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "status %d inflating zlib packet", + status); + SSH_BUFFER_FREE(dest); + return NULL; + } + + len = BLOCKSIZE - zin->avail_out; + if (ssh_buffer_add_data(dest, out_buf, len) < 0) { + SSH_BUFFER_FREE(dest); + return NULL; + } + if (ssh_buffer_get_len(dest) > maxlen) { + /* Size of packet exceeded, avoid a denial of service attack */ + SSH_BUFFER_FREE(dest); + return NULL; + } + zin->next_out = out_buf; + } while (zin->avail_out == 0); + + return dest; } -int decompress_buffer(ssh_session session,ssh_buffer buf, size_t maxlen){ - ssh_buffer dest = NULL; +int +decompress_buffer(ssh_session session, ssh_buffer buf, size_t maxlen) +{ + ssh_buffer dest = NULL; + int rv; - dest = gzip_decompress(session,buf, maxlen); - if (dest == NULL) { - return -1; - } + dest = gzip_decompress(session, buf, maxlen); + if (dest == NULL) { + return -1; + } - if (ssh_buffer_reinit(buf) < 0) { - SSH_BUFFER_FREE(dest); - return -1; - } + if (ssh_buffer_reinit(buf) < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } - if (ssh_buffer_add_data(buf, ssh_buffer_get(dest), ssh_buffer_get_len(dest)) < 0) { - SSH_BUFFER_FREE(dest); - return -1; - } + rv = ssh_buffer_add_data(buf, + ssh_buffer_get(dest), + ssh_buffer_get_len(dest)); + if (rv < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } - SSH_BUFFER_FREE(dest); - return 0; + SSH_BUFFER_FREE(dest); + return 0; } @@ -22,6 +22,9 @@ */ #include "config.h" + +#include <stdio.h> + #include "libssh/priv.h" #include "libssh/socket.h" #include "libssh/dh.h" @@ -104,7 +107,7 @@ _ret: /** * @brief Initialize global cryptographic data structures. * - * This functions is automatically called when the library is loaded. + * This function is automatically called when the library is loaded. * */ void libssh_constructor(void) @@ -127,7 +130,7 @@ void libssh_constructor(void) * The libssh library is implementing the SSH protocols and some of its * extensions. This group of functions is mostly used to implement an SSH * client. - * Some function are needed to implement an SSH server too. + * Some functions are needed to implement an SSH server too. * * @{ */ @@ -136,8 +139,8 @@ void libssh_constructor(void) * @brief Initialize global cryptographic data structures. * * Since version 0.8.0, when libssh is dynamically linked, it is not necessary - * to call this function on systems which are fully supported with regards to - * threading (that is, system with pthreads available). + * to call this function on systems that fully support threading (that is, + * systems with pthreads available). * * If libssh is statically linked, it is necessary to explicitly call ssh_init() * before calling any other provided API, and it is necessary to explicitly call @@ -161,12 +164,14 @@ static int _ssh_finalize(unsigned destructor) { if (_ssh_initialized > 1) { _ssh_initialized--; - goto _ret; + ssh_mutex_unlock(&ssh_init_mutex); + return 0; } if (_ssh_initialized == 1) { if (_ssh_init_ret < 0) { - goto _ret; + ssh_mutex_unlock(&ssh_init_mutex); + return 0; } } } @@ -181,15 +186,22 @@ static int _ssh_finalize(unsigned destructor) { _ssh_initialized = 0; -_ret: if (!destructor) { ssh_mutex_unlock(&ssh_init_mutex); } + +#if (defined(_WIN32) && !defined(HAVE_PTHREAD)) + if (ssh_init_mutex != NULL) { + DeleteCriticalSection(ssh_init_mutex); + SAFE_FREE(ssh_init_mutex); + } +#endif + return 0; } /** - * @brief Finalize and cleanup all libssh and cryptographic data structures. + * @brief Finalize and clean up all libssh and cryptographic data structures. * * This function is automatically called when the library is unloaded. * @@ -206,7 +218,7 @@ void libssh_destructor(void) } /** - * @brief Finalize and cleanup all libssh and cryptographic data structures. + * @brief Finalize and clean up all libssh and cryptographic data structures. * * Since version 0.8.0, when libssh is dynamically linked, it is not necessary * to call this function, since it is automatically called when the library is @@ -269,7 +281,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, * * @see ssh_init() */ -bool is_ssh_initialized() { +bool is_ssh_initialized(void) { bool is_initialized = false; @@ -39,7 +39,7 @@ /* The following implements the SSHKDF for crypto backend that - * do not have a native implementations */ + * do not have a native implementation */ struct ssh_mac_ctx_struct { enum ssh_kdf_digest digest_type; union { @@ -58,73 +58,110 @@ static ssh_mac_ctx ssh_mac_ctx_init(enum ssh_kdf_digest type) } ctx->digest_type = type; - switch(type){ + switch (type) { case SSH_KDF_SHA1: ctx->ctx.sha1_ctx = sha1_init(); + if (ctx->ctx.sha1_ctx == NULL) { + goto err; + } return ctx; case SSH_KDF_SHA256: ctx->ctx.sha256_ctx = sha256_init(); + if (ctx->ctx.sha256_ctx == NULL) { + goto err; + } return ctx; case SSH_KDF_SHA384: ctx->ctx.sha384_ctx = sha384_init(); + if (ctx->ctx.sha384_ctx == NULL) { + goto err; + } return ctx; case SSH_KDF_SHA512: ctx->ctx.sha512_ctx = sha512_init(); + if (ctx->ctx.sha512_ctx == NULL) { + goto err; + } return ctx; - default: - SAFE_FREE(ctx); - return NULL; } +err: + SAFE_FREE(ctx); + return NULL; } -static void ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) +static void ssh_mac_ctx_free(ssh_mac_ctx ctx) { - switch(ctx->digest_type){ + if (ctx == NULL) { + return; + } + + switch (ctx->digest_type) { case SSH_KDF_SHA1: - sha1_update(ctx->ctx.sha1_ctx, data, len); + sha1_ctx_free(ctx->ctx.sha1_ctx); break; case SSH_KDF_SHA256: - sha256_update(ctx->ctx.sha256_ctx, data, len); + sha256_ctx_free(ctx->ctx.sha256_ctx); break; case SSH_KDF_SHA384: - sha384_update(ctx->ctx.sha384_ctx, data, len); + sha384_ctx_free(ctx->ctx.sha384_ctx); break; case SSH_KDF_SHA512: - sha512_update(ctx->ctx.sha512_ctx, data, len); + sha512_ctx_free(ctx->ctx.sha512_ctx); break; } + SAFE_FREE(ctx); } -static void ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) +static int ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) { - switch(ctx->digest_type){ + switch (ctx->digest_type) { + case SSH_KDF_SHA1: + return sha1_update(ctx->ctx.sha1_ctx, data, len); + case SSH_KDF_SHA256: + return sha256_update(ctx->ctx.sha256_ctx, data, len); + case SSH_KDF_SHA384: + return sha384_update(ctx->ctx.sha384_ctx, data, len); + case SSH_KDF_SHA512: + return sha512_update(ctx->ctx.sha512_ctx, data, len); + } + return SSH_ERROR; +} + +static int ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) +{ + int rc = SSH_ERROR; + + switch (ctx->digest_type) { case SSH_KDF_SHA1: - sha1_final(md,ctx->ctx.sha1_ctx); + rc = sha1_final(md, ctx->ctx.sha1_ctx); break; case SSH_KDF_SHA256: - sha256_final(md,ctx->ctx.sha256_ctx); + rc = sha256_final(md, ctx->ctx.sha256_ctx); break; case SSH_KDF_SHA384: - sha384_final(md,ctx->ctx.sha384_ctx); + rc = sha384_final(md, ctx->ctx.sha384_ctx); break; case SSH_KDF_SHA512: - sha512_final(md,ctx->ctx.sha512_ctx); + rc = sha512_final(md, ctx->ctx.sha512_ctx); break; } SAFE_FREE(ctx); + return rc; } int sshkdf_derive_key(struct ssh_crypto_struct *crypto, - unsigned char *key, size_t key_len, - int key_type, unsigned char *output, + unsigned char *key, + size_t key_len, + uint8_t key_type, + unsigned char *output, size_t requested_len) { /* Can't use VLAs with Visual Studio, so allocate the biggest * digest buffer we can possibly need */ unsigned char digest[DIGEST_MAX_LEN]; size_t output_len = crypto->digest_len; - char letter = key_type; ssh_mac_ctx ctx; + int rc; if (DIGEST_MAX_LEN < crypto->digest_len) { return -1; @@ -135,11 +172,30 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, return -1; } - ssh_mac_update(ctx, key, key_len); - ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); - ssh_mac_update(ctx, &letter, 1); - ssh_mac_update(ctx, crypto->session_id, crypto->digest_len); - ssh_mac_final(digest, ctx); + rc = ssh_mac_update(ctx, key, key_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, &key_type, 1); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, crypto->session_id, crypto->session_id_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_final(digest, ctx); + if (rc != SSH_OK) { + return -1; + } if (requested_len < output_len) { output_len = requested_len; @@ -151,10 +207,25 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, if (ctx == NULL) { return -1; } - ssh_mac_update(ctx, key, key_len); - ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); - ssh_mac_update(ctx, output, output_len); - ssh_mac_final(digest, ctx); + rc = ssh_mac_update(ctx, key, key_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, output, output_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_final(digest, ctx); + if (rc != SSH_OK) { + return -1; + } if (requested_len < output_len + crypto->digest_len) { memcpy(output + output_len, digest, requested_len - output_len); } else { @@ -28,6 +28,7 @@ #include <stdio.h> #include <stdbool.h> +#include "libssh/libssh.h" #include "libssh/priv.h" #include "libssh/buffer.h" #include "libssh/dh.h" @@ -88,60 +89,64 @@ #endif /* HAVE_LIBCRYPTO */ #ifdef WITH_ZLIB -#define ZLIB "none,zlib,zlib@openssh.com" +#define ZLIB "none,zlib@openssh.com,zlib" +#define ZLIB_DEFAULT "none,zlib@openssh.com" #else #define ZLIB "none" -#endif +#define ZLIB_DEFAULT "none" +#endif /* WITH_ZLIB */ #ifdef HAVE_CURVE25519 #define CURVE25519 "curve25519-sha256,curve25519-sha256@libssh.org," #else #define CURVE25519 "" -#endif +#endif /* HAVE_CURVE25519 */ -#ifdef HAVE_ECDH +#ifdef HAVE_ECC #define ECDH "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521," -#define EC_HOSTKEYS "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,ecdsa-sha2-nistp256," -#define EC_PUBLIC_KEY_ALGORITHMS "ecdsa-sha2-nistp521-cert-v01@openssh.com," \ +#define EC_HOSTKEYS "ecdsa-sha2-nistp521," \ + "ecdsa-sha2-nistp384," \ + "ecdsa-sha2-nistp256," +#define EC_SK_HOSTKEYS "sk-ecdsa-sha2-nistp256@openssh.com," +#define EC_FIPS_PUBLIC_KEY_ALGOS "ecdsa-sha2-nistp521-cert-v01@openssh.com," \ "ecdsa-sha2-nistp384-cert-v01@openssh.com," \ "ecdsa-sha2-nistp256-cert-v01@openssh.com," +#define EC_PUBLIC_KEY_ALGORITHMS EC_FIPS_PUBLIC_KEY_ALGOS \ + "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com," #else +#define ECDH "" #define EC_HOSTKEYS "" +#define EC_SK_HOSTKEYS "" +#define EC_FIPS_PUBLIC_KEY_ALGOS "" #define EC_PUBLIC_KEY_ALGORITHMS "" -#define ECDH "" -#endif - -#ifdef HAVE_DSA -#define DSA_HOSTKEYS ",ssh-dss" -#define DSA_PUBLIC_KEY_ALGORITHMS ",ssh-dss-cert-v01@openssh.com" -#else -#define DSA_HOSTKEYS "" -#define DSA_PUBLIC_KEY_ALGORITHMS "" -#endif +#endif /* HAVE_ECC */ #ifdef WITH_INSECURE_NONE #define NONE ",none" #else #define NONE -#endif +#endif /* WITH_INSECURE_NONE */ #define HOSTKEYS "ssh-ed25519," \ EC_HOSTKEYS \ + "sk-ssh-ed25519@openssh.com," \ + EC_SK_HOSTKEYS \ "rsa-sha2-512," \ "rsa-sha2-256," \ - "ssh-rsa" \ - DSA_HOSTKEYS + "ssh-rsa" #define DEFAULT_HOSTKEYS "ssh-ed25519," \ EC_HOSTKEYS \ + "sk-ssh-ed25519@openssh.com," \ + EC_SK_HOSTKEYS \ "rsa-sha2-512," \ "rsa-sha2-256" #define PUBLIC_KEY_ALGORITHMS "ssh-ed25519-cert-v01@openssh.com," \ + "sk-ssh-ed25519-cert-v01@openssh.com," \ EC_PUBLIC_KEY_ALGORITHMS \ "rsa-sha2-512-cert-v01@openssh.com," \ "rsa-sha2-256-cert-v01@openssh.com," \ - "ssh-rsa-cert-v01@openssh.com" \ - DSA_PUBLIC_KEY_ALGORITHMS "," \ + "ssh-rsa-cert-v01@openssh.com," \ HOSTKEYS #define DEFAULT_PUBLIC_KEY_ALGORITHMS "ssh-ed25519-cert-v01@openssh.com," \ EC_PUBLIC_KEY_ALGORITHMS \ @@ -173,6 +178,9 @@ /* RFC 8308 */ #define KEX_EXTENSION_CLIENT "ext-info-c" +/* Strict kex mitigation against CVE-2023-48795 */ +#define KEX_STRICT_CLIENT "kex-strict-c-v00@openssh.com" +#define KEX_STRICT_SERVER "kex-strict-s-v00@openssh.com" /* Allowed algorithms in FIPS mode */ #define FIPS_ALLOWED_CIPHERS "aes256-gcm@openssh.com,"\ @@ -186,7 +194,7 @@ "rsa-sha2-512," \ "rsa-sha2-256" -#define FIPS_ALLOWED_PUBLIC_KEY_ALGORITHMS EC_PUBLIC_KEY_ALGORITHMS \ +#define FIPS_ALLOWED_PUBLIC_KEY_ALGORITHMS EC_FIPS_PUBLIC_KEY_ALGOS \ "rsa-sha2-512-cert-v01@openssh.com," \ "rsa-sha2-256-cert-v01@openssh.com," \ FIPS_ALLOWED_HOSTKEYS @@ -214,8 +222,8 @@ static const char *fips_methods[] = { FIPS_ALLOWED_CIPHERS, FIPS_ALLOWED_MACS, FIPS_ALLOWED_MACS, - ZLIB, - ZLIB, + ZLIB_DEFAULT, + ZLIB_DEFAULT, "", "", NULL @@ -229,8 +237,8 @@ static const char *default_methods[] = { CHACHA20 AES, "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-512", "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-512", - "none", - "none", + ZLIB_DEFAULT, + ZLIB_DEFAULT, "", "", NULL @@ -316,6 +324,10 @@ static int cmp_first_kex_algo(const char *client_str, int is_wrong = 1; + if (client_str == NULL || server_str == NULL) { + return is_wrong; + } + colon = strchr(client_str, ','); if (colon == NULL) { client_kex_len = strlen(client_str); @@ -342,6 +354,7 @@ static int cmp_first_kex_algo(const char *client_str, SSH_PACKET_CALLBACK(ssh_packet_kexinit) { int i, ok; + struct ssh_crypto_struct *crypto = session->next_crypto; int server_kex = session->server; ssh_string str = NULL; char *strings[SSH_KEX_METHODS] = {0}; @@ -355,35 +368,67 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) (void)type; (void)user; + SSH_LOG(SSH_LOG_TRACE, "KEXINIT received"); + if (session->session_state == SSH_SESSION_STATE_AUTHENTICATED) { - SSH_LOG(SSH_LOG_INFO, "Initiating key re-exchange"); + if (session->dh_handshake_state == DH_STATE_FINISHED) { + SSH_LOG(SSH_LOG_DEBUG, "Peer initiated key re-exchange"); + /* Reset the sent flag if the re-kex was initiated by the peer */ + session->flags &= ~SSH_SESSION_FLAG_KEXINIT_SENT; + } else if (session->flags & SSH_SESSION_FLAG_KEXINIT_SENT && + session->dh_handshake_state == DH_STATE_INIT_SENT) { + /* This happens only when we are sending our-guessed first kex + * packet right after our KEXINIT packet. */ + SSH_LOG(SSH_LOG_DEBUG, "Received peer kexinit answer."); + } else if (session->session_state != SSH_SESSION_STATE_INITIAL_KEX) { + ssh_set_error(session, SSH_FATAL, + "SSH_KEXINIT received in wrong state"); + goto error; + } } else if (session->session_state != SSH_SESSION_STATE_INITIAL_KEX) { - ssh_set_error(session,SSH_FATAL,"SSH_KEXINIT received in wrong state"); + ssh_set_error(session, SSH_FATAL, + "SSH_KEXINIT received in wrong state"); goto error; } if (server_kex) { - len = ssh_buffer_get_data(packet,session->next_crypto->client_kex.cookie, 16); +#ifdef WITH_SERVER + len = ssh_buffer_get_data(packet, crypto->client_kex.cookie, 16); if (len != 16) { - ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: no cookie in packet"); + ssh_set_error(session, SSH_FATAL, + "ssh_packet_kexinit: no cookie in packet"); goto error; } - ok = ssh_hashbufin_add_cookie(session, session->next_crypto->client_kex.cookie); + ok = ssh_hashbufin_add_cookie(session, crypto->client_kex.cookie); if (ok < 0) { - ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed"); + ssh_set_error(session, SSH_FATAL, + "ssh_packet_kexinit: adding cookie failed"); goto error; } + + ok = server_set_kex(session); + if (ok == SSH_ERROR) { + goto error; + } +#endif /* WITH_SERVER */ } else { - len = ssh_buffer_get_data(packet,session->next_crypto->server_kex.cookie, 16); + len = ssh_buffer_get_data(packet, crypto->server_kex.cookie, 16); if (len != 16) { - ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: no cookie in packet"); + ssh_set_error(session, SSH_FATAL, + "ssh_packet_kexinit: no cookie in packet"); goto error; } - ok = ssh_hashbufin_add_cookie(session, session->next_crypto->server_kex.cookie); + ok = ssh_hashbufin_add_cookie(session, crypto->server_kex.cookie); if (ok < 0) { - ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed"); + ssh_set_error(session, SSH_FATAL, + "ssh_packet_kexinit: adding cookie failed"); + goto error; + } + + ok = ssh_set_client_kex(session); + if (ok == SSH_ERROR) { goto error; } } @@ -396,7 +441,8 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) rc = ssh_buffer_add_ssh_string(session->in_hashbuf, str); if (rc < 0) { - ssh_set_error(session, SSH_FATAL, "Error adding string in hash buffer"); + ssh_set_error(session, SSH_FATAL, + "Error adding string in hash buffer"); goto error; } @@ -409,14 +455,16 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) str = NULL; } - /* copy the server kex info into an array of strings */ + /* copy the peer kex info into an array of strings */ if (server_kex) { +#ifdef WITH_SERVER for (i = 0; i < SSH_KEX_METHODS; i++) { - session->next_crypto->client_kex.methods[i] = strings[i]; + crypto->client_kex.methods[i] = strings[i]; } +#endif /* WITH_SERVER */ } else { /* client */ for (i = 0; i < SSH_KEX_METHODS; i++) { - session->next_crypto->server_kex.methods[i] = strings[i]; + crypto->server_kex.methods[i] = strings[i]; } } @@ -430,30 +478,70 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) * that its value is included when computing the session ID (see * 'make_sessionid'). */ - if (server_kex) { - rc = ssh_buffer_get_u8(packet, &first_kex_packet_follows); - if (rc != 1) { - goto error; - } - rc = ssh_buffer_add_u8(session->in_hashbuf, first_kex_packet_follows); - if (rc < 0) { - goto error; - } + rc = ssh_buffer_get_u8(packet, &first_kex_packet_follows); + if (rc != 1) { + goto error; + } - rc = ssh_buffer_add_u32(session->in_hashbuf, kexinit_reserved); - if (rc < 0) { - goto error; - } + rc = ssh_buffer_add_u8(session->in_hashbuf, first_kex_packet_follows); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u32(session->in_hashbuf, kexinit_reserved); + if (rc < 0) { + goto error; + } + + /* + * Remember whether 'first_kex_packet_follows' was set and the client + * guess was wrong: in this case the next SSH_MSG_KEXDH_INIT message + * must be ignored on the server side. + * Client needs to start the Key exchange over with the correct method + */ + if (first_kex_packet_follows || session->send_first_kex_follows) { + char **client_methods = crypto->client_kex.methods; + char **server_methods = crypto->server_kex.methods; + session->first_kex_follows_guess_wrong = + cmp_first_kex_algo(client_methods[SSH_KEX], + server_methods[SSH_KEX]) || + cmp_first_kex_algo(client_methods[SSH_HOSTKEYS], + server_methods[SSH_HOSTKEYS]); + SSH_LOG(SSH_LOG_DEBUG, "The initial guess was %s.", + session->first_kex_follows_guess_wrong ? "wrong" : "right"); + } + + /* + * handle the "strict KEX" feature. If supported by peer, then set up the + * flag and verify packet sequence numbers. + */ + if (server_kex) { + ok = ssh_match_group(crypto->client_kex.methods[SSH_KEX], + KEX_STRICT_CLIENT); + if (ok) { + SSH_LOG(SSH_LOG_DEBUG, "Client supports strict kex, enabling."); + session->flags |= SSH_SESSION_FLAG_KEX_STRICT; + } + } else { + /* client kex */ + ok = ssh_match_group(crypto->server_kex.methods[SSH_KEX], + KEX_STRICT_SERVER); + if (ok) { + SSH_LOG(SSH_LOG_DEBUG, "Server supports strict kex, enabling."); + session->flags |= SSH_SESSION_FLAG_KEX_STRICT; + } + } +#ifdef WITH_SERVER + if (server_kex) { /* * If client sent a ext-info-c message in the kex list, it supports * RFC 8308 extension negotiation. */ - ok = ssh_match_group(session->next_crypto->client_kex.methods[SSH_KEX], + ok = ssh_match_group(crypto->client_kex.methods[SSH_KEX], KEX_EXTENSION_CLIENT); if (ok) { - const char *hostkeys = NULL; + const char *hostkeys = NULL, *wanted_hostkeys = NULL; /* The client supports extension negotiation */ session->extensions |= SSH_EXT_NEGOTIATION; @@ -463,14 +551,14 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) * by the client and enable the respective extensions to provide * correct signature in the next packet if RSA is negotiated */ - hostkeys = session->next_crypto->client_kex.methods[SSH_HOSTKEYS]; + hostkeys = crypto->client_kex.methods[SSH_HOSTKEYS]; + wanted_hostkeys = session->opts.wanted_methods[SSH_HOSTKEYS]; ok = ssh_match_group(hostkeys, "rsa-sha2-512"); if (ok) { /* Check if rsa-sha2-512 is allowed by config */ - if (session->opts.wanted_methods[SSH_HOSTKEYS] != NULL) { - char *is_allowed = - ssh_find_matching(session->opts.wanted_methods[SSH_HOSTKEYS], - "rsa-sha2-512"); + if (wanted_hostkeys != NULL) { + char *is_allowed = ssh_find_matching(wanted_hostkeys, + "rsa-sha2-512"); if (is_allowed != NULL) { session->extensions |= SSH_EXT_SIG_RSA_SHA512; } @@ -480,10 +568,9 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) ok = ssh_match_group(hostkeys, "rsa-sha2-256"); if (ok) { /* Check if rsa-sha2-256 is allowed by config */ - if (session->opts.wanted_methods[SSH_HOSTKEYS] != NULL) { - char *is_allowed = - ssh_find_matching(session->opts.wanted_methods[SSH_HOSTKEYS], - "rsa-sha2-256"); + if (wanted_hostkeys != NULL) { + char *is_allowed = ssh_find_matching(wanted_hostkeys, + "rsa-sha2-256"); if (is_allowed != NULL) { session->extensions |= SSH_EXT_SIG_RSA_SHA256; } @@ -499,7 +586,7 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) (session->extensions & SSH_EXT_SIG_RSA_SHA512)) { session->extensions &= ~(SSH_EXT_SIG_RSA_SHA256 | SSH_EXT_SIG_RSA_SHA512); rsa_sig_ext = ssh_find_matching("rsa-sha2-512,rsa-sha2-256", - session->next_crypto->client_kex.methods[SSH_HOSTKEYS]); + hostkeys); if (rsa_sig_ext == NULL) { goto error; /* should never happen */ } else if (strcmp(rsa_sig_ext, "rsa-sha2-512") == 0) { @@ -518,24 +605,17 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) session->extensions & SSH_EXT_SIG_RSA_SHA256 ? "SHA256" : "", session->extensions & SSH_EXT_SIG_RSA_SHA512 ? " SHA512" : ""); } - - /* - * Remember whether 'first_kex_packet_follows' was set and the client - * guess was wrong: in this case the next SSH_MSG_KEXDH_INIT message - * must be ignored. - */ - if (first_kex_packet_follows) { - session->first_kex_follows_guess_wrong = - cmp_first_kex_algo(session->next_crypto->client_kex.methods[SSH_KEX], - session->next_crypto->server_kex.methods[SSH_KEX]) || - cmp_first_kex_algo(session->next_crypto->client_kex.methods[SSH_HOSTKEYS], - session->next_crypto->server_kex.methods[SSH_HOSTKEYS]); - } } +#endif /* WITH_SERVER */ /* Note, that his overwrites authenticated state in case of rekeying */ session->session_state = SSH_SESSION_STATE_KEXINIT_RECEIVED; - session->dh_handshake_state = DH_STATE_INIT; + /* if we already sent our initial key exchange packet, do not reset the + * DH state. We will know if we were right with our guess only in + * dh_handshake_state() */ + if (session->send_first_kex_follows == false) { + session->dh_handshake_state = DH_STATE_INIT; + } session->ssh_connection_callback(session); return SSH_PACKET_USED; @@ -543,7 +623,9 @@ error: SSH_STRING_FREE(str); for (i = 0; i < SSH_KEX_METHODS; i++) { if (server_kex) { +#ifdef WITH_SERVER session->next_crypto->client_kex.methods[i] = NULL; +#endif /* WITH_SERVER */ } else { /* client */ session->next_crypto->server_kex.methods[i] = NULL; } @@ -601,7 +683,7 @@ char *ssh_client_select_hostkeys(ssh_session session) /* This removes the certificate types, unsupported for now */ wanted_without_certs = ssh_find_all_matching(HOSTKEYS, wanted); if (wanted_without_certs == NULL) { - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_TRACE, "List of allowed host key algorithms is empty or contains only " "unsupported algorithms"); return NULL; @@ -654,7 +736,7 @@ char *ssh_client_select_hostkeys(ssh_session session) fips_hostkeys = ssh_keep_fips_algos(SSH_HOSTKEYS, new_hostkeys); SAFE_FREE(new_hostkeys); if (fips_hostkeys == NULL) { - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_TRACE, "None of the wanted host keys or keys in known_hosts files " "is allowed in FIPS mode."); return NULL; @@ -677,11 +759,14 @@ int ssh_set_client_kex(ssh_session session) { struct ssh_kex_struct *client = &session->next_crypto->client_kex; const char *wanted; - char *kex = NULL; - char *kex_tmp = NULL; int ok; int i; - size_t kex_len, len; + + /* Skip if already set, for example for the rekey or when we do the guessing + * it could have been already used to make some protocol decisions. */ + if (client->methods[0] != NULL) { + return SSH_OK; + } ok = ssh_get_random(client->cookie, 16, 0); if (!ok) { @@ -689,8 +774,6 @@ int ssh_set_client_kex(ssh_session session) return SSH_ERROR; } - memset(client->methods, 0, SSH_KEX_METHODS * sizeof(char **)); - /* Set the list of allowed algorithms in order of preference, if it hadn't * been set yet. */ for (i = 0; i < SSH_KEX_METHODS; i++) { @@ -726,23 +809,52 @@ int ssh_set_client_kex(ssh_session session) return SSH_OK; } - /* Here we append ext-info-c to the list of kex algorithms */ - kex = client->methods[SSH_KEX]; + ok = ssh_kex_append_extensions(session, client); + if (ok != SSH_OK){ + return ok; + } + + return SSH_OK; +} + +int ssh_kex_append_extensions(ssh_session session, struct ssh_kex_struct *pkex) +{ + char *kex = NULL; + char *kex_tmp = NULL; + size_t kex_len, len; + + /* Here we append ext-info-c and kex-strict-c-v00@openssh.com for client + * and kex-strict-s-v00@openssh.com for server to the list of kex algorithms + */ + kex = pkex->methods[SSH_KEX]; len = strlen(kex); - if (len + strlen(KEX_EXTENSION_CLIENT) + 2 < len) { + if (session->server) { + /* Comma, nul byte */ + kex_len = len + 1 + strlen(KEX_STRICT_SERVER) + 1; + } else { + /* Comma, comma, nul byte */ + kex_len = len + 1 + strlen(KEX_EXTENSION_CLIENT) + 1 + + strlen(KEX_STRICT_CLIENT) + 1; + } + if (kex_len >= MAX_PACKET_LEN) { /* Overflow */ return SSH_ERROR; } - kex_len = len + strlen(KEX_EXTENSION_CLIENT) + 2; /* comma, NULL */ kex_tmp = realloc(kex, kex_len); if (kex_tmp == NULL) { - free(kex); ssh_set_error_oom(session); return SSH_ERROR; } - snprintf(kex_tmp + len, kex_len - len, ",%s", KEX_EXTENSION_CLIENT); - client->methods[SSH_KEX] = kex_tmp; - + if (session->server){ + snprintf(kex_tmp + len, kex_len - len, ",%s", KEX_STRICT_SERVER); + } else { + snprintf(kex_tmp + len, + kex_len - len, + ",%s,%s", + KEX_EXTENSION_CLIENT, + KEX_STRICT_CLIENT); + } + pkex->methods[SSH_KEX] = kex_tmp; return SSH_OK; } @@ -760,71 +872,142 @@ static const char *ssh_find_aead_hmac(const char *cipher) return NULL; } +static enum ssh_key_exchange_e +kex_select_kex_type(const char *kex) +{ + if (strcmp(kex, "diffie-hellman-group1-sha1") == 0) { + return SSH_KEX_DH_GROUP1_SHA1; + } else if (strcmp(kex, "diffie-hellman-group14-sha1") == 0) { + return SSH_KEX_DH_GROUP14_SHA1; + } else if (strcmp(kex, "diffie-hellman-group14-sha256") == 0) { + return SSH_KEX_DH_GROUP14_SHA256; + } else if (strcmp(kex, "diffie-hellman-group16-sha512") == 0) { + return SSH_KEX_DH_GROUP16_SHA512; + } else if (strcmp(kex, "diffie-hellman-group18-sha512") == 0) { + return SSH_KEX_DH_GROUP18_SHA512; +#ifdef WITH_GEX + } else if (strcmp(kex, "diffie-hellman-group-exchange-sha1") == 0) { + return SSH_KEX_DH_GEX_SHA1; + } else if (strcmp(kex, "diffie-hellman-group-exchange-sha256") == 0) { + return SSH_KEX_DH_GEX_SHA256; +#endif /* WITH_GEX */ + } else if (strcmp(kex, "ecdh-sha2-nistp256") == 0) { + return SSH_KEX_ECDH_SHA2_NISTP256; + } else if (strcmp(kex, "ecdh-sha2-nistp384") == 0) { + return SSH_KEX_ECDH_SHA2_NISTP384; + } else if (strcmp(kex, "ecdh-sha2-nistp521") == 0) { + return SSH_KEX_ECDH_SHA2_NISTP521; + } else if (strcmp(kex, "curve25519-sha256@libssh.org") == 0) { + return SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG; + } else if (strcmp(kex, "curve25519-sha256") == 0) { + return SSH_KEX_CURVE25519_SHA256; + } + /* should not happen. We should be getting only valid names at this stage */ + return 0; +} + + +/** @internal + * @brief Reverts guessed callbacks set during the dh_handshake() + * @param session session handle + * @returns void + */ +static void revert_kex_callbacks(ssh_session session) +{ + switch (session->next_crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_KEX_DH_GROUP18_SHA512: + ssh_client_dh_remove_callbacks(session); + break; +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + ssh_client_dhgex_remove_callbacks(session); + break; +#endif /* WITH_GEX */ +#ifdef HAVE_ECDH + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_ECDH_SHA2_NISTP384: + case SSH_KEX_ECDH_SHA2_NISTP521: + ssh_client_ecdh_remove_callbacks(session); + break; +#endif +#ifdef HAVE_CURVE25519 + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + ssh_client_curve25519_remove_callbacks(session); + break; +#endif + } +} + /** @brief Select the different methods on basis of client's and * server's kex messages, and watches out if a match is possible. */ int ssh_kex_select_methods (ssh_session session) { - struct ssh_kex_struct *server = &session->next_crypto->server_kex; - struct ssh_kex_struct *client = &session->next_crypto->client_kex; + struct ssh_crypto_struct *crypto = session->next_crypto; + struct ssh_kex_struct *server = &crypto->server_kex; + struct ssh_kex_struct *client = &crypto->client_kex; char *ext_start = NULL; const char *aead_hmac = NULL; + enum ssh_key_exchange_e kex_type; int i; - /* Here we should drop the ext-info-c from the list so we avoid matching. + /* Here we should drop the extensions from the list so we avoid matching. * it. We added it to the end, so we can just truncate the string here */ - ext_start = strstr(client->methods[SSH_KEX], ","KEX_EXTENSION_CLIENT); - if (ext_start != NULL) { - ext_start[0] = '\0'; + if (session->client) { + ext_start = strstr(client->methods[SSH_KEX], "," KEX_EXTENSION_CLIENT); + if (ext_start != NULL) { + ext_start[0] = '\0'; + } + } + if (session->server) { + ext_start = strstr(server->methods[SSH_KEX], "," KEX_STRICT_SERVER); + if (ext_start != NULL) { + ext_start[0] = '\0'; + } } for (i = 0; i < SSH_KEX_METHODS; i++) { - session->next_crypto->kex_methods[i]=ssh_find_matching(server->methods[i],client->methods[i]); + crypto->kex_methods[i] = ssh_find_matching(server->methods[i], + client->methods[i]); if (i == SSH_MAC_C_S || i == SSH_MAC_S_C) { - aead_hmac = ssh_find_aead_hmac(session->next_crypto->kex_methods[i-2]); + aead_hmac = ssh_find_aead_hmac(crypto->kex_methods[i - 2]); if (aead_hmac) { - free(session->next_crypto->kex_methods[i]); - session->next_crypto->kex_methods[i] = strdup(aead_hmac); + free(crypto->kex_methods[i]); + crypto->kex_methods[i] = strdup(aead_hmac); } } - 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]); + if (crypto->kex_methods[i] == NULL && i < SSH_LANG_C_S) { + ssh_set_error(session, SSH_FATAL, + "kex error : no match for method %s: server [%s], " + "client [%s]", ssh_kex_descriptions[i], + server->methods[i], client->methods[i]); return SSH_ERROR; - } else if ((i >= SSH_LANG_C_S) && (session->next_crypto->kex_methods[i] == NULL)) { + } else if ((i >= SSH_LANG_C_S) && (crypto->kex_methods[i] == NULL)) { /* we can safely do that for languages */ - session->next_crypto->kex_methods[i] = strdup(""); + crypto->kex_methods[i] = strdup(""); } } - if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group1-sha1") == 0){ - session->next_crypto->kex_type=SSH_KEX_DH_GROUP1_SHA1; - } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group14-sha1") == 0){ - session->next_crypto->kex_type=SSH_KEX_DH_GROUP14_SHA1; - } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group14-sha256") == 0){ - session->next_crypto->kex_type=SSH_KEX_DH_GROUP14_SHA256; - } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group16-sha512") == 0){ - session->next_crypto->kex_type=SSH_KEX_DH_GROUP16_SHA512; - } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group18-sha512") == 0){ - session->next_crypto->kex_type=SSH_KEX_DH_GROUP18_SHA512; -#ifdef WITH_GEX - } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group-exchange-sha1") == 0){ - session->next_crypto->kex_type=SSH_KEX_DH_GEX_SHA1; - } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group-exchange-sha256") == 0){ - session->next_crypto->kex_type=SSH_KEX_DH_GEX_SHA256; -#endif /* WITH_GEX */ - } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp256") == 0){ - session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP256; - } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp384") == 0){ - session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP384; - } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp521") == 0){ - session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP521; - } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "curve25519-sha256@libssh.org") == 0){ - session->next_crypto->kex_type=SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG; - } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "curve25519-sha256") == 0){ - session->next_crypto->kex_type=SSH_KEX_CURVE25519_SHA256; - } - SSH_LOG(SSH_LOG_INFO, "Negotiated %s,%s,%s,%s,%s,%s,%s,%s,%s,%s", + + /* We can not set this value directly as the old value is needed to revert + * callbacks if we are client */ + kex_type = kex_select_kex_type(crypto->kex_methods[SSH_KEX]); + if (session->client && session->first_kex_follows_guess_wrong) { + SSH_LOG(SSH_LOG_DEBUG, "Our guess was wrong. Restarting the KEX"); + /* We need to remove the wrong callbacks and start kex again */ + revert_kex_callbacks(session); + session->dh_handshake_state = DH_STATE_INIT; + session->first_kex_follows_guess_wrong = false; + } + crypto->kex_type = kex_type; + + SSH_LOG(SSH_LOG_DEBUG, "Negotiated %s,%s,%s,%s,%s,%s,%s,%s,%s,%s", session->next_crypto->kex_methods[SSH_KEX], session->next_crypto->kex_methods[SSH_HOSTKEYS], session->next_crypto->kex_methods[SSH_CRYPT_C_S], @@ -841,63 +1024,116 @@ int ssh_kex_select_methods (ssh_session session) /* this function only sends the predefined set of kex methods */ -int ssh_send_kex(ssh_session session, int server_kex) +int ssh_send_kex(ssh_session session) { - 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; - } + struct ssh_kex_struct *kex = (session->server ? + &session->next_crypto->server_kex : + &session->next_crypto->client_kex); + ssh_string str = NULL; + int i; + int rc; + int first_kex_packet_follows = 0; + + /* Only client can initiate the handshake methods we implement. If we + * already received the peer mechanisms, there is no point in guessing */ + if (session->client && + session->session_state != SSH_SESSION_STATE_KEXINIT_RECEIVED && + session->send_first_kex_follows) { + first_kex_packet_follows = 1; + } + + SSH_LOG(SSH_LOG_TRACE, + "Sending KEXINIT packet, first_kex_packet_follows = %d", + first_kex_packet_follows); + + rc = ssh_buffer_pack(session->out_buffer, + "bP", + SSH2_MSG_KEXINIT, + 16, + kex->cookie); /* cookie */ + if (rc != SSH_OK) + goto error; + if (ssh_hashbufout_add_cookie(session) < 0) { + goto error; + } - ssh_list_kex(kex); + ssh_list_kex(kex); - for (i = 0; i < SSH_KEX_METHODS; i++) { - str = ssh_string_from_char(kex->methods[i]); - if (str == NULL) { - goto error; + for (i = 0; i < SSH_KEX_METHODS; i++) { + str = ssh_string_from_char(kex->methods[i]); + if (str == NULL) { + goto error; + } + + rc = ssh_buffer_add_ssh_string(session->out_hashbuf, str); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_ssh_string(session->out_buffer, str); + if (rc < 0) { + goto error; + } + SSH_STRING_FREE(str); + str = NULL; } - if (ssh_buffer_add_ssh_string(session->out_hashbuf, str) < 0) { - goto error; + rc = ssh_buffer_pack(session->out_buffer, + "bd", + first_kex_packet_follows, + 0); + if (rc != SSH_OK) { + goto error; } - if (ssh_buffer_add_ssh_string(session->out_buffer, str) < 0) { - goto error; + + /* Prepare also the first_kex_packet_follows and reserved to 0 */ + rc = ssh_buffer_add_u8(session->out_hashbuf, first_kex_packet_follows); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u32(session->out_hashbuf, 0); + if (rc < 0) { + goto error; } - SSH_STRING_FREE(str); - str = NULL; - } - rc = ssh_buffer_pack(session->out_buffer, - "bd", - 0, - 0); - if (rc != SSH_OK) { - goto error; - } + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return -1; + } - if (ssh_packet_send(session) == SSH_ERROR) { - return -1; - } + session->flags |= SSH_SESSION_FLAG_KEXINIT_SENT; + SSH_LOG(SSH_LOG_PACKET, "SSH_MSG_KEXINIT sent"); + + /* If we indicated that we are sending the guessed key exchange packet, + * do it now. The packet is simple, but we need to do some preparations */ + if (first_kex_packet_follows == 1) { + char *list = kex->methods[SSH_KEX]; + char *colon = strchr(list, ','); + size_t kex_name_len = colon ? (size_t)(colon - list) : strlen(list); + char *kex_name = calloc(kex_name_len + 1, 1); + if (kex_name == NULL) { + ssh_set_error_oom(session); + goto error; + } + snprintf(kex_name, kex_name_len + 1, "%.*s", (int)kex_name_len, list); + SSH_LOG(SSH_LOG_TRACE, "Sending the first kex packet for %s", kex_name); + + session->next_crypto->kex_type = kex_select_kex_type(kex_name); + free(kex_name); + + /* run the first step of the DH handshake */ + session->dh_handshake_state = DH_STATE_INIT; + if (dh_handshake(session) == SSH_ERROR) { + goto error; + } + } + return 0; - SSH_LOG(SSH_LOG_PACKET, "SSH_MSG_KEXINIT sent"); - return 0; error: - ssh_buffer_reinit(session->out_buffer); - ssh_buffer_reinit(session->out_hashbuf); - SSH_STRING_FREE(str); + ssh_buffer_reinit(session->out_buffer); + ssh_buffer_reinit(session->out_hashbuf); + SSH_STRING_FREE(str); - return -1; + return -1; } /* @@ -940,7 +1176,7 @@ int ssh_send_rekex(ssh_session session) } session->dh_handshake_state = DH_STATE_INIT; - rc = ssh_send_kex(session, session->server); + rc = ssh_send_kex(session); if (rc < 0) { SSH_LOG(SSH_LOG_PACKET, "Failed to send kex"); return rc; @@ -965,13 +1201,13 @@ char *ssh_keep_known_algos(enum ssh_kex_types_e algo, const char *list) /** * @internal * - * @brief Return a new allocated string containing only the FIPS allowed + * @brief Return a newly allocated string containing only the FIPS allowed * algorithms from the list. * * @param[in] algo The type of the methods to filter * @param[in] list The list to be filtered * - * @return A new allocated list containing only the FIPS allowed algorithms from + * @return A newly allocated list containing only the FIPS allowed algorithms from * the list; NULL in case of error. */ char *ssh_keep_fips_algos(enum ssh_kex_types_e algo, const char *list) @@ -983,6 +1219,111 @@ char *ssh_keep_fips_algos(enum ssh_kex_types_e algo, const char *list) return ssh_find_all_matching(fips_methods[algo], list); } +/** + * @internal + * + * @brief Return a newly allocated string containing the default + * algorithms plus the algorithms specified in list. If the system + * runs in fips mode, this will add only fips approved algorithms. + * Empty list will cause error. + * + * @param[in] algo The type of the methods to filter + * @param[in] list The list to be appended + * + * @return A newly allocated list containing the default algorithms and the + * algorithms in list at the end; NULL in case of error. + */ +char *ssh_add_to_default_algos(enum ssh_kex_types_e algo, const char *list) +{ + char *tmp = NULL, *ret = NULL; + + if (algo > SSH_LANG_S_C || list == NULL || list[0] == '\0') { + return NULL; + } + + if (ssh_fips_mode()) { + tmp = ssh_append_without_duplicates(fips_methods[algo], list); + ret = ssh_find_all_matching(fips_methods[algo], tmp); + } else { + tmp = ssh_append_without_duplicates(default_methods[algo], list); + ret = ssh_find_all_matching(supported_methods[algo], tmp); + } + + free(tmp); + return ret; +} + +/** + * @internal + * + * @brief Return a newly allocated string containing the default + * algorithms excluding the algorithms specified in list. If the system + * runs in fips mode, this will remove from the fips_methods list. + * + * @param[in] algo The type of the methods to filter + * @param[in] list The list to be exclude + * + * @return A newly allocated list containing the default algorithms without the + * algorithms in list; NULL in case of error. + */ +char *ssh_remove_from_default_algos(enum ssh_kex_types_e algo, const char *list) +{ + if (algo > SSH_LANG_S_C) { + return NULL; + } + + if (list == NULL || list[0] == '\0') { + if (ssh_fips_mode()) { + return strdup(fips_methods[algo]); + } else { + return strdup(default_methods[algo]); + } + } + + if (ssh_fips_mode()) { + return ssh_remove_all_matching(fips_methods[algo], list); + } else { + return ssh_remove_all_matching(default_methods[algo], list); + } +} + +/** + * @internal + * + * @brief Return a newly allocated string containing the default + * algorithms with prioritized algorithms specified in list. If the + * algorithms are present in the default list they get prioritized, if not + * they are added to the front of the default list. If the system + * runs in fips mode, this will work with the fips_methods list. + * Empty list will cause error. + * + * @param[in] algo The type of the methods to filter + * @param[in] list The list to be pushed to priority + * + * @return A newly allocated list containing the default algorithms prioritized + * with the algorithms in list at the beginning of the list; NULL in case + * of error. + */ +char *ssh_prefix_default_algos(enum ssh_kex_types_e algo, const char *list) +{ + char *ret = NULL, *tmp = NULL; + + if (algo > SSH_LANG_S_C || list == NULL || list[0] == '\0') { + return NULL; + } + + if (ssh_fips_mode()) { + tmp = ssh_prefix_without_duplicates(fips_methods[algo], list); + ret = ssh_find_all_matching(fips_methods[algo], tmp); + } else { + tmp = ssh_prefix_without_duplicates(default_methods[algo], list); + ret = ssh_find_all_matching(supported_methods[algo], tmp); + } + + free(tmp); + return ret; +} + int ssh_make_sessionid(ssh_session session) { ssh_string num = NULL; @@ -990,10 +1331,18 @@ int ssh_make_sessionid(ssh_session session) ssh_buffer client_hash = NULL; ssh_buffer buf = NULL; ssh_string server_pubkey_blob = NULL; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum client_pubkey, server_pubkey; +#else + bignum client_pubkey = NULL, server_pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ #ifdef WITH_GEX +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum modulus, generator; -#endif +#else + bignum modulus = NULL, generator = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ +#endif /* WITH_GEX */ int rc = SSH_ERROR; buf = ssh_buffer_new(); @@ -1017,33 +1366,6 @@ int ssh_make_sessionid(ssh_session session) client_hash = session->in_hashbuf; } - /* - * Handle the two final fields for the KEXINIT message (RFC 4253 7.1): - * - * boolean first_kex_packet_follows - * uint32 0 (reserved for future extension) - */ - rc = ssh_buffer_add_u8(server_hash, 0); - if (rc < 0) { - goto error; - } - rc = ssh_buffer_add_u32(server_hash, 0); - if (rc < 0) { - goto error; - } - - /* These fields are handled for the server case in ssh_packet_kexinit. */ - if (session->client) { - rc = ssh_buffer_add_u8(client_hash, 0); - if (rc < 0) { - goto error; - } - rc = ssh_buffer_add_u32(client_hash, 0); - if (rc < 0) { - goto error; - } - } - rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); if (rc != SSH_OK) { goto error; @@ -1086,6 +1408,10 @@ int ssh_make_sessionid(ssh_session session) if (rc != SSH_OK) { goto error; } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(client_pubkey); + bignum_safe_free(server_pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ break; #ifdef WITH_GEX case SSH_KEX_DH_GEX_SHA1: @@ -1117,6 +1443,10 @@ int ssh_make_sessionid(ssh_session session) if (rc != SSH_OK) { goto error; } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(modulus); + bignum_safe_free(generator); +#endif /* OPENSSL_VERSION_NUMBER */ break; #endif /* WITH_GEX */ #ifdef HAVE_ECDH @@ -1125,7 +1455,7 @@ int ssh_make_sessionid(ssh_session session) case SSH_KEX_ECDH_SHA2_NISTP521: if (session->next_crypto->ecdh_client_pubkey == NULL || session->next_crypto->ecdh_server_pubkey == NULL) { - SSH_LOG(SSH_LOG_WARNING, "ECDH parameted missing"); + SSH_LOG(SSH_LOG_TRACE, "ECDH parameter missing"); goto error; } rc = ssh_buffer_pack(buf, @@ -1136,7 +1466,7 @@ int ssh_make_sessionid(ssh_session session) goto error; } break; -#endif +#endif /* HAVE_ECDH */ #ifdef HAVE_CURVE25519 case SSH_KEX_CURVE25519_SHA256: case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: @@ -1151,7 +1481,7 @@ int ssh_make_sessionid(ssh_session session) goto error; } break; -#endif +#endif /* HAVE_CURVE25519 */ } rc = ssh_buffer_pack(buf, "B", session->next_crypto->shared_secret); if (rc != SSH_OK) { @@ -1233,12 +1563,14 @@ int ssh_make_sessionid(ssh_session session) } memcpy(session->next_crypto->session_id, session->next_crypto->secret_hash, session->next_crypto->digest_len); + /* Initial length is the same as secret hash */ + session->next_crypto->session_id_len = session->next_crypto->digest_len; } #ifdef DEBUG_CRYPTO - printf("Session hash: \n"); + SSH_LOG(SSH_LOG_DEBUG, "Session hash: \n"); ssh_log_hexdump("secret hash", session->next_crypto->secret_hash, session->next_crypto->digest_len); - ssh_log_hexdump("session id", session->next_crypto->session_id, session->next_crypto->digest_len); -#endif + ssh_log_hexdump("session id", session->next_crypto->session_id, session->next_crypto->session_id_len); +#endif /* DEBUG_CRYPTO */ rc = SSH_OK; error: @@ -1250,6 +1582,10 @@ error: session->out_hashbuf = NULL; SSH_STRING_FREE(num); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(client_pubkey); + bignum_safe_free(server_pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ return rc; } @@ -1434,7 +1770,7 @@ int ssh_generate_session_keys(ssh_session session) intkey_cli_to_srv_len); ssh_log_hexdump("Server to Client Integrity Key", intkey_srv_to_cli, intkey_srv_to_cli_len); -#endif +#endif /* DEBUG_CRYPTO */ rc = 0; error: diff --git a/src/known_hosts.c b/src/known_hosts.c index 3feab159..a1d9a432 100644 --- a/src/known_hosts.c +++ b/src/known_hosts.c @@ -65,12 +65,12 @@ * @param[out] file A pointer to the known host file. Could be pointing to * NULL at start. * - * @param[in] filename The file name of the known host file. + * @param[in] filename The filename of the known host file. * * @param[out] found_type A pointer to a string to be set with the found key * type. * - * @returns The found_type type of key (ie "dsa","ssh-rsa"). Don't + * @returns The found_type type of key (ie "ssh-rsa"). Don't * free that value. NULL if no match was found or the file * was not found. */ @@ -152,7 +152,7 @@ static int check_public_key(ssh_session session, char **tokens) { char *pubkey_64; int rc; - /* ssh-dss or ssh-rsa */ + /* ssh-rsa, ssh-ed25519, .. */ pubkey_64 = tokens[2]; pubkey_buffer = base64_to_bin(pubkey_64); @@ -210,8 +210,8 @@ static int match_hashed_host(const char *host, const char *sourcehash) HMACCTX mac; char *source; char *b64hash; - int match; - unsigned int size; + int match, rc; + size_t size; if (strncmp(sourcehash, "|1|", 3) != 0) { return 0; @@ -256,8 +256,20 @@ static int match_hashed_host(const char *host, const char *sourcehash) return 0; } size = sizeof(buffer); - hmac_update(mac, host, strlen(host)); - hmac_final(mac, buffer, &size); + rc = hmac_update(mac, host, strlen(host)); + if (rc != 1) { + ssh_buffer_free(salt); + ssh_buffer_free(hash); + + return 0; + } + rc = hmac_final(mac, buffer, &size); + if (rc != 1) { + ssh_buffer_free(salt); + ssh_buffer_free(hash); + + return 0; + } if (size == ssh_buffer_get_len(hash) && memcmp(buffer, ssh_buffer_get(hash), size) == 0) { @@ -516,10 +528,12 @@ int ssh_write_knownhost(ssh_session session) errno = 0; file = fopen(session->opts.knownhosts, "a"); if (file == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; if (errno == ENOENT) { dir = ssh_dirname(session->opts.knownhosts); if (dir == NULL) { - ssh_set_error(session, SSH_FATAL, "%s", strerror(errno)); + ssh_set_error(session, SSH_FATAL, + "%s", ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } @@ -527,7 +541,7 @@ int ssh_write_knownhost(ssh_session session) if (rc < 0) { ssh_set_error(session, SSH_FATAL, "Cannot create %s directory: %s", - dir, strerror(errno)); + dir, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); SAFE_FREE(dir); return SSH_ERROR; } @@ -539,13 +553,15 @@ int ssh_write_knownhost(ssh_session session) ssh_set_error(session, SSH_FATAL, "Couldn't open known_hosts file %s" " for appending: %s", - session->opts.knownhosts, strerror(errno)); + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } } else { ssh_set_error(session, SSH_FATAL, "Couldn't open known_hosts file %s for appending: %s", - session->opts.knownhosts, strerror(errno)); + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } } diff --git a/src/knownhosts.c b/src/knownhosts.c index ad36363e..c073b266 100644 --- a/src/knownhosts.c +++ b/src/knownhosts.c @@ -58,8 +58,9 @@ static int hash_hostname(const char *name, unsigned char *salt, unsigned int salt_size, unsigned char **hash, - unsigned int *hash_size) + size_t *hash_size) { + int rc; HMACCTX mac_ctx; mac_ctx = hmac_init(salt, salt_size, SSH_HMAC_SHA1); @@ -67,8 +68,13 @@ static int hash_hostname(const char *name, return SSH_ERROR; } - hmac_update(mac_ctx, name, strlen(name)); - hmac_final(mac_ctx, *hash, hash_size); + rc = hmac_update(mac_ctx, name, strlen(name)); + if (rc != 1) + return SSH_ERROR; + + rc = hmac_final(mac_ctx, *hash, hash_size); + if (rc != 1) + return SSH_ERROR; return SSH_OK; } @@ -81,7 +87,7 @@ static int match_hashed_hostname(const char *host, const char *hashed_host) ssh_buffer hash = NULL; unsigned char hashed_buf[256] = {0}; unsigned char *hashed_buf_ptr = hashed_buf; - unsigned int hashed_buf_size = sizeof(hashed_buf); + size_t hashed_buf_size = sizeof(hashed_buf); int cmp; int rc; int match = 0; @@ -228,8 +234,9 @@ static int ssh_known_hosts_read_entries(const char *match, fp = fopen(filename, "r"); if (fp == NULL) { - SSH_LOG(SSH_LOG_WARN, "Failed to open the known_hosts file '%s': %s", - filename, strerror(errno)); + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + SSH_LOG(SSH_LOG_TRACE, "Failed to open the known_hosts file '%s': %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); /* The missing file is not an error here */ return SSH_OK; } @@ -473,24 +480,33 @@ static const char *ssh_known_host_sigs_from_hostkey_type(enum ssh_keytypes_e typ return "rsa-sha2-512,rsa-sha2-256,ssh-rsa"; case SSH_KEYTYPE_ED25519: return "ssh-ed25519"; -#ifdef HAVE_DSA - case SSH_KEYTYPE_DSS: - return "ssh-dss"; -#endif -#ifdef HAVE_ECDH + case SSH_KEYTYPE_SK_ED25519: + return "sk-ssh-ed25519@openssh.com"; +#ifdef HAVE_ECC case SSH_KEYTYPE_ECDSA_P256: return "ecdsa-sha2-nistp256"; case SSH_KEYTYPE_ECDSA_P384: return "ecdsa-sha2-nistp384"; case SSH_KEYTYPE_ECDSA_P521: return "ecdsa-sha2-nistp521"; + case SSH_KEYTYPE_SK_ECDSA: + return "sk-ecdsa-sha2-nistp256@openssh.com"; +#else + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + SSH_LOG(SSH_LOG_WARN, "ECDSA keys are not supported by this build"); + break; #endif case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_WARN, "The given type %d is not a base private key type " - "or is unsupported", type); - return NULL; + SSH_LOG(SSH_LOG_TRACE, + "The given type %d is not a base private key type " + "or is unsupported", + type); } + + return NULL; } /** @@ -572,6 +588,8 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session) entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); algo = ssh_known_host_sigs_from_hostkey_type(entry->publickey->type); if (algo == NULL) { + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); continue; } @@ -600,7 +618,7 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session) /** * @brief Parse a line from a known_hosts entry into a structure * - * This parses an known_hosts entry into a structure with the key in a libssh + * This parses a known_hosts entry into a structure with the key in a libssh * consumeable form. You can use the PKI key function to further work with it. * * @param[in] hostname The hostname to match the line to @@ -608,7 +626,7 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session) * @param[in] line The line to compare and parse if we have a hostname * match. * - * @param[in] entry A pointer to store the the allocated known_hosts + * @param[in] entry A pointer to store the allocated known_hosts * entry structure. The user needs to free the memory * using SSH_KNOWNHOSTS_ENTRY_FREE(). * @@ -621,6 +639,7 @@ int ssh_known_hosts_parse_line(const char *hostname, struct ssh_knownhosts_entry *e = NULL; char *known_host = NULL; char *p; + char *save_tok = NULL; enum ssh_keytypes_e key_type; int match = 0; int rc = SSH_OK; @@ -631,7 +650,7 @@ int ssh_known_hosts_parse_line(const char *hostname, } /* match pattern for hostname or hashed hostname */ - p = strtok(known_host, " "); + p = strtok_r(known_host, " ", &save_tok); if (p == NULL ) { free(known_host); return SSH_ERROR; @@ -652,14 +671,16 @@ int ssh_known_hosts_parse_line(const char *hostname, match = match_hashed_hostname(hostname, p); } - for (q = strtok(p, ","); + save_tok = NULL; + + for (q = strtok_r(p, ",", &save_tok); q != NULL; - q = strtok(NULL, ",")) { + q = strtok_r(NULL, ",", &save_tok)) { int cmp; if (q[0] == '[' && hostname[0] != '[') { /* Corner case: We have standard port so we do not have - * hostname in square braces. But the patern is enclosed + * hostname in square braces. But the pattern is enclosed * in braces with, possibly standard or wildcard, port. * We need to test against [host]:port pair here. */ @@ -702,7 +723,9 @@ int ssh_known_hosts_parse_line(const char *hostname, goto out; } - p = strtok(known_host, " "); + save_tok = NULL; + + p = strtok_r(known_host, " ", &save_tok); if (p == NULL ) { rc = SSH_ERROR; goto out; @@ -715,7 +738,7 @@ int ssh_known_hosts_parse_line(const char *hostname, } /* pubkey type */ - p = strtok(NULL, " "); + p = strtok_r(NULL, " ", &save_tok); if (p == NULL) { rc = SSH_ERROR; goto out; @@ -723,13 +746,13 @@ int ssh_known_hosts_parse_line(const char *hostname, key_type = ssh_key_type_from_name(p); if (key_type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "key type '%s' unknown!", p); + SSH_LOG(SSH_LOG_TRACE, "key type '%s' unknown!", p); rc = SSH_ERROR; goto out; } /* public key */ - p = strtok(NULL, " "); + p = strtok_r(NULL, " ", &save_tok); if (p == NULL) { rc = SSH_ERROR; goto out; @@ -739,7 +762,7 @@ int ssh_known_hosts_parse_line(const char *hostname, key_type, &e->publickey); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Failed to parse %s key for entry: %s!", ssh_key_type_to_char(key_type), e->unparsed); @@ -747,7 +770,7 @@ int ssh_known_hosts_parse_line(const char *hostname, } /* comment */ - p = strtok(NULL, " "); + p = strtok_r(NULL, " ", &save_tok); if (p != NULL) { p = strstr(line, p); if (p != NULL) { @@ -770,12 +793,12 @@ out: } /** - * @brief Check if the set hostname and port matches an entry in known_hosts. + * @brief Check if the set hostname and port match an entry in known_hosts. * - * This check if the set hostname and port has an entry in the known_hosts file. + * This check if the set hostname and port have an entry in the known_hosts file. * You need to set at least the hostname using ssh_options_set(). * - * @param[in] session The session with with the values set to check. + * @param[in] session The session with the values set to check. * * @return A ssh_known_hosts_e return value. */ @@ -810,7 +833,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) if (session->opts.knownhosts != NULL) { known_hosts_found = ssh_file_readaccess_ok(session->opts.knownhosts); if (!known_hosts_found) { - SSH_LOG(SSH_LOG_WARN, "Cannot access file %s", + SSH_LOG(SSH_LOG_TRACE, "Cannot access file %s", session->opts.knownhosts); } } @@ -819,7 +842,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) global_known_hosts_found = ssh_file_readaccess_ok(session->opts.global_knownhosts); if (!global_known_hosts_found) { - SSH_LOG(SSH_LOG_WARN, "Cannot access file %s", + SSH_LOG(SSH_LOG_TRACE, "Cannot access file %s", session->opts.global_knownhosts); } } @@ -888,11 +911,11 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) * * @param[in] session The session with information to export. * - * @param[in] pentry_string A pointer to a string to store the alloocated + * @param[in] pentry_string A pointer to a string to store the allocated * line of the entry. The user must free it using * ssh_string_free_char(). * - * @return SSH_OK on succcess, SSH_ERROR otherwise. + * @return SSH_OK on success, SSH_ERROR otherwise. */ int ssh_session_export_known_hosts_entry(ssh_session session, char **pentry_string) @@ -957,7 +980,7 @@ int ssh_session_export_known_hosts_entry(ssh_session session, } /** - * @brief Add the current connected server to the user known_hosts file. + * @brief Adds the currently connected server to the user known_hosts file. * * This adds the currently connected server to the known_hosts file by * appending a new line at the end. The global known_hosts file is considered @@ -975,6 +998,7 @@ int ssh_session_update_known_hosts(ssh_session session) size_t nwritten; size_t len; int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; if (session->opts.knownhosts == NULL) { rc = ssh_options_apply(session); @@ -990,7 +1014,8 @@ int ssh_session_update_known_hosts(ssh_session session) if (errno == ENOENT) { dir = ssh_dirname(session->opts.knownhosts); if (dir == NULL) { - ssh_set_error(session, SSH_FATAL, "%s", strerror(errno)); + ssh_set_error(session, SSH_FATAL, "%s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } @@ -998,7 +1023,8 @@ int ssh_session_update_known_hosts(ssh_session session) if (rc < 0) { ssh_set_error(session, SSH_FATAL, "Cannot create %s directory: %s", - dir, strerror(errno)); + dir, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); SAFE_FREE(dir); return SSH_ERROR; } @@ -1010,7 +1036,8 @@ int ssh_session_update_known_hosts(ssh_session session) ssh_set_error(session, SSH_FATAL, "Couldn't open known_hosts file %s" " for appending: %s", - session->opts.knownhosts, strerror(errno)); + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } } else { @@ -1033,7 +1060,8 @@ int ssh_session_update_known_hosts(ssh_session session) if (nwritten != len || ferror(fp)) { ssh_set_error(session, SSH_FATAL, "Couldn't append to known_hosts file %s: %s", - session->opts.knownhosts, strerror(errno)); + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); fclose(fp); return SSH_ERROR; } @@ -1108,7 +1136,7 @@ ssh_known_hosts_check_server_key(const char *hosts_entry, } /** - * @brief Get the known_hosts entry for the current connected session. + * @brief Get the known_hosts entry for the currently connected session. * * @param[in] session The session to validate. * @@ -1127,7 +1155,7 @@ ssh_known_hosts_check_server_key(const char *hosts_entry, * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The * host is thus unknown. File will be * created if host key is accepted.\n - * SSH_KNOWN_HOSTS_ERROR: There had been an eror checking the host. + * SSH_KNOWN_HOSTS_ERROR: There had been an error checking the host. * * @see ssh_knownhosts_entry_free() */ @@ -1196,7 +1224,7 @@ ssh_session_get_known_hosts_entry(ssh_session session, * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The * host is thus unknown. File will be * created if host key is accepted.\n - * SSH_KNOWN_HOSTS_ERROR: There had been an eror checking the host. + * SSH_KNOWN_HOSTS_ERROR: There had been an error checking the host. * * @see ssh_knownhosts_entry_free() */ diff --git a/src/legacy.c b/src/legacy.c index 8ae3d920..72ff7f36 100644 --- a/src/legacy.c +++ b/src/legacy.c @@ -20,7 +20,7 @@ */ /** functions in that file are wrappers to the newly named functions. All - * of them are depreciated, but these wrapper will avoid breaking backward + * of them are depreciated, but these wrappers will avoid breaking backward * compatibility */ @@ -83,12 +83,18 @@ int ssh_userauth_pubkey(ssh_session session, key->type = privatekey->type; key->type_c = ssh_key_type_to_char(key->type); key->flags = SSH_KEY_FLAG_PRIVATE|SSH_KEY_FLAG_PUBLIC; - key->dsa = privatekey->dsa_priv; +#ifndef HAVE_LIBCRYPTO key->rsa = privatekey->rsa_priv; +#else + key->key = privatekey->key_priv; +#endif /* HAVE_LIBCRYPTO */ rc = ssh_userauth_publickey(session, username, key); - key->dsa = NULL; +#ifndef HAVE_LIBCRYPTO key->rsa = NULL; +#else + key->key = NULL; +#endif /* HAVE_LIBCRYPTO */ ssh_key_free(key); return rc; @@ -164,7 +170,7 @@ int channel_change_pty_size(ssh_channel channel,int cols,int rows){ } ssh_channel channel_forward_accept(ssh_session session, int timeout_ms){ - return ssh_channel_accept_forward(session, timeout_ms, NULL); + return ssh_channel_open_forward_port(session, timeout_ms, NULL, NULL, NULL); } int channel_close(ssh_channel channel){ @@ -350,22 +356,15 @@ void publickey_free(ssh_public_key key) { } switch(key->type) { - case SSH_KEYTYPE_DSS: -#ifdef HAVE_LIBGCRYPT - gcry_sexp_release(key->dsa_pub); -#elif defined HAVE_LIBCRYPTO - DSA_free(key->dsa_pub); -#endif - break; case SSH_KEYTYPE_RSA: #ifdef HAVE_LIBGCRYPT gcry_sexp_release(key->rsa_pub); #elif defined HAVE_LIBCRYPTO - RSA_free(key->rsa_pub); + EVP_PKEY_free(key->key_pub); #elif defined HAVE_LIBMBEDCRYPTO mbedtls_pk_free(key->rsa_pub); SAFE_FREE(key->rsa_pub); -#endif +#endif /* HAVE_LIBGCRYPT */ break; default: break; @@ -387,12 +386,18 @@ ssh_public_key publickey_from_privatekey(ssh_private_key prv) { privkey->type = prv->type; privkey->type_c = ssh_key_type_to_char(privkey->type); privkey->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; - privkey->dsa = prv->dsa_priv; +#ifndef HAVE_LIBCRYPTO privkey->rsa = prv->rsa_priv; +#else + privkey->key = prv->key_priv; +#endif /* HAVE_LIBCRYPTO */ rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); - privkey->dsa = NULL; +#ifndef HAVE_LIBCRYPTO privkey->rsa = NULL; +#else + privkey->key = NULL; +#endif /* HAVE_LIBCRYPTO */ ssh_key_free(privkey); if (rc < 0) { return NULL; @@ -438,11 +443,15 @@ ssh_private_key privatekey_from_file(ssh_session session, } privkey->type = key->type; - privkey->dsa_priv = key->dsa; +#ifndef HAVE_LIBCRYPTO privkey->rsa_priv = key->rsa; - key->dsa = NULL; key->rsa = NULL; +#else + privkey->key_priv = key->key; + + key->key = NULL; +#endif /* HAVE_LIBCRYPTO */ ssh_key_free(key); @@ -461,15 +470,13 @@ void privatekey_free(ssh_private_key prv) { } #ifdef HAVE_LIBGCRYPT - gcry_sexp_release(prv->dsa_priv); gcry_sexp_release(prv->rsa_priv); #elif defined HAVE_LIBCRYPTO - DSA_free(prv->dsa_priv); - RSA_free(prv->rsa_priv); + EVP_PKEY_free(prv->key_priv); #elif defined HAVE_LIBMBEDCRYPTO mbedtls_pk_free(prv->rsa_priv); SAFE_FREE(prv->rsa_priv); -#endif +#endif /* HAVE_LIBGCRYPT */ memset(prv, 0, sizeof(struct ssh_private_key_struct)); SAFE_FREE(prv); } @@ -530,10 +537,13 @@ ssh_public_key publickey_from_string(ssh_session session, ssh_string pubkey_s) { pubkey->type = key->type; pubkey->type_c = key->type_c; - pubkey->dsa_pub = key->dsa; - key->dsa = NULL; +#ifndef HAVE_LIBCRYPTO pubkey->rsa_pub = key->rsa; key->rsa = NULL; +#else + pubkey->key_pub = key->key; + key->key = NULL; +#endif /* HAVE_LIBCRYPTO */ ssh_key_free(key); @@ -557,16 +567,22 @@ ssh_string publickey_to_string(ssh_public_key pubkey) { key->type = pubkey->type; key->type_c = pubkey->type_c; - key->dsa = pubkey->dsa_pub; +#ifndef HAVE_LIBCRYPTO key->rsa = pubkey->rsa_pub; +#else + key->key = pubkey->key_pub; +#endif /* HAVE_LIBCRYPTO */ rc = ssh_pki_export_pubkey_blob(key, &key_blob); if (rc < 0) { key_blob = NULL; } - key->dsa = NULL; +#ifndef HAVE_LIBCRYPTO key->rsa = NULL; +#else + key->key = NULL; +#endif /* HAVE_LIBCRYPTO */ ssh_key_free(key); return key_blob; @@ -622,8 +638,12 @@ int ssh_publickey_to_file(ssh_session session, fp = fopen(file, "w+"); if (fp == NULL) { - ssh_set_error(session, SSH_REQUEST_DENIED, - "Error opening %s: %s", file, strerror(errno)); + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Error opening %s: %s", + file, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } @@ -735,7 +755,7 @@ int ssh_accept(ssh_session session) { } int channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) { - return ssh_channel_write(channel, data, len); + return ssh_channel_write_stderr(channel, data, len); } /** @deprecated diff --git a/src/libcrypto-compat.c b/src/libcrypto-compat.c deleted file mode 100644 index 33b8dffd..00000000 --- a/src/libcrypto-compat.c +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright 2016 The OpenSSL Project Authors. All Rights Reserved. - * - * Licensed under the OpenSSL license (the "License"). You may not use - * this file except in compliance with the License. You can obtain a copy - * in the file LICENSE in the source distribution or at - * https://www.openssl.org/source/license.html - */ - -#include "config.h" - -#include <string.h> -#include "libcrypto-compat.h" - -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) -{ - EVP_MD_CTX *ctx = OPENSSL_malloc(sizeof(EVP_MD_CTX)); - if (ctx != NULL) { - EVP_MD_CTX_init(ctx); - } - return ctx; -} - -void EVP_MD_CTX_free(EVP_MD_CTX *ctx) -{ - EVP_MD_CTX_cleanup(ctx); - OPENSSL_free(ctx); -} - -void DH_get0_pqg(const DH *dh, - const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) -{ - if (p) { - *p = dh->p; - } - if (q) { - *q = NULL; - } - if (g) { - *g = dh->g; - } -} - -int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) -{ - if (p) { - if (dh->p) { - BN_free(dh->p); - } - dh->p = p; - } - if (g) { - if (dh->g) { - BN_free(dh->g); - } - dh->g = g; - } - return 1; -} - -void DH_get0_key(const DH *dh, - const BIGNUM **pub_key, const BIGNUM **priv_key) -{ - if (pub_key) { - *pub_key = dh->pub_key; - } - if (priv_key) { - *priv_key = dh->priv_key; - } -} - -int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key) -{ - if (pub_key) { - if (dh->pub_key) { - BN_free(dh->pub_key); - } - dh->pub_key = pub_key; - } - if (priv_key) { - if (dh->priv_key) { - BN_free(dh->priv_key); - } - dh->priv_key = priv_key; - } - return 1; -} - -const char *OpenSSL_version(int type) -{ - return SSLeay_version(type); -} -unsigned long OpenSSL_version_num(void) -{ - return SSLeay(); -} diff --git a/src/libcrypto-compat.h b/src/libcrypto-compat.h index 437b0534..0f2dc184 100644 --- a/src/libcrypto-compat.h +++ b/src/libcrypto-compat.h @@ -2,47 +2,13 @@ #define LIBCRYPTO_COMPAT_H #include <openssl/opensslv.h> -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#include <openssl/rsa.h> -#include <openssl/dsa.h> -#include <openssl/ecdsa.h> -#include <openssl/dh.h> -#include <openssl/evp.h> -#include <openssl/hmac.h> -#include <openssl/bn.h> - -int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d); -int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q); -int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp); -void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d); -void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q); -void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp); - -void DSA_get0_pqg(const DSA *d, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g); -int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g); -void DSA_get0_key(const DSA *d, const BIGNUM **pub_key, const BIGNUM **priv_key); -int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key); - -void DSA_SIG_get0(const DSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps); -int DSA_SIG_set0(DSA_SIG *sig, BIGNUM *r, BIGNUM *s); - -void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps); -int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s); - -EVP_MD_CTX *EVP_MD_CTX_new(void); -void EVP_MD_CTX_free(EVP_MD_CTX *ctx); - -void DH_get0_pqg(const DH *dh, - const BIGNUM **p, const BIGNUM **q, const BIGNUM **g); -int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g); -void DH_get0_key(const DH *dh, - const BIGNUM **pub_key, const BIGNUM **priv_key); -int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key); - -const char *OpenSSL_version(int type); -unsigned long OpenSSL_version_num(void); +#define NISTP256 "P-256" +#define NISTP384 "P-384" +#define NISTP521 "P-521" +#if OPENSSL_VERSION_NUMBER < 0x30000000L +#define EVP_PKEY_eq EVP_PKEY_cmp #endif /* OPENSSL_VERSION_NUMBER */ #endif /* LIBCRYPTO_COMPAT_H */ diff --git a/src/libcrypto.c b/src/libcrypto.c index c14eeeea..f45ffa96 100644 --- a/src/libcrypto.c +++ b/src/libcrypto.c @@ -26,26 +26,31 @@ #include <string.h> #ifdef HAVE_SYS_TIME_H #include <sys/time.h> -#endif +#endif /* HAVE_SYS_TIME_H */ #include "libssh/priv.h" #include "libssh/session.h" #include "libssh/crypto.h" #include "libssh/wrapper.h" #include "libssh/libcrypto.h" -#if defined(HAVE_OPENSSL_EVP_CHACHA20) && defined(HAVE_OPENSSL_EVP_POLY1305) +#include "libssh/pki.h" +#ifdef HAVE_OPENSSL_EVP_CHACHA20 #include "libssh/bytearray.h" #include "libssh/chacha20-poly1305-common.h" #endif #ifdef HAVE_LIBCRYPTO +#include <openssl/opensslv.h> #include <openssl/sha.h> #include <openssl/md5.h> -#include <openssl/dsa.h> +#if OPENSSL_VERSION_NUMBER < 0x30000000L #include <openssl/rsa.h> #include <openssl/hmac.h> -#include <openssl/opensslv.h> +#else +#include <openssl/param_build.h> +#include <openssl/core_names.h> +#endif /* OPENSSL_VERSION_NUMBER */ #include <openssl/rand.h> #include <openssl/engine.h> @@ -54,11 +59,11 @@ #ifdef HAVE_OPENSSL_AES_H #define HAS_AES #include <openssl/aes.h> -#endif +#endif /* HAVE_OPENSSL_AES_H */ #ifdef HAVE_OPENSSL_DES_H #define HAS_DES #include <openssl/des.h> -#endif +#endif /* HAVE_OPENSSL_DES_H */ #if (defined(HAVE_VALGRIND_VALGRIND_H) && defined(HAVE_OPENSSL_IA32CAP_LOC)) #include <valgrind/valgrind.h> @@ -67,14 +72,19 @@ #include "libssh/crypto.h" -#ifdef HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID +#ifdef HAVE_OPENSSL_EVP_KDF_CTX #include <openssl/kdf.h> -#endif +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include <openssl/param_build.h> +#include <openssl/core_names.h> +#endif /* OPENSSL_VERSION_NUMBER */ +#endif /* HAVE_OPENSSL_EVP_KDF_CTX */ #include "libssh/crypto.h" static int libcrypto_initialized = 0; + void ssh_reseed(void){ #ifndef _WIN32 struct timeval tv; @@ -83,265 +93,42 @@ void ssh_reseed(void){ #endif } -/** - * @brief Get random bytes - * - * Make sure to always check the return code of this function! - * - * @param[in] where The buffer to fill with random bytes - * - * @param[in] len The size of the buffer to fill. - * - * @param[in] strong Use a strong or private RNG source. - * - * @return 1 on success, 0 on error. - */ -int ssh_get_random(void *where, int len, int strong) -{ -#ifdef HAVE_OPENSSL_RAND_PRIV_BYTES - if (strong) { - /* Returns -1 when not supported, 0 on error, 1 on success */ - return !!RAND_priv_bytes(where, len); - } -#else - (void)strong; -#endif /* HAVE_RAND_PRIV_BYTES */ - - /* Returns -1 when not supported, 0 on error, 1 on success */ - return !!RAND_bytes(where, len); -} - -SHACTX sha1_init(void) -{ - int rc; - SHACTX c = EVP_MD_CTX_new(); - if (c == NULL) { - return NULL; - } - rc = EVP_DigestInit_ex(c, EVP_sha1(), NULL); - if (rc == 0) { - EVP_MD_CTX_free(c); - c = NULL; - } - return c; -} - -void sha1_update(SHACTX c, const void *data, unsigned long len) -{ - EVP_DigestUpdate(c, data, len); -} +#ifndef WITH_PKCS11_PROVIDER +static ENGINE *engine = NULL; -void sha1_final(unsigned char *md, SHACTX c) +ENGINE *pki_get_engine(void) { - unsigned int mdlen = 0; - - EVP_DigestFinal(c, md, &mdlen); - EVP_MD_CTX_free(c); -} + int ok; -void sha1(const unsigned char *digest, int len, unsigned char *hash) -{ - SHACTX c = sha1_init(); - if (c != NULL) { - sha1_update(c, digest, len); - sha1_final(hash, c); - } -} + if (engine == NULL) { + ENGINE_load_builtin_engines(); -#ifdef HAVE_OPENSSL_ECC -static const EVP_MD *nid_to_evpmd(int nid) -{ - switch (nid) { - case NID_X9_62_prime256v1: - return EVP_sha256(); - case NID_secp384r1: - return EVP_sha384(); - case NID_secp521r1: - return EVP_sha512(); - default: + engine = ENGINE_by_id("pkcs11"); + if (engine == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Could not load the engine: %s", + ERR_error_string(ERR_get_error(), NULL)); return NULL; - } - - return NULL; -} - -void evp(int nid, unsigned char *digest, int len, unsigned char *hash, unsigned int *hlen) -{ - const EVP_MD *evp_md = nid_to_evpmd(nid); - EVP_MD_CTX *md = EVP_MD_CTX_new(); - - EVP_DigestInit(md, evp_md); - EVP_DigestUpdate(md, digest, len); - EVP_DigestFinal(md, hash, hlen); - EVP_MD_CTX_free(md); -} - -EVPCTX evp_init(int nid) -{ - const EVP_MD *evp_md = nid_to_evpmd(nid); - - EVPCTX ctx = EVP_MD_CTX_new(); - if (ctx == NULL) { - return NULL; - } - - EVP_DigestInit(ctx, evp_md); - - return ctx; -} - -void evp_update(EVPCTX ctx, const void *data, unsigned long len) -{ - EVP_DigestUpdate(ctx, data, len); -} - -void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) -{ - EVP_DigestFinal(ctx, md, mdlen); - EVP_MD_CTX_free(ctx); -} -#endif - -SHA256CTX sha256_init(void) -{ - int rc; - SHA256CTX c = EVP_MD_CTX_new(); - if (c == NULL) { - return NULL; - } - rc = EVP_DigestInit_ex(c, EVP_sha256(), NULL); - if (rc == 0) { - EVP_MD_CTX_free(c); - c = NULL; - } - return c; -} - -void sha256_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_free(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_new(); - if (c == NULL) { - return NULL; - } - rc = EVP_DigestInit_ex(c, EVP_sha384(), NULL); - if (rc == 0) { - EVP_MD_CTX_free(c); - c = NULL; - } - return c; -} - -void sha384_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_free(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_new(); - if (c == NULL) { - return NULL; - } - rc = EVP_DigestInit_ex(c, EVP_sha512(), NULL); - if (rc == 0) { - EVP_MD_CTX_free(c); - c = NULL; - } - return c; -} - -void sha512_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_free(c); -} - -void sha512(const unsigned char *digest, int len, unsigned char *hash) -{ - SHA512CTX c = sha512_init(); - if (c != NULL) { - sha512_update(c, digest, len); - sha512_final(hash, c); - } -} + } + SSH_LOG(SSH_LOG_DEBUG, "Engine loaded successfully"); + + ok = ENGINE_init(engine); + if (!ok) { + SSH_LOG(SSH_LOG_TRACE, + "Could not initialize the engine: %s", + ERR_error_string(ERR_get_error(), NULL)); + ENGINE_free(engine); + return NULL; + } -MD5CTX md5_init(void) -{ - int rc; - MD5CTX c = EVP_MD_CTX_new(); - if (c == NULL) { - return NULL; - } - rc = EVP_DigestInit_ex(c, EVP_md5(), NULL); - if(rc == 0) { - EVP_MD_CTX_free(c); - c = NULL; + SSH_LOG(SSH_LOG_DEBUG, "Engine init success"); } - return c; -} - -void md5_update(MD5CTX c, const void *data, unsigned long len) -{ - EVP_DigestUpdate(c, data, len); + return engine; } +#endif /* WITH_PKCS11_PROVIDER */ -void md5_final(unsigned char *md, MD5CTX c) -{ - unsigned int mdlen = 0; - - EVP_DigestFinal(c, md, &mdlen); - EVP_MD_CTX_free(c); -} - -#ifdef HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID +#ifdef HAVE_OPENSSL_EVP_KDF_CTX +#if OPENSSL_VERSION_NUMBER < 0x30000000L static const EVP_MD *sshkdf_digest_to_md(enum ssh_kdf_digest digest_type) { switch (digest_type) { @@ -356,19 +143,50 @@ static const EVP_MD *sshkdf_digest_to_md(enum ssh_kdf_digest digest_type) } return NULL; } +#else +static const char *sshkdf_digest_to_md(enum ssh_kdf_digest digest_type) +{ + switch (digest_type) { + case SSH_KDF_SHA1: + return SN_sha1; + case SSH_KDF_SHA256: + return SN_sha256; + case SSH_KDF_SHA384: + return SN_sha384; + case SSH_KDF_SHA512: + return SN_sha512; + } + return NULL; +} +#endif /* OPENSSL_VERSION_NUMBER */ int ssh_kdf(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, - int key_type, unsigned char *output, + uint8_t key_type, unsigned char *output, size_t requested_len) { + int rc = -1; +#if OPENSSL_VERSION_NUMBER < 0x30000000L EVP_KDF_CTX *ctx = EVP_KDF_CTX_new_id(EVP_KDF_SSHKDF); - int rc; +#else + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "SSHKDF", NULL); + EVP_KDF_CTX *ctx = EVP_KDF_CTX_new(kdf); + OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); + OSSL_PARAM *params = NULL; + const char *md = sshkdf_digest_to_md(crypto->digest_type); + + EVP_KDF_free(kdf); + if (param_bld == NULL) { + EVP_KDF_CTX_free(ctx); + return -1; + } +#endif /* OPENSSL_VERSION_NUMBER */ if (ctx == NULL) { - return -1; + goto out; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L rc = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_MD, sshkdf_digest_to_md(crypto->digest_type)); if (rc != 1) { @@ -388,7 +206,7 @@ int ssh_kdf(struct ssh_crypto_struct *crypto, goto out; } rc = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_SSHKDF_SESSION_ID, - crypto->session_id, crypto->digest_len); + crypto->session_id, crypto->session_id_len); if (rc != 1) { goto out; } @@ -396,8 +214,60 @@ int ssh_kdf(struct ssh_crypto_struct *crypto, if (rc != 1) { goto out; } +#else + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_KDF_PARAM_DIGEST, + md, strlen(md)); + if (rc != 1) { + rc = -1; + goto out; + } + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_KDF_PARAM_KEY, + key, key_len); + if (rc != 1) { + rc = -1; + goto out; + } + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, + OSSL_KDF_PARAM_SSHKDF_XCGHASH, + crypto->secret_hash, + crypto->digest_len); + if (rc != 1) { + rc = -1; + goto out; + } + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, + OSSL_KDF_PARAM_SSHKDF_SESSION_ID, + crypto->session_id, + crypto->session_id_len); + if (rc != 1) { + rc = -1; + goto out; + } + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_KDF_PARAM_SSHKDF_TYPE, + (const char*)&key_type, 1); + if (rc != 1) { + rc = -1; + goto out; + } + + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + rc = -1; + goto out; + } + + rc = EVP_KDF_derive(ctx, output, requested_len, params); + if (rc != 1) { + rc = -1; + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ out: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_BLD_free(param_bld); + OSSL_PARAM_free(params); +#endif EVP_KDF_CTX_free(ctx); if (rc < 0) { return rc; @@ -408,15 +278,15 @@ out: #else int ssh_kdf(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, - int key_type, unsigned char *output, + uint8_t key_type, unsigned char *output, size_t requested_len) { return sshkdf_derive_key(crypto, key, key_len, key_type, output, requested_len); } -#endif +#endif /* HAVE_OPENSSL_EVP_KDF_CTX */ -HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) +HMACCTX hmac_init(const void *key, size_t len, enum ssh_hmac_e type) { HMACCTX ctx = NULL; EVP_PKEY *pkey = NULL; @@ -461,17 +331,22 @@ error: return NULL; } -void hmac_update(HMACCTX ctx, const void *data, unsigned long len) +int hmac_update(HMACCTX ctx, const void *data, size_t len) { - EVP_DigestSignUpdate(ctx, data, len); + return EVP_DigestSignUpdate(ctx, data, len); } -void hmac_final(HMACCTX ctx, unsigned char *hashmacbuf, unsigned int *len) +int hmac_final(HMACCTX ctx, unsigned char *hashmacbuf, size_t *len) { - size_t res; - EVP_DigestSignFinal(ctx, hashmacbuf, &res); + size_t res = *len; + int rc; + rc = EVP_DigestSignFinal(ctx, hashmacbuf, &res); EVP_MD_CTX_free(ctx); - *len = res; + if (rc == 1) { + *len = res; + } + + return rc; } static void evp_cipher_init(struct ssh_cipher_struct *cipher) @@ -515,12 +390,12 @@ static void evp_cipher_init(struct ssh_cipher_struct *cipher) cipher->cipher = EVP_bf_cbc(); break; /* ciphers not using EVP */ -#endif +#endif /* WITH_BLOWFISH_CIPHER */ case SSH_AEAD_CHACHA20_POLY1305: - SSH_LOG(SSH_LOG_WARNING, "The ChaCha cipher cannot be handled here"); + SSH_LOG(SSH_LOG_TRACE, "The ChaCha cipher cannot be handled here"); break; case SSH_NO_CIPHER: - SSH_LOG(SSH_LOG_WARNING, "No valid ciphertype found"); + SSH_LOG(SSH_LOG_TRACE, "No valid ciphertype found"); break; } } @@ -534,7 +409,7 @@ static int evp_cipher_set_encrypt_key(struct ssh_cipher_struct *cipher, rc = EVP_EncryptInit_ex(cipher->ctx, cipher->cipher, NULL, key, IV); if (rc != 1){ - SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptInit_ex failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptInit_ex failed"); return SSH_ERROR; } @@ -546,7 +421,7 @@ static int evp_cipher_set_encrypt_key(struct ssh_cipher_struct *cipher, -1, (uint8_t *)IV); if (rc != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_SET_IV_FIXED failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_SET_IV_FIXED failed"); return SSH_ERROR; } } @@ -564,7 +439,7 @@ static int evp_cipher_set_decrypt_key(struct ssh_cipher_struct *cipher, rc = EVP_DecryptInit_ex(cipher->ctx, cipher->cipher, NULL, key, IV); if (rc != 1){ - SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptInit_ex failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptInit_ex failed"); return SSH_ERROR; } @@ -576,7 +451,7 @@ static int evp_cipher_set_decrypt_key(struct ssh_cipher_struct *cipher, -1, (uint8_t *)IV); if (rc != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_SET_IV_FIXED failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_SET_IV_FIXED failed"); return SSH_ERROR; } } @@ -601,11 +476,11 @@ static void evp_cipher_encrypt(struct ssh_cipher_struct *cipher, (unsigned char *)in, (int)len); if (rc != 1){ - SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptUpdate failed"); return; } if (outlen != (int)len){ - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_DEBUG, "EVP_EncryptUpdate: output size %d for %zu in", outlen, len); @@ -627,11 +502,11 @@ static void evp_cipher_decrypt(struct ssh_cipher_struct *cipher, (unsigned char *)in, (int)len); if (rc != 1){ - SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptUpdate failed"); return; } if (outlen != (int)len){ - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_DEBUG, "EVP_DecryptUpdate: output size %d for %zu in", outlen, len); @@ -686,7 +561,7 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, 1, lastiv); if (rc == 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_IV_GEN failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_IV_GEN failed"); return; } @@ -698,7 +573,7 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, (int)aadlen); outlen = tmplen; if (rc == 0 || outlen != aadlen) { - SSH_LOG(SSH_LOG_WARNING, "Failed to pass authenticated data"); + SSH_LOG(SSH_LOG_TRACE, "Failed to pass authenticated data"); return; } memcpy(out, in, aadlen); @@ -711,7 +586,7 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, (int)len - aadlen); outlen = tmplen; if (rc != 1 || outlen != (int)len - aadlen) { - SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptUpdate failed"); return; } @@ -720,7 +595,7 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, NULL, &tmplen); if (rc < 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptFinal failed: Failed to create a tag"); + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptFinal failed: Failed to create a tag"); return; } @@ -729,7 +604,7 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, authlen, (unsigned char *)tag); if (rc != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_GET_TAG failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_GET_TAG failed"); return; } } @@ -757,7 +632,7 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, 1, lastiv); if (rc == 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_IV_GEN failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_IV_GEN failed"); return SSH_ERROR; } @@ -767,7 +642,7 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, authlen, (unsigned char *)complete_packet + aadlen + encrypted_size); if (rc == 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_SET_TAG failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_SET_TAG failed"); return SSH_ERROR; } @@ -778,7 +653,7 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, (unsigned char *)complete_packet, (int)aadlen); if (rc == 0) { - SSH_LOG(SSH_LOG_WARNING, "Failed to pass authenticated data"); + SSH_LOG(SSH_LOG_TRACE, "Failed to pass authenticated data"); return SSH_ERROR; } /* Do not copy the length to the target buffer, because it is already processed */ @@ -789,14 +664,14 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, (unsigned char *)out, &outlen, (unsigned char *)complete_packet + aadlen, - encrypted_size /* already substracted aadlen*/); + encrypted_size /* already subtracted aadlen */); if (rc != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptUpdate failed"); return SSH_ERROR; } if (outlen != (int)encrypted_size) { - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptUpdate: output size %d for %zd in", outlen, encrypted_size); @@ -808,26 +683,31 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, NULL, &outlen); if (rc < 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptFinal failed: Failed authentication"); + SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptFinal failed: Failed authentication"); return SSH_ERROR; } return SSH_OK; } -#if defined(HAVE_OPENSSL_EVP_CHACHA20) && defined(HAVE_OPENSSL_EVP_POLY1305) +#ifdef HAVE_OPENSSL_EVP_CHACHA20 struct chacha20_poly1305_keysched { /* cipher handle used for encrypting the packets */ EVP_CIPHER_CTX *main_evp; /* cipher handle used for encrypting the length field */ EVP_CIPHER_CTX *header_evp; +#if OPENSSL_VERSION_NUMBER < 0x30000000L /* mac handle used for authenticating the packets */ EVP_PKEY_CTX *pctx; /* Poly1305 key */ EVP_PKEY *key; /* MD context for digesting data in poly1305 */ EVP_MD_CTX *mctx; +#else + /* MAC context used to do poly1305 */ + EVP_MAC_CTX *mctx; +#endif /* OPENSSL_VERSION_NUMBER */ }; static void @@ -845,11 +725,16 @@ chacha20_poly1305_cleanup(struct ssh_cipher_struct *cipher) ctx->main_evp = NULL; EVP_CIPHER_CTX_free(ctx->header_evp); ctx->header_evp = NULL; +#if OPENSSL_VERSION_NUMBER < 0x30000000L /* ctx->pctx is freed as part of MD context */ EVP_PKEY_free(ctx->key); ctx->key = NULL; EVP_MD_CTX_free(ctx->mctx); ctx->mctx = NULL; +#else + EVP_MAC_CTX_free(ctx->mctx); + ctx->mctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ SAFE_FREE(cipher->chacha20_schedule); } @@ -862,6 +747,9 @@ chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher, struct chacha20_poly1305_keysched *ctx = NULL; uint8_t *u8key = key; int ret = SSH_ERROR, rv; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MAC *mac = NULL; +#endif if (cipher->chacha20_schedule == NULL) { ctx = calloc(1, sizeof(*ctx)); @@ -877,38 +765,54 @@ chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher, /* K2 uses the first half of the key */ ctx->main_evp = EVP_CIPHER_CTX_new(); if (ctx->main_evp == NULL) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CIPHER_CTX_new failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CIPHER_CTX_new failed"); goto out; } rv = EVP_EncryptInit_ex(ctx->main_evp, EVP_chacha20(), NULL, u8key, NULL); if (rv != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherInit failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherInit failed"); goto out; } /* K1 uses the second half of the key */ ctx->header_evp = EVP_CIPHER_CTX_new(); if (ctx->header_evp == NULL) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CIPHER_CTX_new failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CIPHER_CTX_new failed"); goto out; } ret = EVP_EncryptInit_ex(ctx->header_evp, EVP_chacha20(), NULL, u8key + CHACHA20_KEYLEN, NULL); if (ret != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherInit failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherInit failed"); goto out; } /* The Poly1305 key initialization is delayed to the time we know * the actual key for packet so we do not need to create a bogus keys */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L ctx->mctx = EVP_MD_CTX_new(); if (ctx->mctx == NULL) { - SSH_LOG(SSH_LOG_WARNING, "EVP_MD_CTX_new failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_MD_CTX_new failed"); return SSH_ERROR; } +#else + mac = EVP_MAC_fetch(NULL, "poly1305", NULL); + if (mac == NULL) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_fetch failed"); + goto out; + } + ctx->mctx = EVP_MAC_CTX_new(mac); + if (ctx->mctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_CTX_new failed"); + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ ret = SSH_OK; out: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MAC_free(mac); +#endif if (ret != SSH_OK) { chacha20_poly1305_cleanup(cipher); } @@ -937,13 +841,13 @@ chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher, ret = EVP_CipherInit_ex(ctx->header_evp, NULL, NULL, NULL, seqbuf, do_encrypt); if (ret != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherInit_ex(header_evp) failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherInit_ex(header_evp) failed"); return SSH_ERROR; } ret = EVP_CipherInit_ex(ctx->main_evp, NULL, NULL, NULL, seqbuf, do_encrypt); if (ret != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherInit_ex(main_evp) failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherInit_ex(main_evp) failed"); return SSH_ERROR; } @@ -972,7 +876,7 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, rv = EVP_CipherUpdate(ctx->main_evp, poly_key, &len, (unsigned char *)zero_block, sizeof(zero_block)); if (rv != 1 || len != CHACHA20_BLOCKSIZE) { - SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptUpdate failed"); goto out; } #ifdef DEBUG_CRYPTO @@ -980,17 +884,18 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, #endif /* DEBUG_CRYPTO */ /* Set the Poly1305 key */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L if (ctx->key == NULL) { /* Poly1305 Initialization needs to know the actual key */ ctx->key = EVP_PKEY_new_mac_key(EVP_PKEY_POLY1305, NULL, poly_key, POLY1305_KEYLEN); if (ctx->key == NULL) { - SSH_LOG(SSH_LOG_WARNING, "EVP_PKEY_new_mac_key failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_PKEY_new_mac_key failed"); goto out; } rv = EVP_DigestSignInit(ctx->mctx, &ctx->pctx, NULL, NULL, ctx->key); if (rv != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_DigestSignInit failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignInit failed"); goto out; } } else { @@ -999,10 +904,17 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, EVP_PKEY_CTRL_SET_MAC_KEY, POLY1305_KEYLEN, (void *)poly_key); if (rv <= 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_PKEY_CTX_ctrl failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_PKEY_CTX_ctrl failed"); goto out; } } +#else + rv = EVP_MAC_init(ctx->mctx, poly_key, POLY1305_KEYLEN, NULL); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_init failed"); + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ ret = SSH_OK; out: @@ -1036,7 +948,7 @@ chacha20_poly1305_aead_decrypt_length(struct ssh_cipher_struct *cipher, rv = EVP_CipherUpdate(ctx->header_evp, out, &outlen, in, len); if (rv != 1 || outlen != sizeof(uint32_t)) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); return SSH_ERROR; } @@ -1046,7 +958,7 @@ chacha20_poly1305_aead_decrypt_length(struct ssh_cipher_struct *cipher, rv = EVP_CipherFinal_ex(ctx->header_evp, out + outlen, &outlen); if (rv != 1 || outlen != 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherFinal_ex failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherFinal_ex failed"); return SSH_ERROR; } @@ -1071,7 +983,7 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, /* Prepare the Poly1305 key */ rv = chacha20_poly1305_packet_setup(cipher, seq, 0); if (rv != SSH_OK) { - SSH_LOG(SSH_LOG_WARNING, "Failed to setup packet"); + SSH_LOG(SSH_LOG_TRACE, "Failed to setup packet"); goto out; } @@ -1080,19 +992,34 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, #endif /* DEBUG_CRYPTO */ /* Calculate MAC of received data */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L rv = EVP_DigestSignUpdate(ctx->mctx, complete_packet, encrypted_size + sizeof(uint32_t)); if (rv != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_DigestSignUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignUpdate failed"); goto out; } rv = EVP_DigestSignFinal(ctx->mctx, tag, &taglen); if (rv != 1) { - SSH_LOG(SSH_LOG_WARNING, "poly1305 verify error"); + SSH_LOG(SSH_LOG_TRACE, "poly1305 verify error"); + goto out; + } +#else + rv = EVP_MAC_update(ctx->mctx, complete_packet, + encrypted_size + sizeof(uint32_t)); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_update failed"); goto out; } + rv = EVP_MAC_final(ctx->mctx, tag, &taglen, POLY1305_TAGLEN); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_final failed"); + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ + #ifdef DEBUG_CRYPTO ssh_log_hexdump("calculated mac", tag, POLY1305_TAGLEN); #endif /* DEBUG_CRYPTO */ @@ -1110,13 +1037,13 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, (uint8_t *)complete_packet + sizeof(uint32_t), encrypted_size); if (rv != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); goto out; } rv = EVP_CipherFinal_ex(ctx->main_evp, out + len, &len); if (rv != 1 || len != 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherFinal_ex failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherFinal_ex failed"); goto out; } @@ -1141,7 +1068,7 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, /* Prepare the Poly1305 key */ ret = chacha20_poly1305_packet_setup(cipher, seq, 1); if (ret != SSH_OK) { - SSH_LOG(SSH_LOG_WARNING, "Failed to setup packet"); + SSH_LOG(SSH_LOG_TRACE, "Failed to setup packet"); return; } @@ -1156,7 +1083,7 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, (unsigned char *)&in_packet->length, sizeof(uint32_t)); if (ret != 1 || outlen != sizeof(uint32_t)) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); return; } #ifdef DEBUG_CRYPTO @@ -1165,7 +1092,7 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, #endif /* DEBUG_CRYPTO */ ret = EVP_CipherFinal_ex(ctx->header_evp, (uint8_t *)out + outlen, &outlen); if (ret != 1 || outlen != 0) { - SSH_LOG(SSH_LOG_PACKET, "EVP_EncryptFinal_ex failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptFinal_ex failed"); return; } @@ -1177,23 +1104,37 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, in_packet->payload, len - sizeof(uint32_t)); if (ret != 1) { - SSH_LOG(SSH_LOG_WARNING, "EVP_CipherUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); return; } /* step 4, compute the MAC */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L ret = EVP_DigestSignUpdate(ctx->mctx, out_packet, len); if (ret <= 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_DigestSignUpdate failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignUpdate failed"); return; } ret = EVP_DigestSignFinal(ctx->mctx, tag, &taglen); if (ret <= 0) { - SSH_LOG(SSH_LOG_WARNING, "EVP_DigestSignFinal failed"); + SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignFinal failed"); + return; + } +#else + ret = EVP_MAC_update(ctx->mctx, (void*)out_packet, len); + if (ret != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_update failed"); return; } + + ret = EVP_MAC_final(ctx->mctx, tag, &taglen, POLY1305_TAGLEN); + if (ret != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_final failed"); + return; + } +#endif /* OPENSSL_VERSION_NUMBER */ } -#endif /* defined(HAVE_OPENSSL_EVP_CHACHA20) && defined(HAVE_OPENSSL_EVP_POLY1305) */ +#endif /* HAVE_OPENSSL_EVP_CHACHA20 */ #ifdef WITH_INSECURE_NONE static void @@ -1222,7 +1163,7 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .decrypt = evp_cipher_decrypt, .cleanup = evp_cipher_cleanup }, -#endif +#endif /* WITH_BLOWFISH_CIPHER */ #ifdef HAS_AES { .name = "aes128-ctr", @@ -1333,7 +1274,7 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { }, #endif /* HAS_DES */ { -#if defined(HAVE_OPENSSL_EVP_CHACHA20) && defined(HAVE_OPENSSL_EVP_POLY1305) +#ifdef HAVE_OPENSSL_EVP_CHACHA20 .ciphertype = SSH_AEAD_CHACHA20_POLY1305, .name = "chacha20-poly1305@openssh.com", .blocksize = CHACHA20_BLOCKSIZE/8, @@ -1349,7 +1290,7 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .cleanup = chacha20_poly1305_cleanup #else .name = "chacha20-poly1305@openssh.com" -#endif /* defined(HAVE_OPENSSL_EVP_CHACHA20) && defined(HAVE_OPENSSL_EVP_POLY1305) */ +#endif /* HAVE_OPENSSL_EVP_CHACHA20 */ }, #ifdef WITH_INSECURE_NONE { @@ -1376,13 +1317,15 @@ struct ssh_cipher_struct *ssh_get_ciphertab(void) */ int ssh_crypto_init(void) { - UNUSED_VAR(size_t i); +#ifndef HAVE_OPENSSL_EVP_CHACHA20 + size_t i; +#endif if (libcrypto_initialized) { return SSH_OK; } if (OpenSSL_version_num() != OPENSSL_VERSION_NUMBER){ - SSH_LOG(SSH_LOG_WARNING, "libssh compiled with %s " + SSH_LOG(SSH_LOG_DEBUG, "libssh compiled with %s " "headers, currently running with %s.", OPENSSL_VERSION_TEXT, OpenSSL_version(OpenSSL_version_num()) @@ -1398,12 +1341,9 @@ int ssh_crypto_init(void) /* Bit #57 denotes AES-NI instruction set extension */ OPENSSL_ia32cap &= ~(1LL << 57); } -#endif -#if OPENSSL_VERSION_NUMBER < 0x10100000L - OpenSSL_add_all_algorithms(); -#endif +#endif /* CAN_DISABLE_AESNI */ -#if !defined(HAVE_OPENSSL_EVP_CHACHA20) || !defined(HAVE_OPENSSL_EVP_POLY1305) +#ifndef HAVE_OPENSSL_EVP_CHACHA20 for (i = 0; ssh_ciphertab[i].name != NULL; i++) { int cmp; @@ -1415,7 +1355,7 @@ int ssh_crypto_init(void) break; } } -#endif /* !defined(HAVE_OPENSSL_EVP_CHACHA20) || !defined(HAVE_OPENSSL_EVP_POLY1305) */ +#endif /* HAVE_OPENSSL_EVP_CHACHA20 */ libcrypto_initialized = 1; @@ -1432,13 +1372,210 @@ void ssh_crypto_finalize(void) return; } - ENGINE_cleanup(); -#if OPENSSL_VERSION_NUMBER < 0x10100000L - EVP_cleanup(); - CRYPTO_cleanup_all_ex_data(); +/* TODO this should finalize engine if it was started, but during atexit calls, + * we are crashing. AFAIK this is related to the dlopened pkcs11 modules calling + * the crypto cleanups earlier. */ +#if 0 + if (engine != NULL) { + ENGINE_finish(engine); + ENGINE_free(engine); + engine = NULL; + } #endif libcrypto_initialized = 0; } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +/** + * @internal + * @brief Create EVP_PKEY from parameters + * + * @param[in] name Algorithm to use. For more info see manpage of EVP_PKEY_CTX_new_from_name + * + * @param[in] param_bld Constructed param builder for the pkey + * + * @param[out] pkey Created EVP_PKEY variable + * + * @param[in] selection Reference selections at man EVP_PKEY_FROMDATA + * + * @return 0 on success, -1 on error + */ +int evp_build_pkey(const char* name, OSSL_PARAM_BLD *param_bld, + EVP_PKEY **pkey, int selection) +{ + int rc; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, name, NULL); + OSSL_PARAM *params = NULL; + + if (ctx == NULL) { + return -1; + } + + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + EVP_PKEY_CTX_free(ctx); + return -1; + } + + rc = EVP_PKEY_fromdata_init(ctx); + if (rc != 1) { + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + return -1; + } + + rc = EVP_PKEY_fromdata(ctx, pkey, selection, params); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to import private key: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + return -1; + } + + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + + return SSH_OK; +} + +/** + * @brief creates a copy of EVP_PKEY + * + * @param[in] name Algorithm to use. For more info see manpage of + * EVP_PKEY_CTX_new_from_name + * + * @param[in] key Key being duplicated from + * + * @param[in] demote Same as at pki_key_dup, only the public + * part of the key gets duplicated if true + * + * @param[out] new_key The key where the duplicate is saved + * + * @return 0 on success, -1 on error + */ +static int +evp_dup_pkey(const char *name, const ssh_key key, int demote, ssh_key new_key) +{ + int rc; + EVP_PKEY_CTX *ctx = NULL; + OSSL_PARAM *params = NULL; + + /* The simple case -- just reference the existing key */ + if (!demote || (key->flags & SSH_KEY_FLAG_PRIVATE) == 0) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + return -1; + } + new_key->key = key->key; + return SSH_OK; + } + + /* demote == 1 */ + ctx = EVP_PKEY_CTX_new_from_name(NULL, name, NULL); + if (ctx == NULL) { + return -1; + } + + rc = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, ¶ms); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + return -1; + } + + if (strcmp(name, "EC") == 0) { + OSSL_PARAM *locate_param = NULL; + /* For ECC keys provided by engine or provider, we need to have the + * explicit public part available, otherwise the key will not be + * usable */ + locate_param = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PUB_KEY); + if (locate_param == NULL) { + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return -1; + } + } + rc = EVP_PKEY_fromdata_init(ctx); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return -1; + } + + rc = EVP_PKEY_fromdata(ctx, &(new_key->key), EVP_PKEY_PUBLIC_KEY, params); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return -1; + } + + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + + return SSH_OK; +} + +int evp_dup_rsa_pkey(const ssh_key key, ssh_key new_key, int demote) +{ + return evp_dup_pkey("RSA", key, demote, new_key); +} + +int evp_dup_ecdsa_pkey(const ssh_key key, ssh_key new_key, int demote) +{ + return evp_dup_pkey("EC", key, demote, new_key); +} +#endif /* OPENSSL_VERSION_NUMBER */ + +ssh_string +pki_key_make_ecpoint_string(const EC_GROUP *g, const EC_POINT *p) +{ + ssh_string s = NULL; + size_t len; + + len = EC_POINT_point2oct(g, + p, + POINT_CONVERSION_UNCOMPRESSED, + NULL, + 0, + NULL); + if (len == 0) { + return NULL; + } + + s = ssh_string_new(len); + if (s == NULL) { + return NULL; + } + + len = EC_POINT_point2oct(g, + p, + POINT_CONVERSION_UNCOMPRESSED, + ssh_string_data(s), + ssh_string_len(s), + NULL); + if (len != ssh_string_len(s)) { + SSH_STRING_FREE(s); + return NULL; + } + + return s; +} + +int pki_key_ecgroup_name_to_nid(const char *group) +{ + if (strcmp(group, NISTP256) == 0 || + strcmp(group, "secp256r1") == 0 || + strcmp(group, "prime256v1") == 0) { + return NID_X9_62_prime256v1; + } else if (strcmp(group, NISTP384) == 0 || + strcmp(group, "secp384r1") == 0) { + return NID_secp384r1; + } else if (strcmp(group, NISTP521) == 0 || + strcmp(group, "secp521r1") == 0) { + return NID_secp521r1; + } + return -1; +} #endif /* LIBCRYPTO */ diff --git a/src/libgcrypt.c b/src/libgcrypt.c index 2383ffa0..4feda00c 100644 --- a/src/libgcrypt.c +++ b/src/libgcrypt.c @@ -69,181 +69,16 @@ static int alloc_key(struct ssh_cipher_struct *cipher) { void ssh_reseed(void){ } -int ssh_get_random(void *where, int len, int strong) -{ - /* variable not used in gcrypt */ - (void) strong; - - /* not using GCRY_VERY_STRONG_RANDOM which is a bit overkill */ - gcry_randomize(where,len,GCRY_STRONG_RANDOM); - - return 1; -} - -SHACTX sha1_init(void) { - SHACTX ctx = NULL; - gcry_md_open(&ctx, GCRY_MD_SHA1, 0); - - return ctx; -} - -void sha1_update(SHACTX c, const void *data, unsigned long len) { - gcry_md_write(c, data, len); -} - -void sha1_final(unsigned char *md, SHACTX c) { - gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA_DIGEST_LEN); - gcry_md_close(c); -} - -void sha1(const unsigned char *digest, int len, unsigned char *hash) { - gcry_md_hash_buffer(GCRY_MD_SHA1, hash, digest, len); -} - -#ifdef HAVE_GCRYPT_ECC -static int nid_to_md_algo(int nid) -{ - switch (nid) { - case NID_gcrypt_nistp256: - return GCRY_MD_SHA256; - case NID_gcrypt_nistp384: - return GCRY_MD_SHA384; - case NID_gcrypt_nistp521: - return GCRY_MD_SHA512; - } - return GCRY_MD_NONE; -} - -void evp(int nid, unsigned char *digest, int len, - unsigned char *hash, unsigned int *hlen) -{ - int algo = nid_to_md_algo(nid); - - /* Note: What gcrypt calls 'hash' is called 'digest' here and - vice-versa. */ - gcry_md_hash_buffer(algo, hash, digest, len); - *hlen = gcry_md_get_algo_dlen(algo); -} - -EVPCTX evp_init(int nid) -{ - gcry_error_t err; - int algo = nid_to_md_algo(nid); - EVPCTX ctx; - - err = gcry_md_open(&ctx, algo, 0); - if (err) { - return NULL; - } - - return ctx; -} - -void evp_update(EVPCTX ctx, const void *data, unsigned long len) -{ - gcry_md_write(ctx, data, len); -} - -void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) -{ - int algo = gcry_md_get_algo(ctx); - *mdlen = gcry_md_get_algo_dlen(algo); - memcpy(md, gcry_md_read(ctx, algo), *mdlen); - gcry_md_close(ctx); -} -#endif - -SHA256CTX sha256_init(void) { - SHA256CTX ctx = NULL; - gcry_md_open(&ctx, GCRY_MD_SHA256, 0); - - return ctx; -} - -void sha256_update(SHACTX c, const void *data, unsigned long len) { - gcry_md_write(c, data, len); -} - -void sha256_final(unsigned char *md, SHACTX c) { - gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA256_DIGEST_LEN); - gcry_md_close(c); -} - -void sha256(const unsigned char *digest, int len, unsigned char *hash){ - gcry_md_hash_buffer(GCRY_MD_SHA256, hash, digest, len); -} - -SHA384CTX sha384_init(void) { - SHA384CTX ctx = NULL; - gcry_md_open(&ctx, GCRY_MD_SHA384, 0); - - return ctx; -} - -void sha384_update(SHACTX c, const void *data, unsigned long len) { - gcry_md_write(c, data, len); -} - -void sha384_final(unsigned char *md, SHACTX c) { - gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA384_DIGEST_LEN); - gcry_md_close(c); -} - -void sha384(const unsigned char *digest, int len, unsigned char *hash) { - gcry_md_hash_buffer(GCRY_MD_SHA384, hash, digest, len); -} - -SHA512CTX sha512_init(void) { - SHA512CTX ctx = NULL; - gcry_md_open(&ctx, GCRY_MD_SHA512, 0); - - return ctx; -} - -void sha512_update(SHACTX c, const void *data, unsigned long len) { - gcry_md_write(c, data, len); -} - -void sha512_final(unsigned char *md, SHACTX c) { - gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA512_DIGEST_LEN); - gcry_md_close(c); -} - -void sha512(const unsigned char *digest, int len, unsigned char *hash) { - gcry_md_hash_buffer(GCRY_MD_SHA512, hash, digest, len); -} - -MD5CTX md5_init(void) { - MD5CTX c = NULL; - gcry_md_open(&c, GCRY_MD_MD5, 0); - - return c; -} - -void md5_update(MD5CTX c, const void *data, unsigned long len) { - gcry_md_write(c,data,len); -} - -void md5_final(unsigned char *md, MD5CTX c) { - gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), MD5_DIGEST_LEN); - gcry_md_close(c); -} - int ssh_kdf(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, - int key_type, unsigned char *output, + uint8_t key_type, unsigned char *output, size_t requested_len) { return sshkdf_derive_key(crypto, key, key_len, key_type, output, requested_len); } -HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) { +HMACCTX hmac_init(const void *key, size_t len, enum ssh_hmac_e type) { HMACCTX c = NULL; switch(type) { @@ -268,14 +103,17 @@ HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) { return c; } -void hmac_update(HMACCTX c, const void *data, unsigned long len) { +int hmac_update(HMACCTX c, const void *data, size_t len) { gcry_md_write(c, data, len); + return 1; } -void hmac_final(HMACCTX c, unsigned char *hashmacbuf, unsigned int *len) { - *len = gcry_md_get_algo_dlen(gcry_md_get_algo(c)); +int hmac_final(HMACCTX c, unsigned char *hashmacbuf, size_t *len) { + unsigned int tmp = gcry_md_get_algo_dlen(gcry_md_get_algo(c)); + *len = (size_t)tmp; memcpy(hashmacbuf, gcry_md_read(c, 0), *len); gcry_md_close(c); + return 1; } #ifdef WITH_BLOWFISH_CIPHER @@ -307,12 +145,12 @@ static int blowfish_set_key(struct ssh_cipher_struct *cipher, void *key, void *I } static void blowfish_encrypt(struct ssh_cipher_struct *cipher, void *in, - void *out, unsigned long len) { + void *out, size_t len) { gcry_cipher_encrypt(cipher->key[0], out, len, in, len); } static void blowfish_decrypt(struct ssh_cipher_struct *cipher, void *in, - void *out, unsigned long len) { + void *out, size_t len) { gcry_cipher_decrypt(cipher->key[0], out, len, in, len); } #endif /* WITH_BLOWFISH_CIPHER */ @@ -350,7 +188,7 @@ static int aes_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { } break; default: - SSH_LOG(SSH_LOG_WARNING, "Unksupported key length %u.", cipher->keysize); + SSH_LOG(SSH_LOG_TRACE, "Unsupported key length %u.", cipher->keysize); SAFE_FREE(cipher->key); return -1; } @@ -434,7 +272,7 @@ aes_gcm_encrypt(struct ssh_cipher_struct *cipher, err = gcry_cipher_setiv(cipher->key[0], cipher->last_iv, AES_GCM_IVLEN); - /* This actualy does not increment the packet counter for the + /* This actually does not increment the packet counter for the * current encryption operation, but for the next one. The first * operation needs to be completed with the derived IV. * @@ -443,7 +281,7 @@ aes_gcm_encrypt(struct ssh_cipher_struct *cipher, */ uint64_inc(cipher->last_iv + 4); if (err) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", gpg_strerror(err)); return; } @@ -451,7 +289,7 @@ aes_gcm_encrypt(struct ssh_cipher_struct *cipher, /* Pass the authenticated data (packet_length) */ err = gcry_cipher_authenticate(cipher->key[0], in, aadlen); if (err) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_authenticate failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_authenticate failed: %s", gpg_strerror(err)); return; } @@ -464,7 +302,7 @@ aes_gcm_encrypt(struct ssh_cipher_struct *cipher, (unsigned char *)in + aadlen, len - aadlen); if (err) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", gpg_strerror(err)); return; } @@ -474,7 +312,7 @@ aes_gcm_encrypt(struct ssh_cipher_struct *cipher, (void *)tag, authlen); if (err) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_gettag failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_gettag failed: %s", gpg_strerror(err)); return; } @@ -499,7 +337,7 @@ aes_gcm_decrypt(struct ssh_cipher_struct *cipher, err = gcry_cipher_setiv(cipher->key[0], cipher->last_iv, AES_GCM_IVLEN); - /* This actualy does not increment the packet counter for the + /* This actually does not increment the packet counter for the * current encryption operation, but for the next one. The first * operation needs to be completed with the derived IV. * @@ -508,7 +346,7 @@ aes_gcm_decrypt(struct ssh_cipher_struct *cipher, */ uint64_inc(cipher->last_iv + 4); if (err) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", gpg_strerror(err)); return SSH_ERROR; } @@ -518,7 +356,7 @@ aes_gcm_decrypt(struct ssh_cipher_struct *cipher, complete_packet, aadlen); if (err) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_authenticate failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_authenticate failed: %s", gpg_strerror(err)); return SSH_ERROR; } @@ -532,7 +370,7 @@ aes_gcm_decrypt(struct ssh_cipher_struct *cipher, (unsigned char *)complete_packet + aadlen, encrypted_size); if (err) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_decrypt failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_decrypt failed: %s", gpg_strerror(err)); return SSH_ERROR; } @@ -542,10 +380,10 @@ aes_gcm_decrypt(struct ssh_cipher_struct *cipher, (unsigned char *)complete_packet + aadlen + encrypted_size, authlen); if (gpg_err_code(err) == GPG_ERR_CHECKSUM) { - SSH_LOG(SSH_LOG_WARNING, "The authentication tag does not match"); + SSH_LOG(SSH_LOG_DEBUG, "The authentication tag does not match"); return SSH_ERROR; } else if (err != GPG_ERR_NO_ERROR) { - SSH_LOG(SSH_LOG_WARNING, "General error while decryption: %s", + SSH_LOG(SSH_LOG_TRACE, "General error while decryption: %s", gpg_strerror(err)); return SSH_ERROR; } @@ -578,12 +416,12 @@ static int des3_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { } static void des3_encrypt(struct ssh_cipher_struct *cipher, void *in, - void *out, unsigned long len) { + void *out, size_t len) { gcry_cipher_encrypt(cipher->key[0], out, len, in, len); } static void des3_decrypt(struct ssh_cipher_struct *cipher, void *in, - void *out, unsigned long len) { + void *out, size_t len) { gcry_cipher_decrypt(cipher->key[0], out, len, in, len); } @@ -631,7 +469,7 @@ static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, err = gcry_cipher_open(&ctx->main_hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_STREAM, 0); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_open failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_open failed: %s", gpg_strerror(err)); SAFE_FREE(cipher->chacha20_schedule); return -1; @@ -639,7 +477,7 @@ static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, err = gcry_cipher_open(&ctx->header_hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_STREAM, 0); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_open failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_open failed: %s", gpg_strerror(err)); gcry_cipher_close(ctx->main_hd); SAFE_FREE(cipher->chacha20_schedule); @@ -647,7 +485,7 @@ static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, } err = gcry_mac_open(&ctx->mac_hd, GCRY_MAC_POLY1305, 0, NULL); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_mac_open failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_open failed: %s", gpg_strerror(err)); gcry_cipher_close(ctx->main_hd); gcry_cipher_close(ctx->header_hd); @@ -660,7 +498,7 @@ static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, err = gcry_cipher_setkey(ctx->main_hd, u8key, CHACHA20_KEYLEN); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setkey failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setkey failed: %s", gpg_strerror(err)); chacha20_cleanup(cipher); return -1; @@ -669,7 +507,7 @@ static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, err = gcry_cipher_setkey(ctx->header_hd, u8key + CHACHA20_KEYLEN, CHACHA20_KEYLEN); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setkey failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setkey failed: %s", gpg_strerror(err)); chacha20_cleanup(cipher); return -1; @@ -696,7 +534,7 @@ static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, /* step 1, prepare the poly1305 key */ err = gcry_cipher_setiv(ctx->main_hd, (uint8_t *)&seq, sizeof(seq)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", gpg_strerror(err)); goto out; } @@ -708,13 +546,13 @@ static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, zero_block, sizeof(zero_block)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", gpg_strerror(err)); goto out; } err = gcry_mac_setkey(ctx->mac_hd, poly_key, POLY1305_KEYLEN); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_mac_setkey failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_setkey failed: %s", gpg_strerror(err)); goto out; } @@ -722,7 +560,7 @@ static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, /* step 2, encrypt length field */ err = gcry_cipher_setiv(ctx->header_hd, (uint8_t *)&seq, sizeof(seq)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", gpg_strerror(err)); goto out; } @@ -732,7 +570,7 @@ static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, (uint8_t *)&in_packet->length, sizeof(uint32_t)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", gpg_strerror(err)); goto out; } @@ -744,7 +582,7 @@ static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, in_packet->payload, len - sizeof(uint32_t)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", gpg_strerror(err)); goto out; } @@ -752,13 +590,13 @@ static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, /* step 4, compute the MAC */ err = gcry_mac_write(ctx->mac_hd, (uint8_t *)out_packet, len); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_mac_write failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_write failed: %s", gpg_strerror(err)); goto out; } err = gcry_mac_read(ctx->mac_hd, tag, &taglen); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_mac_read failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_read failed: %s", gpg_strerror(err)); goto out; } @@ -784,7 +622,7 @@ static int chacha20_poly1305_aead_decrypt_length( err = gcry_cipher_setiv(ctx->header_hd, (uint8_t *)&seq, sizeof(seq)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", gpg_strerror(err)); return SSH_ERROR; } @@ -794,7 +632,7 @@ static int chacha20_poly1305_aead_decrypt_length( in, sizeof(uint32_t)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_decrypt failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_decrypt failed: %s", gpg_strerror(err)); return SSH_ERROR; } @@ -820,7 +658,7 @@ static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, /* step 1, prepare the poly1305 key */ err = gcry_cipher_setiv(ctx->main_hd, (uint8_t *)&seq, sizeof(seq)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", gpg_strerror(err)); goto out; } @@ -832,13 +670,13 @@ static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, zero_block, sizeof(zero_block)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", gpg_strerror(err)); goto out; } err = gcry_mac_setkey(ctx->mac_hd, poly_key, POLY1305_KEYLEN); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_mac_setkey failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_setkey failed: %s", gpg_strerror(err)); goto out; } @@ -847,7 +685,7 @@ static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, err = gcry_mac_write(ctx->mac_hd, (uint8_t *)complete_packet, encrypted_size + sizeof(uint32_t)); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_mac_write failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_write failed: %s", gpg_strerror(err)); goto out; } @@ -856,7 +694,7 @@ static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, SSH_LOG(SSH_LOG_PACKET, "poly1305 verify error"); goto out; } else if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_mac_verify failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_verify failed: %s", gpg_strerror(err)); goto out; } @@ -868,7 +706,7 @@ static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, (uint8_t *)complete_packet + sizeof(uint32_t), encrypted_size); if (err != 0) { - SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_decrypt failed: %s", + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_decrypt failed: %s", gpg_strerror(err)); goto out; } diff --git a/src/libmbedcrypto.c b/src/libmbedcrypto.c index 35e750e4..55951764 100644 --- a/src/libmbedcrypto.c +++ b/src/libmbedcrypto.c @@ -27,6 +27,7 @@ #include "libssh/crypto.h" #include "libssh/priv.h" #include "libssh/misc.h" +#include "mbedcrypto-compat.h" #if defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) #include "libssh/bytearray.h" #include "libssh/chacha20-poly1305-common.h" @@ -41,7 +42,7 @@ #endif /* MBEDTLS_GCM_C */ static mbedtls_entropy_context ssh_mbedtls_entropy; -static mbedtls_ctr_drbg_context ssh_mbedtls_ctr_drbg; +extern mbedtls_ctr_drbg_context ssh_mbedtls_ctr_drbg; static int libmbedcrypto_initialized = 0; @@ -50,354 +51,16 @@ void ssh_reseed(void) mbedtls_ctr_drbg_reseed(&ssh_mbedtls_ctr_drbg, NULL, 0); } -int ssh_get_random(void *where, int len, int strong) -{ - return ssh_mbedtls_random(where, len, strong); -} - -SHACTX sha1_init(void) -{ - SHACTX ctx = NULL; - int rc; - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); - - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if (ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - -void sha1_update(SHACTX c, const void *data, unsigned long len) -{ - mbedtls_md_update(c, data, len); -} - -void sha1_final(unsigned char *md, SHACTX c) -{ - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); -} - -void sha1(const unsigned char *digest, int len, unsigned char *hash) -{ - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); - } -} - -static mbedtls_md_type_t nid_to_md_algo(int nid) -{ - switch (nid) { - case NID_mbedtls_nistp256: - return MBEDTLS_MD_SHA256; - case NID_mbedtls_nistp384: - return MBEDTLS_MD_SHA384; - case NID_mbedtls_nistp521: - return MBEDTLS_MD_SHA512; - } - return MBEDTLS_MD_NONE; -} - -void evp(int nid, unsigned char *digest, int len, - unsigned char *hash, unsigned int *hlen) -{ - mbedtls_md_type_t algo = nid_to_md_algo(nid); - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(algo); - - - if (md_info != NULL) { - *hlen = mbedtls_md_get_size(md_info); - mbedtls_md(md_info, digest, len, hash); - } -} - -EVPCTX evp_init(int nid) -{ - EVPCTX ctx = NULL; - int rc; - mbedtls_md_type_t algo = nid_to_md_algo(nid); - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(algo); - - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if (ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - -void evp_update(EVPCTX ctx, const void *data, unsigned long len) -{ - mbedtls_md_update(ctx, data, len); -} - -void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) -{ - *mdlen = mbedtls_md_get_size(ctx->md_info); - mbedtls_md_finish(ctx, md); - mbedtls_md_free(ctx); - SAFE_FREE(ctx); -} - -SHA256CTX sha256_init(void) -{ - SHA256CTX ctx = NULL; - int rc; - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); - - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if(ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - -void sha256_update(SHA256CTX c, const void *data, unsigned long len) -{ - mbedtls_md_update(c, data, len); -} - -void sha256_final(unsigned char *md, SHA256CTX c) -{ - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); -} - -void sha256(const unsigned char *digest, int len, unsigned char *hash) -{ - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); - } -} - -SHA384CTX sha384_init(void) -{ - SHA384CTX ctx = NULL; - int rc; - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); - - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if (ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - -void sha384_update(SHA384CTX c, const void *data, unsigned long len) -{ - mbedtls_md_update(c, data, len); -} - -void sha384_final(unsigned char *md, SHA384CTX c) -{ - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); -} - -void sha384(const unsigned char *digest, int len, unsigned char *hash) -{ - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); - } -} - -SHA512CTX sha512_init(void) -{ - SHA512CTX ctx = NULL; - int rc; - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if (ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - -void sha512_update(SHA512CTX c, const void *data, unsigned long len) -{ - mbedtls_md_update(c, data, len); -} - -void sha512_final(unsigned char *md, SHA512CTX c) -{ - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); -} - -void sha512(const unsigned char *digest, int len, unsigned char *hash) -{ - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); - } -} - -MD5CTX md5_init(void) -{ - MD5CTX ctx = NULL; - int rc; - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_MD5); - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if (ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - - -void md5_update(MD5CTX c, const void *data, unsigned long len) { - mbedtls_md_update(c, data, len); -} - -void md5_final(unsigned char *md, MD5CTX c) -{ - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); -} - int ssh_kdf(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, - int key_type, unsigned char *output, + uint8_t key_type, unsigned char *output, size_t requested_len) { return sshkdf_derive_key(crypto, key, key_len, key_type, output, requested_len); } -HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) +HMACCTX hmac_init(const void *key, size_t len, enum ssh_hmac_e type) { HMACCTX ctx = NULL; const mbedtls_md_info_t *md_info = NULL; @@ -446,17 +109,21 @@ error: return NULL; } -void hmac_update(HMACCTX c, const void *data, unsigned long len) +/* mbedtls returns 0 on success, but in this context + * success is 1 */ +int hmac_update(HMACCTX c, const void *data, size_t len) { - mbedtls_md_hmac_update(c, data, len); + return !mbedtls_md_hmac_update(c, data, len); } -void hmac_final(HMACCTX c, unsigned char *hashmacbuf, unsigned int *len) +int hmac_final(HMACCTX c, unsigned char *hashmacbuf, size_t *len) { - *len = mbedtls_md_get_size(c->md_info); - mbedtls_md_hmac_finish(c, hashmacbuf); + int rc; + *len = (unsigned int)mbedtls_md_get_size(c->md_info); + rc = !mbedtls_md_hmac_finish(c, hashmacbuf); mbedtls_md_free(c); SAFE_FREE(c); + return rc; } static int @@ -467,6 +134,8 @@ cipher_init(struct ssh_cipher_struct *cipher, { const mbedtls_cipher_info_t *cipher_info = NULL; mbedtls_cipher_context_t *ctx; + size_t key_bitlen = 0; + size_t iv_size = 0; int rc; if (operation == MBEDTLS_ENCRYPT) { @@ -474,7 +143,7 @@ cipher_init(struct ssh_cipher_struct *cipher, } else if (operation == MBEDTLS_DECRYPT) { ctx = &cipher->decrypt_ctx; } else { - SSH_LOG(SSH_LOG_WARNING, "unknown operation"); + SSH_LOG(SSH_LOG_TRACE, "unknown operation"); return 1; } @@ -483,21 +152,21 @@ cipher_init(struct ssh_cipher_struct *cipher, rc = mbedtls_cipher_setup(ctx, cipher_info); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_setup failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_setup failed"); goto error; } - rc = mbedtls_cipher_setkey(ctx, key, - cipher_info->key_bitlen, - operation); + key_bitlen = mbedtls_cipher_info_get_key_bitlen(cipher_info); + rc = mbedtls_cipher_setkey(ctx, key, key_bitlen, operation); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_setkey failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_setkey failed"); goto error; } - rc = mbedtls_cipher_set_iv(ctx, IV, cipher_info->iv_size); + iv_size = mbedtls_cipher_info_get_iv_size(cipher_info); + rc = mbedtls_cipher_set_iv(ctx, IV, iv_size); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_set_iv failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_set_iv failed"); goto error; } @@ -516,13 +185,13 @@ cipher_set_encrypt_key(struct ssh_cipher_struct *cipher, rc = cipher_init(cipher, MBEDTLS_ENCRYPT, key, IV); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "cipher_init failed"); + SSH_LOG(SSH_LOG_TRACE, "cipher_init failed"); goto error; } rc = mbedtls_cipher_reset(&cipher->encrypt_ctx); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_reset failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed"); goto error; } @@ -540,23 +209,23 @@ cipher_set_encrypt_key_cbc(struct ssh_cipher_struct *cipher, rc = cipher_init(cipher, MBEDTLS_ENCRYPT, key, IV); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "cipher_init failed"); + SSH_LOG(SSH_LOG_TRACE, "cipher_init failed"); goto error; } - /* libssh only encypts and decrypts packets that are multiples of a block + /* libssh only encrypts and decrypts packets that are multiples of a block * size, and no padding is used */ rc = mbedtls_cipher_set_padding_mode(&cipher->encrypt_ctx, MBEDTLS_PADDING_NONE); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_set_padding_mode failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_set_padding_mode failed"); goto error; } rc = mbedtls_cipher_reset(&cipher->encrypt_ctx); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_reset failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed"); goto error; } @@ -573,17 +242,18 @@ cipher_set_key_gcm(struct ssh_cipher_struct *cipher, void *IV) { const mbedtls_cipher_info_t *cipher_info = NULL; + size_t key_bitlen = 0; int rc; mbedtls_gcm_init(&cipher->gcm_ctx); cipher_info = mbedtls_cipher_info_from_type(cipher->type); - rc = mbedtls_gcm_setkey(&cipher->gcm_ctx, - MBEDTLS_CIPHER_ID_AES, - key, - cipher_info->key_bitlen); + key_bitlen = mbedtls_cipher_info_get_key_bitlen(cipher_info); + rc = mbedtls_gcm_setkey(&cipher->gcm_ctx, MBEDTLS_CIPHER_ID_AES, + key, key_bitlen); + if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_gcm_setkey failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_gcm_setkey failed"); goto error; } @@ -606,13 +276,13 @@ cipher_set_decrypt_key(struct ssh_cipher_struct *cipher, rc = cipher_init(cipher, MBEDTLS_DECRYPT, key, IV); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "cipher_init failed"); + SSH_LOG(SSH_LOG_TRACE, "cipher_init failed"); goto error; } mbedtls_cipher_reset(&cipher->decrypt_ctx); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_reset failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed"); goto error; } @@ -631,20 +301,20 @@ cipher_set_decrypt_key_cbc(struct ssh_cipher_struct *cipher, rc = cipher_init(cipher, MBEDTLS_DECRYPT, key, IV); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "cipher_init failed"); + SSH_LOG(SSH_LOG_TRACE, "cipher_init failed"); goto error; } rc = mbedtls_cipher_set_padding_mode(&cipher->decrypt_ctx, MBEDTLS_PADDING_NONE); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_set_padding_mode failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_set_padding_mode failed"); goto error; } mbedtls_cipher_reset(&cipher->decrypt_ctx); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_reset failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed"); goto error; } @@ -664,7 +334,7 @@ static void cipher_encrypt(struct ssh_cipher_struct *cipher, int rc = 0; rc = mbedtls_cipher_update(&cipher->encrypt_ctx, in, len, out, &outlen); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update failed during encryption"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_update failed during encryption"); return; } @@ -680,12 +350,12 @@ static void cipher_encrypt(struct ssh_cipher_struct *cipher, total_len += outlen; if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_finish failed during encryption"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_finish failed during encryption"); return; } if (total_len != len) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update: output size %zu for %zu", + SSH_LOG(SSH_LOG_DEBUG, "mbedtls_cipher_update: output size %zu for %zu", outlen, len); return; } @@ -699,12 +369,12 @@ static void cipher_encrypt_cbc(struct ssh_cipher_struct *cipher, void *in, void int rc = 0; rc = mbedtls_cipher_update(&cipher->encrypt_ctx, in, len, out, &outlen); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update failed during encryption"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_update failed during encryption"); return; } if (outlen != len) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update: output size %zu for %zu", + SSH_LOG(SSH_LOG_DEBUG, "mbedtls_cipher_update: output size %zu for %zu", outlen, len); return; } @@ -722,7 +392,7 @@ static void cipher_decrypt(struct ssh_cipher_struct *cipher, rc = mbedtls_cipher_update(&cipher->decrypt_ctx, in, len, out, &outlen); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update failed during decryption"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_update failed during decryption"); return; } @@ -736,14 +406,14 @@ static void cipher_decrypt(struct ssh_cipher_struct *cipher, outlen, &outlen); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_reset failed during decryption"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed during decryption"); return; } total_len += outlen; if (total_len != len) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update: output size %zu for %zu", + SSH_LOG(SSH_LOG_DEBUG, "mbedtls_cipher_update: output size %zu for %zu", outlen, len); return; } @@ -757,7 +427,7 @@ static void cipher_decrypt_cbc(struct ssh_cipher_struct *cipher, void *in, void int rc = 0; rc = mbedtls_cipher_update(&cipher->decrypt_ctx, in, len, out, &outlen); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update failed during decryption"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_update failed during decryption"); return; } @@ -776,19 +446,19 @@ static void cipher_decrypt_cbc(struct ssh_cipher_struct *cipher, void *in, void } if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_finish failed during decryption"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_finish failed during decryption"); return; } rc = mbedtls_cipher_reset(&cipher->decrypt_ctx); if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_reset failed during decryption"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed during decryption"); return; } if (outlen != len) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_update: output size %zu for %zu", + SSH_LOG(SSH_LOG_DEBUG, "mbedtls_cipher_update: output size %zu for %zu", outlen, len); return; } @@ -842,7 +512,7 @@ cipher_encrypt_gcm(struct ssh_cipher_struct *cipher, authlen, tag); /* tag */ if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_gcm_crypt_and_tag failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_gcm_crypt_and_tag failed"); return; } @@ -876,7 +546,7 @@ cipher_decrypt_gcm(struct ssh_cipher_struct *cipher, (const uint8_t *)complete_packet + aadlen, /* input */ (unsigned char *)out); /* output */ if (rc != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_gcm_auth_decrypt failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_gcm_auth_decrypt failed"); return SSH_ERROR; } @@ -950,14 +620,14 @@ chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher, /* K2 uses the first half of the key */ rv = mbedtls_chacha20_setkey(&ctx->main_ctx, u8key); if (rv != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_setkey(main_ctx) failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_setkey(main_ctx) failed"); goto out; } /* K1 uses the second half of the key */ rv = mbedtls_chacha20_setkey(&ctx->header_ctx, u8key + CHACHA20_KEYLEN); if (rv != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_setkey(header_ctx) failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_setkey(header_ctx) failed"); goto out; } @@ -981,7 +651,7 @@ chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher, /* The nonce in mbedTLS is 96 b long. The counter is passed through separate * parameter of 32 b size. - * Encode the seqence number into the last 8 bytes. + * Encode the sequence number into the last 8 bytes. */ PUSH_BE_U64(seqbuf, 4, seq); #ifdef DEBUG_CRYPTO @@ -990,13 +660,13 @@ chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher, ret = mbedtls_chacha20_starts(&ctx->header_ctx, seqbuf, 0); if (ret != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_starts(header_ctx) failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_starts(header_ctx) failed"); return SSH_ERROR; } ret = mbedtls_chacha20_starts(&ctx->main_ctx, seqbuf, 0); if (ret != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_starts(main_ctx) failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_starts(main_ctx) failed"); return SSH_ERROR; } @@ -1025,7 +695,7 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, rv = mbedtls_chacha20_update(&ctx->main_ctx, sizeof(zero_block), zero_block, poly_key); if (rv != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); goto out; } #ifdef DEBUG_CRYPTO @@ -1035,7 +705,7 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, /* Set the Poly1305 key */ rv = mbedtls_poly1305_starts(&ctx->poly_ctx, poly_key); if (rv != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_starts failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_starts failed"); goto out; } @@ -1071,7 +741,7 @@ chacha20_poly1305_aead_decrypt_length(struct ssh_cipher_struct *cipher, rv = mbedtls_chacha20_update(&ctx->header_ctx, sizeof(uint32_t), in, out); if (rv != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); return SSH_ERROR; } @@ -1099,7 +769,7 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, /* Prepare the Poly1305 key */ rv = chacha20_poly1305_packet_setup(cipher, seq, 0); if (rv != SSH_OK) { - SSH_LOG(SSH_LOG_WARNING, "Failed to setup packet"); + SSH_LOG(SSH_LOG_TRACE, "Failed to setup packet"); goto out; } @@ -1111,13 +781,13 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, rv = mbedtls_poly1305_update(&ctx->poly_ctx, complete_packet, encrypted_size + sizeof(uint32_t)); if (rv != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_update failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_update failed"); goto out; } rv = mbedtls_poly1305_finish(&ctx->poly_ctx, tag); if (rv != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_finish failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_finish failed"); goto out; } @@ -1138,7 +808,7 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, (uint8_t *)complete_packet + sizeof(uint32_t), out); if (rv != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); goto out; } @@ -1162,7 +832,7 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, /* Prepare the Poly1305 key */ ret = chacha20_poly1305_packet_setup(cipher, seq, 1); if (ret != SSH_OK) { - SSH_LOG(SSH_LOG_WARNING, "Failed to setup packet"); + SSH_LOG(SSH_LOG_TRACE, "Failed to setup packet"); return; } @@ -1175,7 +845,7 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, (unsigned char *)&in_packet->length, (unsigned char *)&out_packet->length); if (ret != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); return; } #ifdef DEBUG_CRYPTO @@ -1188,19 +858,19 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, ret = mbedtls_chacha20_update(&ctx->main_ctx, len - sizeof(uint32_t), in_packet->payload, out_packet->payload); if (ret != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); return; } /* step 4, compute the MAC */ ret = mbedtls_poly1305_update(&ctx->poly_ctx, (const unsigned char *)out_packet, len); if (ret != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_update failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_update failed"); return; } ret = mbedtls_poly1305_finish(&ctx->poly_ctx, tag); if (ret != 0) { - SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_finish failed"); + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_finish failed"); return; } } @@ -1430,22 +1100,6 @@ int ssh_crypto_init(void) return SSH_OK; } -int ssh_mbedtls_random(void *where, int len, int strong) -{ - int rc = 0; - if (strong) { - mbedtls_ctr_drbg_set_prediction_resistance(&ssh_mbedtls_ctr_drbg, - MBEDTLS_CTR_DRBG_PR_ON); - rc = mbedtls_ctr_drbg_random(&ssh_mbedtls_ctr_drbg, where, len); - mbedtls_ctr_drbg_set_prediction_resistance(&ssh_mbedtls_ctr_drbg, - MBEDTLS_CTR_DRBG_PR_OFF); - } else { - rc = mbedtls_ctr_drbg_random(&ssh_mbedtls_ctr_drbg, where, len); - } - - return !rc; -} - mbedtls_ctr_drbg_context *ssh_get_mbedtls_ctr_drbg_context(void) { return &ssh_mbedtls_ctr_drbg; diff --git a/src/libssh.map b/src/libssh.map index 82cdfa21..558f921d 100644 --- a/src/libssh.map +++ b/src/libssh.map @@ -1,4 +1,4 @@ -# This map file was updated with abimap-0.3.1 +# This map file was updated with abimap-0.3.2 LIBSSH_4_5_0 # Released { @@ -402,10 +402,8 @@ LIBSSH_4_5_0 # Released ssh_userauth_pubkey; ssh_userauth_publickey; ssh_userauth_publickey_auto; - ssh_userauth_publickey_auto_get_current_identity; ssh_userauth_try_publickey; ssh_version; - ssh_vlog; ssh_write_knownhost; string_burn; string_copy; @@ -449,3 +447,30 @@ LIBSSH_4_8_1 # Released ssh_session_get_known_hosts_entry; ssh_threads_get_default; } LIBSSH_4_8_0; + +LIBSSH_4_9_0 # Released +{ + global: + ssh_channel_open_forward_port; + ssh_key_dup; + ssh_send_issue_banner; + ssh_session_set_disconnect_message; + ssh_userauth_publickey_auto_get_current_identity; + ssh_vlog; +} LIBSSH_4_8_1; + +LIBSSH_AFTER_4_9_0 +{ + global: + sftp_channel_default_data_callback; + sftp_channel_default_subsystem_request; + sftp_aio_begin_read; + sftp_aio_begin_write; + sftp_aio_free; + sftp_aio_wait_read; + sftp_aio_wait_write; + ssh_pki_export_privkey_base64_format; + ssh_pki_export_privkey_file_format; + ssh_channel_request_pty_size_modes; +} LIBSSH_4_9_0; + @@ -47,7 +47,7 @@ static LIBSSH_THREAD ssh_logging_callback ssh_log_cb; static LIBSSH_THREAD void *ssh_log_userdata; /** - * @defgroup libssh_log The SSH logging functions. + * @defgroup libssh_log The SSH logging functions * @ingroup libssh * * Logging functions for debugging and problem resolving. diff --git a/src/match.c b/src/match.c index 1a60d732..3e58f733 100644 --- a/src/match.c +++ b/src/match.c @@ -43,7 +43,7 @@ #include "libssh/priv.h" -#define MAX_MATCH_RECURSION 32 +#define MAX_MATCH_RECURSION 16 /* * Returns true if the given string matches the pattern (which may contain ? @@ -51,74 +51,77 @@ */ static int match_pattern(const char *s, const char *pattern, size_t limit) { - bool had_asterisk = false; - if (s == NULL || pattern == NULL || limit <= 0) { - return 0; - } + bool had_asterisk = false; - for (;;) { - /* If at end of pattern, accept if also at end of string. */ - if (*pattern == '\0') { - return (*s == '\0'); + if (s == NULL || pattern == NULL || limit <= 0) { + return 0; } - while (*pattern == '*') { - /* Skip the asterisk. */ - had_asterisk = true; - pattern++; - } + for (;;) { + /* If at end of pattern, accept if also at end of string. */ + if (*pattern == '\0') { + return (*s == '\0'); + } - if (had_asterisk) { - /* If at end of pattern, accept immediately. */ - if (!*pattern) - return 1; + /* Skip all the asterisks and adjacent question marks */ + while (*pattern == '*' || (had_asterisk && *pattern == '?')) { + if (*pattern == '*') { + had_asterisk = true; + } + pattern++; + } - /* If next character in pattern is known, optimize. */ - if (*pattern != '?') { + if (had_asterisk) { + /* If at end of pattern, accept immediately. */ + if (!*pattern) + return 1; + + /* If next character in pattern is known, optimize. */ + if (*pattern != '?') { + /* + * Look instances of the next character in + * pattern, and try to match starting from + * those. + */ + for (; *s; s++) + if (*s == *pattern && match_pattern(s + 1, pattern + 1, limit - 1)) { + return 1; + } + /* Failed. */ + return 0; + } + /* + * Move ahead one character at a time and try to + * match at each position. + */ + for (; *s; s++) { + if (match_pattern(s, pattern, limit - 1)) { + return 1; + } + } + /* Failed. */ + return 0; + } /* - * Look instances of the next character in - * pattern, and try to match starting from - * those. + * There must be at least one more character in the string. + * If we are at the end, fail. */ - for (; *s; s++) - if (*s == *pattern && match_pattern(s + 1, pattern + 1, limit - 1)) { - return 1; - } - /* Failed. */ - return 0; - } - /* - * Move ahead one character at a time and try to - * match at each position. - */ - for (; *s; s++) { - if (match_pattern(s, pattern, limit - 1)) { - return 1; + if (!*s) { + return 0; } - } - /* Failed. */ - return 0; - } - /* - * There must be at least one more character in the string. - * If we are at the end, fail. - */ - if (!*s) { - return 0; - } - /* Check if the next character of the string is acceptable. */ - if (*pattern != '?' && *pattern != *s) { - return 0; - } + /* Check if the next character of the string is acceptable. */ + if (*pattern != '?' && *pattern != *s) { + return 0; + } - /* Move to the next character, both in string and in pattern. */ - s++; - pattern++; - } + /* Move to the next character, both in string and in pattern. */ + s++; + pattern++; + } - /* NOTREACHED */ - return 0; + /* NOTREACHED */ + return 0; } /* @@ -128,11 +131,11 @@ static int match_pattern(const char *s, const char *pattern, size_t limit) * no match at all. */ int match_pattern_list(const char *string, const char *pattern, - unsigned int len, int dolower) { + size_t len, int dolower) { char sub[1024]; int negated; int got_positive; - unsigned int i, subi; + size_t i, subi; got_positive = 0; for (i = 0; i < len;) { diff --git a/src/mbedcrypto-compat.h b/src/mbedcrypto-compat.h new file mode 100644 index 00000000..705294a7 --- /dev/null +++ b/src/mbedcrypto-compat.h @@ -0,0 +1,39 @@ +#ifndef MBEDCRYPTO_COMPAT_H +#define MBEDCRYPTO_COMPAT_H + +/* mbedtls/version.h should be available for both v2 and v3 + * v3 defines the version inside build_info.h so if it isn't defined + * in version.h we should have v3 + */ +#include <mbedtls/version.h> +#include <mbedtls/cipher.h> +#ifdef MBEDTLS_VERSION_MAJOR +#if MBEDTLS_VERSION_MAJOR < 3 + +static inline size_t mbedtls_cipher_info_get_key_bitlen( + const mbedtls_cipher_info_t *info) +{ + if (info == NULL) { + return 0; + } + return info->key_bitlen; +} + +static inline size_t mbedtls_cipher_info_get_iv_size( + const mbedtls_cipher_info_t *info) +{ + if (info == NULL) { + return 0; + } + return (size_t)info->iv_size; +} + +#define MBEDTLS_PRIVATE(X) X +#endif /* MBEDTLS_VERSION_MAJOR < 3 */ +#else /* MBEDTLS_VERSION_MAJOR */ +#include <mbedtls/build_info.h> +#if MBEDTLS_VERSION_MAJOR < 3 +#define MBEDTLS_PRIVATE(X) X +#endif /* MBEDTLS_VERSION_MAJOR < 3 */ +#endif /* MBEDTLS_VERSION_MAJOR */ +#endif /* MBEDCRYPTO_COMPAT_H */ diff --git a/src/mbedcrypto_missing.c b/src/mbedcrypto_missing.c index ac0d688c..2c1a8d7a 100644 --- a/src/mbedcrypto_missing.c +++ b/src/mbedcrypto_missing.c @@ -45,7 +45,7 @@ void ssh_mbedcry_bn_free(bignum bn) SAFE_FREE(bn); } -unsigned char *ssh_mbedcry_bn2num(const_bignum num, int radix) +char *ssh_mbedcry_bn2num(const_bignum num, int radix) { char *buf = NULL; size_t olen; @@ -56,7 +56,7 @@ unsigned char *ssh_mbedcry_bn2num(const_bignum num, int radix) return NULL; } - buf = malloc(olen); + buf = mbedtls_calloc(1, olen); if (buf == NULL) { return NULL; } @@ -67,7 +67,7 @@ unsigned char *ssh_mbedcry_bn2num(const_bignum num, int radix) return NULL; } - return (unsigned char *) buf; + return buf; } int ssh_mbedcry_rand(bignum rnd, int bits, int top, int bottom) diff --git a/src/md_crypto.c b/src/md_crypto.c new file mode 100644 index 00000000..f7cda8dd --- /dev/null +++ b/src/md_crypto.c @@ -0,0 +1,324 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libcrypto-compat.h" +#include "libssh/crypto.h" +#include "libssh/wrapper.h" + +#include <openssl/crypto.h> +#include <openssl/evp.h> +#include <openssl/md5.h> +#include <openssl/sha.h> + +SHACTX +sha1_init(void) +{ + int rc; + SHACTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha1(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha1_ctx_free(SHACTX c) +{ + EVP_MD_CTX_free(c); +} + +int +sha1_update(SHACTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha1_final(unsigned char *md, SHACTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha1(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHACTX c = sha1_init(); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha1_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha1_final(hash, c); +} + +SHA256CTX +sha256_init(void) +{ + int rc; + SHA256CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha256(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha256_ctx_free(SHA256CTX c) +{ + EVP_MD_CTX_free(c); +} + +int +sha256_update(SHA256CTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha256_final(unsigned char *md, SHA256CTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha256(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHA256CTX c = sha256_init(); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha256_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha256_final(hash, c); +} + +SHA384CTX +sha384_init(void) +{ + int rc; + SHA384CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha384(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha384_ctx_free(SHA384CTX c) +{ + EVP_MD_CTX_free(c); +} + +int +sha384_update(SHA384CTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha384_final(unsigned char *md, SHA384CTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha384(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHA384CTX c = sha384_init(); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha384_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha384_final(hash, c); +} + +SHA512CTX +sha512_init(void) +{ + int rc = 0; + SHA512CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha512(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha512_ctx_free(SHA512CTX c) +{ + EVP_MD_CTX_free(c); +} + +int +sha512_update(SHA512CTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha512_final(unsigned char *md, SHA512CTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha512(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHA512CTX c = sha512_init(); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha512_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha512_final(hash, c); +} + +MD5CTX +md5_init(void) +{ + int rc; + MD5CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_md5(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +md5_ctx_free(MD5CTX c) +{ + EVP_MD_CTX_free(c); +} + +int +md5_update(MD5CTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +md5_final(unsigned char *md, MD5CTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} diff --git a/src/md_gcrypt.c b/src/md_gcrypt.c new file mode 100644 index 00000000..93c7b0d9 --- /dev/null +++ b/src/md_gcrypt.c @@ -0,0 +1,246 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * Copyright (C) 2016 g10 Code GmbH + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "libssh/wrapper.h" + +#include <gcrypt.h> + +SHACTX +sha1_init(void) +{ + SHACTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA1, 0); + + return ctx; +} + +int +sha1_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +void +sha1_ctx_free(SHACTX c) +{ + gcry_md_close(c); +} + +int +sha1_final(unsigned char *md, SHACTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int +sha1(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA1, hash, digest, len); + return SSH_OK; +} + +SHA256CTX +sha256_init(void) +{ + SHA256CTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA256, 0); + + return ctx; +} + +void +sha256_ctx_free(SHA256CTX c) +{ + gcry_md_close(c); +} + +int +sha256_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +int +sha256_final(unsigned char *md, SHACTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA256_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int +sha256(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA256, hash, digest, len); + return SSH_OK; +} + +SHA384CTX +sha384_init(void) +{ + SHA384CTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA384, 0); + + return ctx; +} + +void +sha384_ctx_free(SHA384CTX c) +{ + gcry_md_close(c); +} + +int +sha384_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +int +sha384_final(unsigned char *md, SHACTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA384_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int +sha384(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA384, hash, digest, len); + return SSH_OK; +} + +SHA512CTX +sha512_init(void) +{ + SHA512CTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA512, 0); + + return ctx; +} + +void +sha512_ctx_free(SHA512CTX c) +{ + gcry_md_close(c); +} + +int +sha512_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +int +sha512_final(unsigned char *md, SHACTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA512_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int +sha512(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA512, hash, digest, len); + return SSH_OK; +} + +MD5CTX +md5_init(void) +{ + MD5CTX c = NULL; + gcry_md_open(&c, GCRY_MD_MD5, 0); + + return c; +} + +void +md5_ctx_free(MD5CTX c) +{ + gcry_md_close(c); +} + +int +md5_update(MD5CTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +int +md5_final(unsigned char *md, MD5CTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, MD5_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} diff --git a/src/md_mbedcrypto.c b/src/md_mbedcrypto.c new file mode 100644 index 00000000..b3529b4b --- /dev/null +++ b/src/md_mbedcrypto.c @@ -0,0 +1,407 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk <juraj.vijtiuk@sartura.hr> + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "libssh/wrapper.h" +#include "mbedcrypto-compat.h" + +#include <mbedtls/md.h> + +SHACTX +sha1_init(void) +{ + SHACTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha1_ctx_free(SHACTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +sha1_update(SHACTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha1_final(unsigned char *md, SHACTX c) +{ + int rc = mbedtls_md_finish(c, md); + sha1_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha1(const unsigned char *digest, size_t len, unsigned char *hash) +{ + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + int rc; + + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +SHA256CTX +sha256_init(void) +{ + SHA256CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha256_ctx_free(SHA256CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +sha256_update(SHA256CTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha256_final(unsigned char *md, SHA256CTX c) +{ + int rc = mbedtls_md_finish(c, md); + mbedtls_md_free(c); + SAFE_FREE(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha256(const unsigned char *digest, size_t len, unsigned char *hash) +{ + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +SHA384CTX +sha384_init(void) +{ + SHA384CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); + + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha384_ctx_free(SHA384CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +sha384_update(SHA384CTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha384_final(unsigned char *md, SHA384CTX c) +{ + int rc = mbedtls_md_finish(c, md); + sha384_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha384(const unsigned char *digest, size_t len, unsigned char *hash) +{ + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); + int rc; + + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +SHA512CTX +sha512_init(void) +{ + SHA512CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha512_ctx_free(SHA512CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +sha512_update(SHA512CTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha512_final(unsigned char *md, SHA512CTX c) +{ + int rc = mbedtls_md_finish(c, md); + sha512_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha512(const unsigned char *digest, size_t len, unsigned char *hash) +{ + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + int rc; + + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +MD5CTX +md5_init(void) +{ + MD5CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_MD5); + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +md5_ctx_free(MD5CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +md5_update(MD5CTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +md5_final(unsigned char *md, MD5CTX c) +{ + int rc = mbedtls_md_finish(c, md); + mbedtls_md_free(c); + SAFE_FREE(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} diff --git a/src/messages.c b/src/messages.c index c7fcc887..be9462ac 100644 --- a/src/messages.c +++ b/src/messages.c @@ -54,7 +54,7 @@ * This file contains the message parsing utilities for client and server * programs using libssh. * - * On the server the the main loop of the program will call + * On the server the main loop of the program will call * ssh_message_get(session) to get messages as they come. They are not 1-1 with * the protocol messages. Then, the user will know what kind of a message it is * and use the appropriate functions to handle it (or use the default handlers @@ -79,7 +79,7 @@ static ssh_message ssh_message_new(ssh_session session) #ifndef WITH_SERVER -/* Reduced version of the reply default that only reply with +/* Reduced version of the reply default that only replies with * SSH_MSG_UNIMPLEMENTED */ static int ssh_message_reply_default(ssh_message msg) { @@ -160,7 +160,7 @@ static int ssh_execute_server_request(ssh_session session, ssh_message msg) if (channel != NULL) { rc = ssh_message_channel_request_open_reply_accept_channel(msg, channel); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_TRACE, "Failed to send reply for accepting a channel " "open"); } @@ -237,7 +237,7 @@ static int ssh_execute_server_request(ssh_session session, ssh_message msg) msg->channel_request.pxwidth, msg->channel_request.pxheight); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_TRACE, "Failed to iterate callbacks for window change"); } return SSH_OK; @@ -317,6 +317,17 @@ static int ssh_execute_server_request(ssh_session session, ssh_message msg) return SSH_AGAIN; } +static int ssh_reply_channel_open_request(ssh_message msg, ssh_channel channel) +{ + if (channel != NULL) { + return ssh_message_channel_request_open_reply_accept_channel(msg, channel); + } + + ssh_message_reply_default(msg); + + return SSH_OK; +} + static int ssh_execute_client_request(ssh_session session, ssh_message msg) { ssh_channel channel = NULL; @@ -329,30 +340,26 @@ static int ssh_execute_client_request(ssh_session session, ssh_message msg) msg->channel_request_open.originator, msg->channel_request_open.originator_port, session->common.callbacks->userdata); - if (channel != NULL) { - rc = ssh_message_channel_request_open_reply_accept_channel(msg, channel); - - return rc; - } else { - ssh_message_reply_default(msg); - } - return SSH_OK; + return ssh_reply_channel_open_request(msg, channel); } else if (msg->type == SSH_REQUEST_CHANNEL_OPEN && msg->channel_request_open.type == SSH_CHANNEL_AUTH_AGENT && ssh_callbacks_exists(session->common.callbacks, channel_open_request_auth_agent_function)) { channel = session->common.callbacks->channel_open_request_auth_agent_function (session, session->common.callbacks->userdata); - if (channel != NULL) { - rc = ssh_message_channel_request_open_reply_accept_channel(msg, channel); - - return rc; - } else { - ssh_message_reply_default(msg); - } + return ssh_reply_channel_open_request(msg, channel); + } else if (msg->type == SSH_REQUEST_CHANNEL_OPEN + && msg->channel_request_open.type == SSH_CHANNEL_FORWARDED_TCPIP + && ssh_callbacks_exists(session->common.callbacks, channel_open_request_forwarded_tcpip_function)) { + channel = session->common.callbacks->channel_open_request_forwarded_tcpip_function(session, + msg->channel_request_open.destination, + msg->channel_request_open.destination_port, + msg->channel_request_open.originator, + msg->channel_request_open.originator_port, + session->common.callbacks->userdata); - return SSH_OK; + return ssh_reply_channel_open_request(msg, channel); } return rc; @@ -593,6 +600,7 @@ void ssh_message_free(ssh_message msg){ switch(msg->type) { case SSH_REQUEST_AUTH: SAFE_FREE(msg->auth_request.username); + SAFE_FREE(msg->auth_request.sigtype); if (msg->auth_request.password) { explicit_bzero(msg->auth_request.password, strlen(msg->auth_request.password)); @@ -714,8 +722,8 @@ static ssh_buffer ssh_msg_userauth_build_digest(ssh_session session, rc = ssh_buffer_pack(buffer, "dPbsssbsS", - crypto->digest_len, /* session ID string */ - (size_t)crypto->digest_len, crypto->session_id, + crypto->session_id_len, /* session ID string */ + crypto->session_id_len, crypto->session_id, SSH2_MSG_USERAUTH_REQUEST, /* type */ msg->auth_request.username, service, @@ -774,7 +782,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_request){ cmp = strcmp(service, "ssh-connection"); if (cmp != 0) { - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_TRACE, "Invalid service request: %s", service); goto end; @@ -852,6 +860,14 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_request){ goto error; } msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_NONE; + msg->auth_request.sigtype = strdup(ssh_string_get_char(algo)); + if (msg->auth_request.sigtype == NULL) { + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_ERROR; + SSH_STRING_FREE(algo); + algo = NULL; + goto error; + } + // has a valid signature ? if(has_sign) { ssh_string sig_blob = NULL; @@ -1046,7 +1062,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ } if (session->kbdint == NULL) { - SSH_LOG(SSH_LOG_PROTOCOL, "Warning: Got a keyboard-interactive " + SSH_LOG(SSH_LOG_DEBUG, "Warning: Got a keyboard-interactive " "response but it seems we didn't send the request."); session->kbdint = ssh_kbdint_new(); @@ -1067,10 +1083,10 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ session->kbdint->nanswers = 0; } - SSH_LOG(SSH_LOG_PACKET,"kbdint: %d answers",nanswers); + SSH_LOG(SSH_LOG_PACKET,"kbdint: %" PRIu32 " answers", nanswers); if (nanswers > KBDINT_MAX_PROMPT) { ssh_set_error(session, SSH_FATAL, - "Too much answers received from client: %u (0x%.4x)", + "Too much answers received from client: %" PRIu32 " (0x%.4" PRIx32 ")", nanswers, nanswers); ssh_kbdint_free(session->kbdint); session->kbdint = NULL; @@ -1080,8 +1096,8 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ if(nanswers != session->kbdint->nprompts) { /* warn but let the application handle this case */ - SSH_LOG(SSH_LOG_PROTOCOL, "Warning: Number of prompts and answers" - " mismatch: p=%u a=%u", session->kbdint->nprompts, nanswers); + SSH_LOG(SSH_LOG_DEBUG, "Warning: Number of prompts and answers" + " mismatch: p=%" PRIu32 " a=%" PRIu32, session->kbdint->nprompts, nanswers); } session->kbdint->nanswers = nanswers; @@ -1160,8 +1176,16 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open){ ssh_set_error(session,SSH_FATAL, "Invalid state when receiving channel open request (must be authenticated)"); goto error; } - - if (strcmp(type_c,"session") == 0) { + + if (strcmp(type_c, "session") == 0) { + if (session->flags & SSH_SESSION_FLAG_NO_MORE_SESSIONS) { + ssh_session_set_disconnect_message(session, "No more sessions allowed!"); + ssh_set_error(session, SSH_FATAL, "No more sessions allowed!"); + session->session_state = SSH_SESSION_STATE_ERROR; + ssh_disconnect(session); + goto error; + } + msg->channel_request_open.type = SSH_CHANNEL_SESSION; SAFE_FREE(type_c); goto end; @@ -1174,9 +1198,9 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open){ &destination_port, &msg->channel_request_open.originator, &originator_port); - if (rc != SSH_OK) { - goto error; - } + if (rc != SSH_OK) { + goto error; + } msg->channel_request_open.destination_port = (uint16_t) destination_port; msg->channel_request_open.originator_port = (uint16_t) originator_port; @@ -1239,7 +1263,7 @@ end: * * @param[in] chan The channel the request is made on. * - * @returns SSH_OK on success, SSH_ERROR if an error occured. + * @returns SSH_OK on success, SSH_ERROR if an error occurred. */ int ssh_message_channel_request_open_reply_accept_channel(ssh_message msg, ssh_channel chan) { ssh_session session; @@ -1273,7 +1297,7 @@ int ssh_message_channel_request_open_reply_accept_channel(ssh_message msg, ssh_c } SSH_LOG(SSH_LOG_PACKET, - "Accepting a channel request_open for chan %d", + "Accepting a channel request_open for chan %" PRIu32, chan->remote_channel); rc = ssh_packet_send(session); @@ -1330,7 +1354,7 @@ ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg) { * * @param[in] want_reply The want_reply field from the request. * - * @returns SSH_OK on success, SSH_ERROR if an error occured. + * @returns SSH_OK on success, SSH_ERROR if an error occurred. */ int ssh_message_handle_channel_request(ssh_session session, ssh_channel channel, ssh_buffer packet, const char *request, uint8_t want_reply) { @@ -1344,7 +1368,7 @@ int ssh_message_handle_channel_request(ssh_session session, ssh_channel channel, } SSH_LOG(SSH_LOG_PACKET, - "Received a %s channel_request for channel (%d:%d) (want_reply=%hhd)", + "Received a %s channel_request for channel (%" PRIu32 ":%" PRIu32 ") (want_reply=%hhd)", request, channel->local_channel, channel->remote_channel, want_reply); msg->type = SSH_REQUEST_CHANNEL; @@ -1444,6 +1468,14 @@ error: return SSH_ERROR; } +/** @internal + * + * @brief Sends a successful channel request reply + * + * @param msg A message to reply to + * + * @returns SSH_OK on success, SSH_ERROR if an error occurred. + */ int ssh_message_channel_request_reply_success(ssh_message msg) { uint32_t channel; int rc; @@ -1456,7 +1488,7 @@ int ssh_message_channel_request_reply_success(ssh_message msg) { channel = msg->channel_request.channel->remote_channel; SSH_LOG(SSH_LOG_PACKET, - "Sending a channel_request success to channel %d", channel); + "Sending a channel_request success to channel %" PRIu32, channel); rc = ssh_buffer_pack(msg->session->out_buffer, "bd", @@ -1487,7 +1519,7 @@ SSH_PACKET_CALLBACK(ssh_packet_global_request){ (void)type; (void)packet; - SSH_LOG(SSH_LOG_PROTOCOL,"Received SSH_MSG_GLOBAL_REQUEST packet"); + SSH_LOG(SSH_LOG_DEBUG,"Received SSH_MSG_GLOBAL_REQUEST packet"); r = ssh_buffer_unpack(packet, "sb", &request, &want_reply); @@ -1519,12 +1551,12 @@ SSH_PACKET_CALLBACK(ssh_packet_global_request){ msg->global_request.type = SSH_GLOBAL_REQUEST_TCPIP_FORWARD; msg->global_request.want_reply = want_reply; - SSH_LOG(SSH_LOG_PROTOCOL, "Received SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, want_reply, + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, want_reply, msg->global_request.bind_address, msg->global_request.bind_port); if(ssh_callbacks_exists(session->common.callbacks, global_request_function)) { - SSH_LOG(SSH_LOG_PROTOCOL, "Calling callback for SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, + SSH_LOG(SSH_LOG_DEBUG, "Calling callback for SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, want_reply, msg->global_request.bind_address, msg->global_request.bind_port); session->common.callbacks->global_request_function(session, msg, session->common.callbacks->userdata); @@ -1549,7 +1581,7 @@ SSH_PACKET_CALLBACK(ssh_packet_global_request){ msg->global_request.type = SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD; msg->global_request.want_reply = want_reply; - SSH_LOG(SSH_LOG_PROTOCOL, "Received SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, want_reply, + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, want_reply, msg->global_request.bind_address, msg->global_request.bind_port); @@ -1563,14 +1595,23 @@ SSH_PACKET_CALLBACK(ssh_packet_global_request){ } else if(strcmp(request, "keepalive@openssh.com") == 0) { msg->global_request.type = SSH_GLOBAL_REQUEST_KEEPALIVE; msg->global_request.want_reply = want_reply; - SSH_LOG(SSH_LOG_PROTOCOL, "Received keepalive@openssh.com %d", want_reply); + SSH_LOG(SSH_LOG_DEBUG, "Received keepalive@openssh.com %d", want_reply); if(ssh_callbacks_exists(session->common.callbacks, global_request_function)) { session->common.callbacks->global_request_function(session, msg, session->common.callbacks->userdata); } else { ssh_message_global_request_reply_success(msg, 0); } + } else if (strcmp(request, "no-more-sessions@openssh.com") == 0) { + msg->global_request.type = SSH_GLOBAL_REQUEST_NO_MORE_SESSIONS; + msg->global_request.want_reply = want_reply; + + SSH_LOG(SSH_LOG_PROTOCOL, "Received no-more-sessions@openssh.com %d", want_reply); + + ssh_message_global_request_reply_success(msg, 0); + + session->flags |= SSH_SESSION_FLAG_NO_MORE_SESSIONS; } else { - SSH_LOG(SSH_LOG_PROTOCOL, "UNKNOWN SSH_MSG_GLOBAL_REQUEST %s, " + SSH_LOG(SSH_LOG_DEBUG, "UNKNOWN SSH_MSG_GLOBAL_REQUEST %s, " "want_reply = %d", request, want_reply); goto reply_with_failure; } @@ -1603,7 +1644,7 @@ reply_with_failure: error: SAFE_FREE(msg); SAFE_FREE(request); - SSH_LOG(SSH_LOG_WARNING, "Invalid SSH_MSG_GLOBAL_REQUEST packet"); + SSH_LOG(SSH_LOG_TRACE, "Invalid SSH_MSG_GLOBAL_REQUEST packet"); return rc; } @@ -32,6 +32,7 @@ #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> +#include <net/if.h> #endif /* _WIN32 */ @@ -59,6 +60,7 @@ #include <ws2tcpip.h> #include <shlobj.h> #include <direct.h> +#include <netioapi.h> #ifdef HAVE_IO_H #include <io.h> @@ -94,8 +96,10 @@ #define ZLIB_STRING "" #endif +#define ARPA_DOMAIN_MAX_LEN 63 + /** - * @defgroup libssh_misc The SSH helper functions. + * @defgroup libssh_misc The SSH helper functions * @ingroup libssh * * Different helper functions used in the SSH Library. @@ -104,7 +108,8 @@ */ #ifdef _WIN32 -char *ssh_get_user_home_dir(void) { +char *ssh_get_user_home_dir(void) +{ char tmp[PATH_MAX] = {0}; char *szPath = NULL; @@ -122,12 +127,13 @@ char *ssh_get_user_home_dir(void) { } /* we have read access on file */ -int ssh_file_readaccess_ok(const char *file) { - if (_access(file, 4) < 0) { - return 0; - } +int ssh_file_readaccess_ok(const char *file) +{ + if (_access(file, 4) < 0) { + return 0; + } - return 1; + return 1; } /** @@ -158,7 +164,8 @@ int ssh_dir_writeable(const char *path) #define SSH_USEC_IN_SEC 1000000LL #define SSH_SECONDS_SINCE_1601 11644473600LL -int gettimeofday(struct timeval *__p, void *__t) { +int ssh_gettimeofday(struct timeval *__p, void *__t) +{ union { unsigned long long ns100; /* time since 1 Jan 1601 in 100ns units */ FILETIME ft; @@ -171,26 +178,34 @@ int gettimeofday(struct timeval *__p, void *__t) { return (0); } -char *ssh_get_local_username(void) { +char *ssh_get_local_username(void) +{ DWORD size = 0; - char *user; + char *user = NULL; + int rc; /* get the size */ GetUserName(NULL, &size); - user = (char *) malloc(size); + user = (char *)malloc(size); if (user == NULL) { return NULL; } if (GetUserName(user, &size)) { - return user; + rc = ssh_check_username_syntax(user); + if (rc == SSH_OK) { + return user; + } } + free(user); + return NULL; } -int ssh_is_ipaddr_v4(const char *str) { +int ssh_is_ipaddr_v4(const char *str) +{ struct sockaddr_storage ss; int sslen = sizeof(ss); int rc = SOCKET_ERROR; @@ -212,24 +227,40 @@ int ssh_is_ipaddr_v4(const char *str) { return 0; } -int ssh_is_ipaddr(const char *str) { +int ssh_is_ipaddr(const char *str) +{ int rc = SOCKET_ERROR; + char *s = strdup(str); - if (strchr(str, ':')) { + if (s == NULL) { + return -1; + } + if (strchr(s, ':')) { struct sockaddr_storage ss; int sslen = sizeof(ss); - - /* TODO link-local (IP:v6:addr%ifname). */ - rc = WSAStringToAddressA((LPSTR) str, + char *network_interface = strchr(s, '%'); + + /* link-local (IP:v6:addr%ifname). */ + if (network_interface != NULL) { + rc = if_nametoindex(network_interface + 1); + if (rc == 0) { + free(s); + return 0; + } + *network_interface = '\0'; + } + rc = WSAStringToAddressA((LPSTR) s, AF_INET6, NULL, (struct sockaddr*)&ss, &sslen); if (rc == 0) { + free(s); return 1; } } + free(s); return ssh_is_ipaddr_v4(str); } #else /* _WIN32 */ @@ -302,7 +333,7 @@ char *ssh_get_local_username(void) struct passwd pwd; struct passwd *pwdbuf = NULL; char buf[NSS_BUFLEN_PASSWD]; - char *name; + char *name = NULL; int rc; rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf); @@ -311,15 +342,18 @@ char *ssh_get_local_username(void) } name = strdup(pwd.pw_name); + rc = ssh_check_username_syntax(name); - if (name == NULL) { + if (rc != SSH_OK) { + free(name); return NULL; } return name; } -int ssh_is_ipaddr_v4(const char *str) { +int ssh_is_ipaddr_v4(const char *str) +{ int rc = -1; struct in_addr dest; @@ -331,25 +365,42 @@ int ssh_is_ipaddr_v4(const char *str) { return 0; } -int ssh_is_ipaddr(const char *str) { +int ssh_is_ipaddr(const char *str) +{ int rc = -1; + char *s = strdup(str); - if (strchr(str, ':')) { + if (s == NULL) { + return -1; + } + if (strchr(s, ':')) { struct in6_addr dest6; - - /* TODO link-local (IP:v6:addr%ifname). */ - rc = inet_pton(AF_INET6, str, &dest6); + char *network_interface = strchr(s, '%'); + + /* link-local (IP:v6:addr%ifname). */ + if (network_interface != NULL) { + rc = if_nametoindex(network_interface + 1); + if (rc == 0) { + free(s); + return 0; + } + *network_interface = '\0'; + } + rc = inet_pton(AF_INET6, s, &dest6); if (rc > 0) { + free(s); return 1; } } + free(s); return ssh_is_ipaddr_v4(str); } #endif /* _WIN32 */ -char *ssh_lowercase(const char* str) { +char *ssh_lowercase(const char* str) +{ char *new, *p; if (str == NULL) { @@ -392,15 +443,17 @@ char *ssh_hostport(const char *host, int port) * @brief Convert a buffer into a colon separated hex string. * The caller has to free the memory. * - * @param what What should be converted to a hex string. + * @param[in] what What should be converted to a hex string. * - * @param len Length of the buffer to convert. + * @param[in] len Length of the buffer to convert. * - * @return The hex string or NULL on error. + * @return The hex string or NULL on error. The memory needs + * to be freed using ssh_string_free_char(). * * @see ssh_string_free_char() */ -char *ssh_get_hexa(const unsigned char *what, size_t len) { +char *ssh_get_hexa(const unsigned char *what, size_t len) +{ const char h[] = "0123456789abcdef"; char *hexa; size_t i; @@ -428,7 +481,8 @@ char *ssh_get_hexa(const unsigned char *what, size_t len) { /** * @deprecated Please use ssh_print_hash() instead */ -void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len) { +void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len) +{ char *hexa = ssh_get_hexa(what, len); if (hexa == NULL) { @@ -455,7 +509,7 @@ void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len) { * " 00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ................" * * The value for each byte as corresponding ASCII character is printed at the - * end if the value is printable. Otherwise it is replace with '.'. + * end if the value is printable. Otherwise, it is replaced with '.'. * * @param[in] descr A description for the content to be logged * @param[in] what The buffer to be logged @@ -622,7 +676,7 @@ void ssh_log_hexdump(const char *descr, const unsigned char *what, size_t len) return; error: - SSH_LOG(SSH_LOG_WARN, "Could not print to buffer"); + SSH_LOG(SSH_LOG_DEBUG, "Could not print to buffer"); return; } @@ -649,48 +703,55 @@ error: * } * @endcode */ -const char *ssh_version(int req_version) { - if (req_version <= LIBSSH_VERSION_INT) { - return SSH_STRINGIFY(LIBSSH_VERSION) GCRYPT_STRING CRYPTO_STRING MBED_STRING - ZLIB_STRING; - } +const char *ssh_version(int req_version) +{ + if (req_version <= LIBSSH_VERSION_INT) { + return SSH_STRINGIFY(LIBSSH_VERSION) GCRYPT_STRING CRYPTO_STRING + MBED_STRING ZLIB_STRING; + } - return NULL; + return NULL; } -struct ssh_list *ssh_list_new(void) { - struct ssh_list *ret=malloc(sizeof(struct ssh_list)); - if(!ret) - return NULL; - ret->root=ret->end=NULL; - return ret; +struct ssh_list *ssh_list_new(void) +{ + struct ssh_list *ret = malloc(sizeof(struct ssh_list)); + if (ret == NULL) { + return NULL; + } + ret->root = ret->end = NULL; + return ret; } -void ssh_list_free(struct ssh_list *list){ - struct ssh_iterator *ptr,*next; - if(!list) - return; - ptr=list->root; - while(ptr){ - next=ptr->next; - SAFE_FREE(ptr); - ptr=next; - } - SAFE_FREE(list); +void ssh_list_free(struct ssh_list *list) +{ + struct ssh_iterator *ptr, *next; + if (!list) + return; + ptr = list->root; + while (ptr) { + next = ptr->next; + SAFE_FREE(ptr); + ptr = next; + } + SAFE_FREE(list); } -struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list){ - if(!list) - return NULL; - return list->root; +struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list) +{ + if (!list) + return NULL; + return list->root; } -struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value){ - struct ssh_iterator *it; - for(it = ssh_list_get_iterator(list); it != NULL ;it=it->next) - if(it->data==value) - return it; - return NULL; +struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value) +{ + struct ssh_iterator *it; + + for (it = ssh_list_get_iterator(list); it != NULL ; it = it->next) + if (it->data == value) + return it; + return NULL; } /** @@ -703,7 +764,7 @@ struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value){ size_t ssh_list_count(const struct ssh_list *list) { struct ssh_iterator *it = NULL; - int count = 0; + size_t count = 0; for (it = ssh_list_get_iterator(list); it != NULL ; it = it->next) { count++; @@ -712,16 +773,20 @@ size_t ssh_list_count(const struct ssh_list *list) return count; } -static struct ssh_iterator *ssh_iterator_new(const void *data){ - struct ssh_iterator *iterator=malloc(sizeof(struct ssh_iterator)); - if(!iterator) - return NULL; - iterator->next=NULL; - iterator->data=data; - return iterator; +static struct ssh_iterator *ssh_iterator_new(const void *data) +{ + struct ssh_iterator *iterator = malloc(sizeof(struct ssh_iterator)); + + if (iterator == NULL) { + return NULL; + } + iterator->next = NULL; + iterator->data = data; + return iterator; } -int ssh_list_append(struct ssh_list *list,const void *data){ +int ssh_list_append(struct ssh_list *list,const void *data) +{ struct ssh_iterator *iterator = NULL; if (list == NULL) { @@ -744,7 +809,8 @@ int ssh_list_append(struct ssh_list *list,const void *data){ return SSH_OK; } -int ssh_list_prepend(struct ssh_list *list, const void *data){ +int ssh_list_prepend(struct ssh_list *list, const void *data) +{ struct ssh_iterator *it = NULL; if (list == NULL) { @@ -768,8 +834,9 @@ int ssh_list_prepend(struct ssh_list *list, const void *data){ return SSH_OK; } -void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator){ - struct ssh_iterator *ptr,*prev; +void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator) +{ + struct ssh_iterator *ptr, *prev; if (list == NULL) { return; @@ -803,12 +870,13 @@ void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator){ * @brief Removes the top element of the list and returns the data value * attached to it. * - * @param[in[ list The ssh_list to remove the element. + * @param[in] list The ssh_list to remove the element. * * @returns A pointer to the element being stored in head, or NULL * if the list is empty. */ -const void *_ssh_list_pop_head(struct ssh_list *list){ +const void *_ssh_list_pop_head(struct ssh_list *list) +{ struct ssh_iterator *iterator = NULL; const void *data = NULL; @@ -834,17 +902,21 @@ const void *_ssh_list_pop_head(struct ssh_list *list){ * dirname breaks a null-terminated pathname string into a directory component. * In the usual case, ssh_dirname() returns the string up to, but not including, * the final '/'. Trailing '/' characters are not counted as part of the - * pathname. The caller must free the memory. + * pathname. The caller must free the memory using ssh_string_free_char(). * * @param[in] path The path to parse. * * @return The dirname of path or NULL if we can't allocate memory. * If path does not contain a slash, c_dirname() returns - * the string ".". If path is the string "/", it returns + * the string ".". If path is a string "/", it returns * the string "/". If path is NULL or an empty string, - * "." is returned. + * "." is returned. The memory needs to be freed using + * ssh_string_free_char(). + * + * @see ssh_string_free_char() */ -char *ssh_dirname (const char *path) { +char *ssh_dirname (const char *path) +{ char *new = NULL; size_t len; @@ -895,11 +967,15 @@ char *ssh_dirname (const char *path) { * @param[in] path The path to parse. * * @return The filename of path or NULL if we can't allocate - * memory. If path is a the string "/", basename returns + * memory. If path is the string "/", basename returns * the string "/". If path is NULL or an empty string, - * "." is returned. + * "." is returned. The caller needs to free this memory + * ssh_string_free_char(). + * + * @see ssh_string_free_char() */ -char *ssh_basename (const char *path) { +char *ssh_basename (const char *path) +{ char *new = NULL; const char *s; size_t len; @@ -1032,9 +1108,13 @@ int ssh_mkdirs(const char *pathname, mode_t mode) * * @param[in] d The directory to expand. * - * @return The expanded directory, NULL on error. + * @return The expanded directory, NULL on error. The caller + * needs to free the memory using ssh_string_free_char(). + * + * @see ssh_string_free_char() */ -char *ssh_path_expand_tilde(const char *d) { +char *ssh_path_expand_tilde(const char *d) +{ char *h = NULL, *r; const char *p; size_t ld; @@ -1101,10 +1181,14 @@ char *ssh_path_expand_tilde(const char *d) { * %l local hostname * %r remote username * %p remote port - * @returns Expanded string. + * @returns Expanded string. The caller needs to free the memory using + * ssh_string_free_char(). + * + * @see ssh_string_free_char() */ -char *ssh_path_expand_escape(ssh_session session, const char *s) { - char host[NI_MAXHOST]; +char *ssh_path_expand_escape(ssh_session session, const char *s) +{ + char host[NI_MAXHOST] = {0}; char *buf = NULL; char *r = NULL; char *x = NULL; @@ -1156,7 +1240,15 @@ char *ssh_path_expand_escape(ssh_session session, const char *s) { case '%': goto escape; case 'd': - x = strdup(session->opts.sshdir); + if (session->opts.sshdir) { + x = strdup(session->opts.sshdir); + } else { + ssh_set_error(session, SSH_FATAL, + "Cannot expand sshdir"); + free(buf); + free(r); + return NULL; + } break; case 'u': x = ssh_get_local_username(); @@ -1167,20 +1259,35 @@ char *ssh_path_expand_escape(ssh_session session, const char *s) { } break; case 'h': - x = strdup(session->opts.host); + if (session->opts.host) { + x = strdup(session->opts.host); + } else { + ssh_set_error(session, SSH_FATAL, + "Cannot expand host"); + free(buf); + free(r); + return NULL; + } break; case 'r': - x = strdup(session->opts.username); + if (session->opts.username) { + x = strdup(session->opts.username); + } else { + ssh_set_error(session, SSH_FATAL, + "Cannot expand username"); + free(buf); + free(r); + return NULL; + } break; case 'p': - if (session->opts.port < 65536) { - char tmp[6]; - - snprintf(tmp, - sizeof(tmp), - "%u", - session->opts.port > 0 ? session->opts.port : 22); - x = strdup(tmp); + { + char tmp[6]; + + snprintf(tmp, sizeof(tmp), "%hu", + (uint16_t)(session->opts.port > 0 ? session->opts.port + : 22)); + x = strdup(tmp); } break; default: @@ -1267,7 +1374,7 @@ int ssh_analyze_banner(ssh_session session, int server) return -1; } - SSH_LOG(SSH_LOG_PROTOCOL, "Analyzing banner: %s", banner); + SSH_LOG(SSH_LOG_DEBUG, "Analyzing banner: %s", banner); switch (banner[4]) { case '2': @@ -1297,27 +1404,31 @@ int ssh_analyze_banner(ssh_session session, int server) * 012345678901234567890 */ if (strlen(openssh) > 9) { + errno = 0; major = strtoul(openssh + 8, &tmp, 10); if ((tmp == (openssh + 8)) || ((errno == ERANGE) && (major == ULONG_MAX)) || ((errno != 0) && (major == 0)) || ((major < 1) || (major > 100))) { /* invalid major */ + errno = 0; goto done; } + errno = 0; minor = strtoul(openssh + 10, &tmp, 10); if ((tmp == (openssh + 10)) || ((errno == ERANGE) && (major == ULONG_MAX)) || ((errno != 0) && (major == 0)) || (minor > 100)) { /* invalid minor */ + errno = 0; goto done; } session->openssh = SSH_VERSION_INT(((int) major), ((int) minor), 0); - SSH_LOG(SSH_LOG_PROTOCOL, + SSH_LOG(SSH_LOG_DEBUG, "We are talking to an OpenSSH %s version: %lu.%lu (%x)", server ? "client" : "server", major, minor, session->openssh); @@ -1340,7 +1451,8 @@ done: * @brief initializes a timestamp to the current time * @param[out] ts pointer to an allocated ssh_timestamp structure */ -void ssh_timestamp_init(struct ssh_timestamp *ts){ +void ssh_timestamp_init(struct ssh_timestamp *ts) +{ #ifdef HAVE_CLOCK_GETTIME struct timespec tp; clock_gettime(CLOCK, &tp); @@ -1363,17 +1475,18 @@ void ssh_timestamp_init(struct ssh_timestamp *ts){ * @returns difference in milliseconds */ -static int ssh_timestamp_difference(struct ssh_timestamp *old, - struct ssh_timestamp *new){ - long seconds, usecs, msecs; - seconds = new->seconds - old->seconds; - usecs = new->useconds - old->useconds; - if (usecs < 0){ - seconds--; - usecs += 1000000; - } - msecs = seconds * 1000 + usecs/1000; - return msecs; +static int +ssh_timestamp_difference(struct ssh_timestamp *old, struct ssh_timestamp *new) +{ + long seconds, usecs, msecs; + seconds = new->seconds - old->seconds; + usecs = new->useconds - old->useconds; + if (usecs < 0){ + seconds--; + usecs += 1000000; + } + msecs = seconds * 1000 + usecs/1000; + return msecs; } /** @@ -1384,14 +1497,20 @@ static int ssh_timestamp_difference(struct ssh_timestamp *old, * @param[in] usec number of microseconds * @returns milliseconds, or 10000 if user supplied values are equal to zero */ -int ssh_make_milliseconds(long sec, long usec) { - int res = usec ? (usec / 1000) : 0; +int ssh_make_milliseconds(unsigned long sec, unsigned long usec) +{ + unsigned long res = usec ? (usec / 1000) : 0; res += (sec * 1000); if (res == 0) { res = 10 * 1000; /* use a reasonable default value in case * SSH_OPTIONS_TIMEOUT is not set in options. */ } - return res; + + if (res > INT_MAX) { + return SSH_TIMEOUT_INFINITE; + } else { + return (int)res; + } } /** @@ -1404,7 +1523,8 @@ int ssh_make_milliseconds(long sec, long usec) { * @returns 1 if timeout is elapsed * 0 otherwise */ -int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout) { +int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout) +{ struct ssh_timestamp now; switch(timeout) { @@ -1412,7 +1532,7 @@ int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout) { * -2 means user-defined timeout as available in * session->timeout, session->timeout_usec. */ - SSH_LOG(SSH_LOG_WARN, "ssh_timeout_elapsed called with -2. this needs to " + SSH_LOG(SSH_LOG_DEBUG, "ssh_timeout_elapsed called with -2. this needs to " "be fixed. please set a breakpoint on misc.c:%d and " "fix the caller\n", __LINE__); return 0; @@ -1436,7 +1556,8 @@ int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout) { * timeout * @returns remaining time in milliseconds, 0 if elapsed, -1 if never. */ -int ssh_timeout_update(struct ssh_timestamp *ts, int timeout){ +int ssh_timeout_update(struct ssh_timestamp *ts, int timeout) +{ struct ssh_timestamp now; int ms, ret; if (timeout <= 0) { @@ -1565,20 +1686,20 @@ int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len) enum ssh_quote_state_e state = NO_QUOTE; if (file_name == NULL || buf == NULL || buf_len == 0) { - SSH_LOG(SSH_LOG_WARNING, "Invalid parameter"); + SSH_LOG(SSH_LOG_TRACE, "Invalid parameter"); return SSH_ERROR; } /* Only allow file names smaller than 32kb. */ if (strlen(file_name) > 32 * 1024) { - SSH_LOG(SSH_LOG_WARNING, "File name too long"); + SSH_LOG(SSH_LOG_TRACE, "File name too long"); return SSH_ERROR; } /* Paranoia check */ required_buf_len = (size_t)3 * strlen(file_name) + 1; if (required_buf_len > buf_len) { - SSH_LOG(SSH_LOG_WARNING, "Buffer too small"); + SSH_LOG(SSH_LOG_TRACE, "Buffer too small"); return SSH_ERROR; } @@ -1629,13 +1750,13 @@ int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len) *dst++ = '\\'; break; case SINGLE_QUOTE: - /* Close the current quoted string and replace '!' for unquoted + /* Close the currently quoted string and replace '!' for unquoted * "\!" */ *dst++ = '\''; *dst++ = '\\'; break; case DOUBLE_QUOTE: - /* Close current quoted string and replace "!" for unquoted + /* Close currently quoted string and replace "!" for unquoted * "\!" */ *dst++ = '"'; *dst++ = '\\'; @@ -1736,7 +1857,7 @@ int ssh_newline_vis(const char *string, char *buf, size_t buf_len) } if ((2 * strlen(string) + 1) > buf_len) { - SSH_LOG(SSH_LOG_WARNING, "Buffer too small"); + SSH_LOG(SSH_LOG_TRACE, "Buffer too small"); return SSH_ERROR; } @@ -1759,23 +1880,23 @@ int ssh_newline_vis(const char *string, char *buf, size_t buf_len) * * @brief Replaces the last 6 characters of a string from 'X' to 6 random hexdigits. * - * @param[in] template Any input string with last 6 characters as 'X'. + * @param[in,out] name Any input string with last 6 characters as 'X'. * @returns -1 as error when the last 6 characters of the input to be replaced are not 'X' * 0 otherwise. */ -int ssh_tmpname(char *template) +int ssh_tmpname(char *name) { char *tmp = NULL; size_t i = 0; int rc = 0; uint8_t random[6]; - if (template == NULL) { + if (name == NULL) { goto err; } - tmp = template + strlen(template) - 6; - if (tmp < template) { + tmp = name + strlen(name) - 6; + if (tmp < name) { goto err; } @@ -1811,15 +1932,19 @@ err: /** * @internal * - * @brief Finds the first occurence of a patterm in a string and replaces it. + * @brief Finds the first occurrence of a pattern in a string and replaces it. * - * @param[in] src Source string containing the patern to be replaced. + * @param[in] src Source string containing the pattern to be replaced. * @param[in] pattern Pattern to be replaced in the source string. - * Note: this function replaces the first occurence of pattern only. + * Note: this function replaces the first occurrence of + * pattern only. * @param[in] replace String to be replaced is stored in replace. * * @returns src_replaced a pointer that points to the replaced string. - * NULL if allocation fails or if src is NULL. + * NULL if allocation fails or if src is NULL. The returned memory needs to be + * freed using ssh_string_free_char(). + * + * @see ssh_string_free_char() */ char *ssh_strreplace(const char *src, const char *pattern, const char *replace) { @@ -1859,4 +1984,247 @@ char *ssh_strreplace(const char *src, const char *pattern, const char *replace) } } +/** + * @internal + * + * @brief Processes errno into error string + * + * @param[in] err_num The errno value + * @param[out] buf Pointer to a place where the string could be saved + * @param[in] buflen The allocated size of buf + * + * @return error string + */ +char *ssh_strerror(int err_num, char *buf, size_t buflen) +{ +#if defined(__linux__) && defined(__GLIBC__) && defined(_GNU_SOURCE) + /* GNU extension on Linux */ + return strerror_r(err_num, buf, buflen); +#else + int rv; + +#if defined(_WIN32) + rv = strerror_s(buf, buflen, err_num); +#else + /* POSIX version available for example on FreeBSD or in musl libc */ + rv = strerror_r(err_num, buf, buflen); +#endif /* _WIN32 */ + + /* make sure the buffer is initialized and terminated with NULL */ + if (-rv == ERANGE) { + buf[0] = '\0'; + } + return buf; +#endif /* defined(__linux__) && defined(__GLIBC__) && defined(_GNU_SOURCE) */ +} + +/** + * @brief Read the requested number of bytes from a local file. + * + * A call to read() may perform a short read even when sufficient data is + * present in the file. This function can be used to avoid such short reads. + * + * This function tries to read the requested number of bytes from the file + * until one of the following occurs : + * - Requested number of bytes are read. + * - EOF is encountered before reading the requested number of bytes. + * - An error occurs. + * + * On encountering an error due to an interrupt, this function ignores that + * error and continues trying to read the data. + * + * @param[in] fd The file descriptor of the local file to read from. + * + * @param[out] buf Pointer to a buffer in which read data will be + * stored. + * + * @param[in] nbytes Number of bytes to read. + * + * @returns Number of bytes read on success, + * SSH_ERROR on error with errno set to indicate the + * error. + */ +ssize_t ssh_readn(int fd, void *buf, size_t nbytes) +{ + size_t total_bytes_read = 0; + ssize_t bytes_read; + + if (fd < 0 || buf == NULL || nbytes == 0) { + errno = EINVAL; + return SSH_ERROR; + } + + do { + bytes_read = read(fd, + ((char *)buf) + total_bytes_read, + nbytes - total_bytes_read); + if (bytes_read == -1) { + if (errno == EINTR) { + /* Ignoring errors due to signal interrupts */ + continue; + } + + return SSH_ERROR; + } + + if (bytes_read == 0) { + /* EOF encountered on the local file before reading nbytes */ + break; + } + + total_bytes_read += (size_t)bytes_read; + } while (total_bytes_read < nbytes); + + return total_bytes_read; +} + +/** + * @brief Write the requested number of bytes to a local file. + * + * A call to write() may perform a short write on a local file. This function + * can be used to avoid short writes. + * + * This function tries to write the requested number of bytes until those many + * bytes are written or some error occurs. + * + * On encountering an error due to an interrupt, this function ignores that + * error and continues trying to write the data. + * + * @param[in] fd The file descriptor of the local file to write to. + * + * @param[in] buf Pointer to a buffer in which data to write is stored. + * + * @param[in] nbytes Number of bytes to write. + * + * @returns Number of bytes written on success, + * SSH_ERROR on error with errno set to indicate the + * error. + */ +ssize_t ssh_writen(int fd, const void *buf, size_t nbytes) +{ + size_t total_bytes_written = 0; + ssize_t bytes_written; + + if (fd < 0 || buf == NULL || nbytes == 0) { + errno = EINVAL; + return SSH_ERROR; + } + + do { + bytes_written = write(fd, + ((const char *)buf) + total_bytes_written, + nbytes - total_bytes_written); + if (bytes_written == -1) { + if(errno == EINTR) { + /* Ignoring errors due to signal interrupts */ + continue; + } + + return SSH_ERROR; + } + + total_bytes_written += (size_t)bytes_written; + } while (total_bytes_written < nbytes); + + return total_bytes_written; +} + +/** + * @brief Checks syntax of a domain name + * + * The check is made based on the RFC1035 section 2.3.1 + * Allowed characters are: hyphen, period, digits (0-9) and letters (a-zA-Z) + * + * The label should be no longer than 63 characters + * The label should start with a letter and end with a letter or number + * The label in this implementation can start with a number to allow virtual + * URLs to pass. Note that this will make IPv4 addresses to pass + * this check too. + * + * @param hostname The domain name to be checked, has to be null terminated + * + * @return SSH_OK if the hostname passes syntax check + * SSH_ERROR otherwise or if hostname is NULL or empty string + */ +int ssh_check_hostname_syntax(const char *hostname) +{ + char *it = NULL, *s = NULL, *buf = NULL; + size_t it_len; + char c; + + if (hostname == NULL || strlen(hostname) == 0) { + return SSH_ERROR; + } + + /* strtok_r writes into the string, keep the input clean */ + s = strdup(hostname); + if (s == NULL) { + return SSH_ERROR; + } + + it = strtok_r(s, ".", &buf); + /* if the token has 0 length */ + if (it == NULL) { + free(s); + return SSH_ERROR; + } + do { + it_len = strlen(it); + if (it_len > ARPA_DOMAIN_MAX_LEN || + /* the first char must be a letter, but some virtual urls start + * with a number */ + isalnum(it[0]) == 0 || + isalnum(it[it_len - 1]) == 0) { + free(s); + return SSH_ERROR; + } + while (*it != '\0') { + c = *it; + /* the "." is allowed too, but tokenization removes it from the + * string */ + if (isalnum(c) == 0 && c != '-') { + free(s); + return SSH_ERROR; + } + it++; + } + } while ((it = strtok_r(NULL, ".", &buf)) != NULL); + + free(s); + + return SSH_OK; +} + +/** + * @brief Checks syntax of a username + * + * This check disallows metacharacters in the username + * + * @param username The username to be checked, has to be null terminated + * + * @return SSH_OK if the username passes syntax check + * SSH_ERROR otherwise or if username is NULL or empty string + */ +int ssh_check_username_syntax(const char *username) +{ + size_t username_len; + + if (username == NULL || *username == '-') { + return SSH_ERROR; + } + + username_len = strlen(username); + if (username_len == 0 || username[username_len - 1] == '\\' || + strpbrk(username, "'`\";&<>|(){}") != NULL) { + return SSH_ERROR; + } + for (size_t i = 0; i < username_len; i++) { + if (isspace(username[i]) != 0 && username[i + 1] == '-') { + return SSH_ERROR; + } + } + + return SSH_OK; +} + /** @} */ diff --git a/src/options.c b/src/options.c index 264eacb7..961aba4e 100644 --- a/src/options.c +++ b/src/options.c @@ -32,10 +32,12 @@ #include <winsock2.h> #endif #include <sys/types.h> +#include "libssh/pki_priv.h" #include "libssh/priv.h" #include "libssh/session.h" #include "libssh/misc.h" #include "libssh/options.h" +#include "libssh/config_parser.h" #ifdef WITH_SERVER #include "libssh/server.h" #include "libssh/bind.h" @@ -51,21 +53,23 @@ * @brief Duplicate the options of a session structure. * * If you make several sessions with the same options this is useful. You - * cannot use twice the same option structure in ssh_session_connect. + * cannot use twice the same option structure in ssh_connect. * * @param src The session to use to copy the options. * * @param dest A pointer to store the allocated session with duplicated - * options. You have to free the memory. + * options. You have to free the memory using ssh_free() * - * @returns 0 on sucess, -1 on error with errno set. + * @returns 0 on success, -1 on error with errno set. * - * @see ssh_session_connect() + * @see ssh_connect() + * @see ssh_free() */ int ssh_options_copy(ssh_session src, ssh_session *dest) { ssh_session new; struct ssh_iterator *it = NULL; + struct ssh_list *list = NULL; char *id = NULL; int i; @@ -103,24 +107,51 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) } /* Remove the default identities */ - for (id = ssh_list_pop_head(char *, new->opts.identity); + for (id = ssh_list_pop_head(char *, new->opts.identity_non_exp); id != NULL; - id = ssh_list_pop_head(char *, new->opts.identity)) { + id = ssh_list_pop_head(char *, new->opts.identity_non_exp)) { SAFE_FREE(id); } /* Copy the new identities from the source list */ - if (src->opts.identity != NULL) { + list = new->opts.identity_non_exp; + it = ssh_list_get_iterator(src->opts.identity_non_exp); + for (i = 0; i < 2; i++) { + while (it) { + int rc; + + id = strdup((char *)it->data); + if (id == NULL) { + ssh_free(new); + return -1; + } + + rc = ssh_list_append(list, id); + if (rc < 0) { + free(id); + ssh_free(new); + return -1; + } + it = it->next; + } + + /* copy the identity list if there is any already */ + list = new->opts.identity; it = ssh_list_get_iterator(src->opts.identity); + } + + list = new->opts.certificate_non_exp; + it = ssh_list_get_iterator(src->opts.certificate_non_exp); + for (i = 0; i < 2; i++) { while (it) { int rc; - id = strdup((char *) it->data); + id = strdup((char *)it->data); if (id == NULL) { ssh_free(new); return -1; } - rc = ssh_list_append(new->opts.identity, id); + rc = ssh_list_append(list, id); if (rc < 0) { free(id); ssh_free(new); @@ -128,6 +159,10 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) } it = it->next; } + + /* copy the certificate list if there is any already */ + list = new->opts.certificate; + it = ssh_list_get_iterator(src->opts.certificate); } if (src->opts.sshdir != NULL) { @@ -196,6 +231,14 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) } } + if (src->opts.control_path != NULL) { + new->opts.control_path = strdup(src->opts.control_path); + if (new->opts.control_path == NULL) { + ssh_free(new); + return -1; + } + } + memcpy(new->opts.options_seen, src->opts.options_seen, sizeof(new->opts.options_seen)); @@ -209,6 +252,7 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) new->opts.flags = src->opts.flags; new->opts.nodelay = src->opts.nodelay; new->opts.config_processed = src->opts.config_processed; + new->opts.control_master = src->opts.control_master; new->common.log_verbosity = src->common.log_verbosity; new->common.callbacks = src->common.callbacks; @@ -219,14 +263,30 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) int ssh_options_set_algo(ssh_session session, enum ssh_kex_types_e algo, - const char *list) + const char *list, + char **place) { - char *p = NULL; + /* When the list start with +,-,^ the filtration of unknown algorithms + * gets handled inside the helper functions, otherwise the list is taken + * as it is. */ + char *p = (char *)list; + + if (algo < SSH_COMP_C_S) { + if (list[0] == '+') { + p = ssh_add_to_default_algos(algo, list+1); + } else if (list[0] == '-') { + p = ssh_remove_from_default_algos(algo, list+1); + } else if (list[0] == '^') { + p = ssh_prefix_default_algos(algo, list+1); + } + } - if (ssh_fips_mode()) { - p = ssh_keep_fips_algos(algo, list); - } else { - p = ssh_keep_known_algos(algo, list); + if (p == list) { + if (ssh_fips_mode()) { + p = ssh_keep_fips_algos(algo, list); + } else { + p = ssh_keep_known_algos(algo, list); + } } if (p == NULL) { @@ -236,8 +296,8 @@ int ssh_options_set_algo(ssh_session session, return -1; } - SAFE_FREE(session->opts.wanted_methods[algo]); - session->opts.wanted_methods[algo] = p; + SAFE_FREE(*place); + *place = p; return 0; } @@ -263,9 +323,10 @@ int ssh_options_set_algo(ssh_session session, * The file descriptor to use (socket_t).\n * \n * If you wish to open the socket yourself for a reason - * or another, set the file descriptor. Don't forget to - * set the hostname as the hostname is used as a key in - * the known_host mechanism. + * or another, set the file descriptor and take care of closing + * it (this is new behavior in libssh 0.10). + * Don't forget to set the hostname as the hostname is used + * as a key in the known_host mechanism. * * - SSH_OPTIONS_BINDADDR: * The address to bind the client to (const char *). @@ -312,13 +373,28 @@ int ssh_options_set_algo(ssh_session session, * Add a new identity file (const char *, format string) to * the identity list.\n * \n - * By default identity, id_dsa and id_rsa are checked.\n + * By default id_rsa, id_ecdsa and id_ed25519 files are used.\n * \n * The identity used to authenticate with public key will be * prepended to the list. * It may include "%s" which will be replaced by the * user home directory. * + * - SSH_OPTIONS_CERTIFICATE: + * Add a new certificate file (const char *, format string) to + * the certificate list.\n + * \n + * By default id_rsa-cert.pub, id_ecdsa-cert.pub and + * id_ed25519-cert.pub files are used, when the underlying + * private key is present.\n + * \n + * The certificate itself can not be used to authenticate to + * remote server so it needs to be paired with private key + * (aka identity file) provided with separate option, from agent + * or from PKCS#11 token. + * It may include "%s" which will be replaced by the + * user home directory. + * * - SSH_OPTIONS_TIMEOUT: * Set a timeout for the connection in seconds (long). * @@ -340,8 +416,9 @@ int ssh_options_set_algo(ssh_session session, * - SSH_LOG_NOLOG: No logging * - SSH_LOG_WARNING: Only warnings * - SSH_LOG_PROTOCOL: High level protocol information - * - SSH_LOG_PACKET: Lower level protocol infomations, packet level + * - SSH_LOG_PACKET: Lower level protocol information, packet level * - SSH_LOG_FUNCTIONS: Every function path + * The default is SSH_LOG_NOLOG. * * - SSH_OPTIONS_LOG_VERBOSITY_STR: * Set the session logging verbosity via a @@ -353,34 +430,60 @@ int ssh_options_set_algo(ssh_session session, * * - SSH_OPTIONS_CIPHERS_C_S: * Set the symmetric cipher client to server (const char *, - * comma-separated list). + * comma-separated list). The list can be prepended by +,-,^ + * which can append, remove or move to the beginning + * (prioritizing) of the default list respectively. Giving an + * empty list after + and ^ will cause error. * * - SSH_OPTIONS_CIPHERS_S_C: * Set the symmetric cipher server to client (const char *, - * comma-separated list). + * comma-separated list). The list can be prepended by +,-,^ + * which can append, remove or move to the beginning + * (prioritizing) of the default list respectively. Giving an + * empty list after + and ^ will cause error. * * - SSH_OPTIONS_KEY_EXCHANGE: * Set the key exchange method to be used (const char *, * comma-separated list). ex: * "ecdh-sha2-nistp256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" + * The list can be prepended by +,-,^ which will append, + * remove or move to the beginning (prioritizing) of the + * default list respectively. Giving an empty list + * after + and ^ will cause error. * * - SSH_OPTIONS_HMAC_C_S: * Set the Message Authentication Code algorithm client to server - * (const char *, comma-separated list). + * (const char *, comma-separated list). The list can be + * prepended by +,-,^ which will append, remove or move to + * the beginning (prioritizing) of the default list + * respectively. Giving an empty list after + and ^ will + * cause error. * * - SSH_OPTIONS_HMAC_S_C: * Set the Message Authentication Code algorithm server to client - * (const char *, comma-separated list). + * (const char *, comma-separated list). The list can be + * prepended by +,-,^ which will append, remove or move to + * the beginning (prioritizing) of the default list + * respectively. Giving an empty list after + and ^ will + * cause error. * * - SSH_OPTIONS_HOSTKEYS: * Set the preferred server host key types (const char *, * comma-separated list). ex: - * "ssh-rsa,ssh-dss,ecdh-sha2-nistp256" + * "ssh-rsa,ecdh-sha2-nistp256". The list can be + * prepended by +,-,^ which will append, remove or move to + * the beginning (prioritizing) of the default list + * respectively. Giving an empty list after + and ^ will + * cause error. * * - SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: * Set the preferred public key algorithms to be used for * authentication (const char *, comma-separated list). ex: - * "ssh-rsa,rsa-sha2-256,ssh-dss,ecdh-sha2-nistp256" + * "ssh-rsa,rsa-sha2-256,ecdh-sha2-nistp256" + * The list can be prepended by +,-,^ which will append, + * remove or move to the beginning (prioritizing) of the + * default list respectively. Giving an empty list + * after + and ^ will cause error. * * - SSH_OPTIONS_COMPRESSION_C_S: * Set the compression to use for client to server @@ -461,10 +564,49 @@ int ssh_options_set_algo(ssh_session session, * (uint64_t, 0=default) * * - SSH_OPTIONS_REKEY_TIME - * Set the time limit for a session before intializing a rekey + * Set the time limit for a session before initializing a rekey * in seconds. RFC 4253 Section 9 recommends one hour. * (uint32_t, 0=off) * + * - SSH_OPTIONS_RSA_MIN_SIZE + * Set the minimum RSA key size in bits to be accepted by the + * client for both authentication and hostkey verification. + * The values under 768 bits are not accepted even with this + * configuration option as they are considered completely broken. + * Setting 0 will revert the value to defaults. + * Default is 1024 bits or 2048 bits in FIPS mode. + * (int *) + + * - SSH_OPTIONS_IDENTITY_AGENT + * Set the path to the SSH agent socket. If unset, the + * SSH_AUTH_SOCK environment is consulted. + * (const char *) + + * - SSH_OPTIONS_IDENTITIES_ONLY + * Use only keys specified in the SSH config, even if agent + * offers more. + * (bool) + * + * - SSH_OPTIONS_CONTROL_MASTER + * Set the option to enable the sharing of multiple sessions over a + * single network connection using connection multiplexing. + * + * The possible options are among the following: + * - SSH_CONTROL_MASTER_AUTO: enable connection sharing if possible + * - SSH_CONTROL_MASTER_YES: enable connection sharing unconditionally + * - SSH_CONTROL_MASTER_ASK: ask for confirmation if connection sharing is to be enabled + * - SSH_CONTROL_MASTER_AUTOASK: enable connection sharing if possible, + * but ask for confirmation + * - SSH_CONTROL_MASTER_NO: disable connection sharing unconditionally + * + * The default is SSH_CONTROL_MASTER_NO. + * + * - SSH_OPTIONS_CONTROL_PATH + * Set the path to the control socket used for connection sharing. + * Set to "none" to disable connection sharing. + * (const char *) + * + * * @param value The value to set. This is a generic pointer and the * datatype which is used should be set according to the * type set. @@ -472,12 +614,14 @@ int ssh_options_set_algo(ssh_session session, * @return 0 on success, < 0 on error. */ int ssh_options_set(ssh_session session, enum ssh_options_e type, - const void *value) { + const void *value) +{ const char *v; char *p, *q; long int i; unsigned int u; int rc; + char **wanted_methods = session->opts.wanted_methods; if (session == NULL) { return -1; @@ -490,33 +634,18 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - q = strdup(value); - if (q == NULL) { - ssh_set_error_oom(session); + char *username = NULL, *hostname = NULL; + rc = ssh_config_parse_uri(value, &username, &hostname, NULL, true); + if (rc != SSH_OK) { return -1; } - p = strchr(q, '@'); - - SAFE_FREE(session->opts.host); - - if (p) { - *p = '\0'; - session->opts.host = strdup(p + 1); - if (session->opts.host == NULL) { - SAFE_FREE(q); - ssh_set_error_oom(session); - return -1; - } - + if (username != NULL) { SAFE_FREE(session->opts.username); - session->opts.username = strdup(q); - SAFE_FREE(q); - if (session->opts.username == NULL) { - ssh_set_error_oom(session); - return -1; - } - } else { - session->opts.host = q; + session->opts.username = username; + } + if (hostname != NULL) { + SAFE_FREE(session->opts.host); + session->opts.host = hostname; } } break; @@ -547,7 +676,9 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, } i = strtol(q, &p, 10); if (q == p) { + SSH_LOG(SSH_LOG_DEBUG, "No port number was parsed"); SAFE_FREE(q); + return -1; } SAFE_FREE(q); if (i <= 0) { @@ -607,6 +738,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_oom(session); return -1; } + rc = ssh_check_username_syntax(session->opts.username); + if (rc != SSH_OK) { + ssh_set_error_invalid(session); + return -1; + } } break; case SSH_OPTIONS_SSH_DIR: @@ -639,7 +775,27 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, if (q == NULL) { return -1; } - rc = ssh_list_prepend(session->opts.identity, q); + if (session->opts.exp_flags & SSH_OPT_EXP_FLAG_IDENTITY) { + rc = ssh_list_append(session->opts.identity_non_exp, q); + } else { + rc = ssh_list_prepend(session->opts.identity_non_exp, q); + } + if (rc < 0) { + free(q); + return -1; + } + break; + case SSH_OPTIONS_CERTIFICATE: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } + q = strdup(v); + if (q == NULL) { + return -1; + } + rc = ssh_list_append(session->opts.certificate_non_exp, q); if (rc < 0) { free(q); return -1; @@ -659,6 +815,7 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_oom(session); return -1; } + session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; } break; case SSH_OPTIONS_GLOBAL_KNOWNHOSTS: @@ -680,6 +837,7 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_oom(session); return -1; } + session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS; } break; case SSH_OPTIONS_TIMEOUT: @@ -743,7 +901,9 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, } i = strtol(q, &p, 10); if (q == p) { + SSH_LOG(SSH_LOG_DEBUG, "No log verbositiy was parsed"); SAFE_FREE(q); + return -1; } SAFE_FREE(q); if (i < 0) { @@ -761,7 +921,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_CRYPT_C_S, v) < 0) + rc = ssh_options_set_algo(session, + SSH_CRYPT_C_S, + v, + &wanted_methods[SSH_CRYPT_C_S]); + if (rc < 0) return -1; } break; @@ -771,7 +935,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_CRYPT_S_C, v) < 0) + rc = ssh_options_set_algo(session, + SSH_CRYPT_S_C, + v, + &wanted_methods[SSH_CRYPT_S_C]); + if (rc < 0) return -1; } break; @@ -781,7 +949,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_KEX, v) < 0) + rc = ssh_options_set_algo(session, + SSH_KEX, + v, + &wanted_methods[SSH_KEX]); + if (rc < 0) return -1; } break; @@ -791,7 +963,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_HOSTKEYS, v) < 0) + rc = ssh_options_set_algo(session, + SSH_HOSTKEYS, + v, + &wanted_methods[SSH_HOSTKEYS]); + if (rc < 0) return -1; } break; @@ -801,20 +977,12 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_fips_mode()) { - p = ssh_keep_fips_algos(SSH_HOSTKEYS, v); - } else { - p = ssh_keep_known_algos(SSH_HOSTKEYS, v); - } - if (p == NULL) { - ssh_set_error(session, SSH_REQUEST_DENIED, - "Setting method: no known public key algorithm (%s)", - v); + rc = ssh_options_set_algo(session, + SSH_HOSTKEYS, + v, + &session->opts.pubkey_accepted_types); + if (rc < 0) return -1; - } - - SAFE_FREE(session->opts.pubkey_accepted_types); - session->opts.pubkey_accepted_types = p; } break; case SSH_OPTIONS_HMAC_C_S: @@ -823,7 +991,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_MAC_C_S, v) < 0) + rc = ssh_options_set_algo(session, + SSH_MAC_C_S, + v, + &wanted_methods[SSH_MAC_C_S]); + if (rc < 0) return -1; } break; @@ -833,7 +1005,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (ssh_options_set_algo(session, SSH_MAC_S_C, v) < 0) + rc = ssh_options_set_algo(session, + SSH_MAC_S_C, + v, + &wanted_methods[SSH_MAC_S_C]); + if (rc < 0) return -1; } break; @@ -843,16 +1019,18 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (strcasecmp(value,"yes")==0){ - if(ssh_options_set_algo(session,SSH_COMP_C_S,"zlib@openssh.com,zlib") < 0) - return -1; - } else if (strcasecmp(value,"no")==0){ - if(ssh_options_set_algo(session,SSH_COMP_C_S,"none") < 0) - return -1; - } else { - if (ssh_options_set_algo(session, SSH_COMP_C_S, v) < 0) - return -1; + const char *tmp = v; + if (strcasecmp(value, "yes") == 0){ + tmp = "zlib@openssh.com,none"; + } else if (strcasecmp(value, "no") == 0){ + tmp = "none,zlib@openssh.com"; } + rc = ssh_options_set_algo(session, + SSH_COMP_C_S, + tmp, + &wanted_methods[SSH_COMP_C_S]); + if (rc < 0) + return -1; } break; case SSH_OPTIONS_COMPRESSION_S_C: @@ -861,16 +1039,19 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - if (strcasecmp(value,"yes")==0){ - if(ssh_options_set_algo(session,SSH_COMP_S_C,"zlib@openssh.com,zlib") < 0) - return -1; - } else if (strcasecmp(value,"no")==0){ - if(ssh_options_set_algo(session,SSH_COMP_S_C,"none") < 0) - return -1; - } else { - if (ssh_options_set_algo(session, SSH_COMP_S_C, v) < 0) - return -1; + const char *tmp = v; + if (strcasecmp(value, "yes") == 0){ + tmp = "zlib@openssh.com,none"; + } else if (strcasecmp(value, "no") == 0){ + tmp = "none,zlib@openssh.com"; } + + rc = ssh_options_set_algo(session, + SSH_COMP_S_C, + tmp, + &wanted_methods[SSH_COMP_S_C]); + if (rc < 0) + return -1; } break; case SSH_OPTIONS_COMPRESSION: @@ -906,7 +1087,6 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, session->opts.StrictHostKeyChecking = (*x & 0xff) > 0 ? 1 : 0; } - session->opts.StrictHostKeyChecking = *(int*)value; break; case SSH_OPTIONS_PROXYCOMMAND: v = value; @@ -923,6 +1103,7 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, return -1; } session->opts.ProxyCommand = q; + session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_PROXYCOMMAND; } } break; @@ -1029,6 +1210,77 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, session->opts.rekey_time = (*x) * 1000; } break; + case SSH_OPTIONS_RSA_MIN_SIZE: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *)value; + if (*x > 0 && *x < 768) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The provided value (%u) for minimal RSA key " + "size is too small. Use at least 768 bits.", *x); + return -1; + } + session->opts.rsa_min_size = *x; + } + break; + case SSH_OPTIONS_IDENTITY_AGENT: + v = value; + SAFE_FREE(session->opts.agent_socket); + if (v == NULL) { + /* The default value will be set by the ssh_options_apply() */ + } else if (v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + session->opts.agent_socket = ssh_path_expand_tilde(v); + if (session->opts.agent_socket == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_IDENTITIES_ONLY: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + bool *x = (bool *)value; + session->opts.identities_only = *x; + } + break; + case SSH_OPTIONS_CONTROL_MASTER: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + if (*x < SSH_CONTROL_MASTER_NO || *x > SSH_CONTROL_MASTER_AUTOASK) { + ssh_set_error_invalid(session); + return -1; + } + session->opts.control_master = *x; + } + break; + case SSH_OPTIONS_CONTROL_PATH: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + SAFE_FREE(session->opts.control_path); + rc = strcasecmp(v, "none"); + if (rc != 0) { + session->opts.control_path = ssh_path_expand_tilde(v); + if (session->opts.control_path == NULL) { + ssh_set_error_oom(session); + return -1; + } + session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_CONTROL_PATH; + } + } + break; default: ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); return -1; @@ -1070,7 +1322,7 @@ int ssh_options_get_port(ssh_session session, unsigned int* port_target) { /** * @brief This function can get ssh options, it does not support all options provided for * ssh options set, but mostly those which a user-space program may care about having - * trusted the ssh driver to infer these values from underlaying configuration files. + * trusted the ssh driver to infer these values from underlying configuration files. * It operates only on those SSH_OPTIONS_* which return char*. If you wish to receive * the port then please use ssh_options_get_port() which returns an unsigned int. * @@ -1090,7 +1342,7 @@ int ssh_options_get_port(ssh_session session, unsigned int* port_target) { * - SSH_OPTIONS_IDENTITY: * Get the first identity file name (const char *).\n * \n - * By default identity, id_dsa and id_rsa are checked. + * By default id_rsa, id_ecdsa and id_ed25519 files are used. * * - SSH_OPTIONS_PROXYCOMMAND: * Get the proxycommand necessary to log into the @@ -1103,6 +1355,9 @@ int ssh_options_get_port(ssh_session session, unsigned int* port_target) { * - SSH_OPTIONS_KNOWNHOSTS: * Get the path to the known_hosts file being used. * + * - SSH_OPTIONS_CONTROL_PATH: + * Get the path to the control socket being used for connection multiplexing. + * * @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 @@ -1134,7 +1389,11 @@ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value) break; } case SSH_OPTIONS_IDENTITY: { - struct ssh_iterator *it = ssh_list_get_iterator(session->opts.identity); + struct ssh_iterator *it; + it = ssh_list_get_iterator(session->opts.identity); + if (it == NULL) { + it = ssh_list_get_iterator(session->opts.identity_non_exp); + } if (it == NULL) { return SSH_ERROR; } @@ -1153,6 +1412,10 @@ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value) src = session->opts.global_knownhosts; break; } + case SSH_OPTIONS_CONTROL_PATH: { + src = session->opts.control_path; + break; + } default: ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); return SSH_ERROR; @@ -1175,10 +1438,10 @@ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value) * This is a helper for your application to generate the appropriate * options from the command line arguments.\n * The argv array and argc value are changed so that the parsed - * arguments wont appear anymore in them.\n + * arguments won't appear anymore in them.\n * The single arguments (without switches) are not parsed. thus, * myssh -l user localhost\n - * The command wont set the hostname value of options to localhost. + * The command won't set the hostname value of options to localhost. * * @param session The session to configure. * @@ -1208,8 +1471,6 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) size_t i = 0; int argc = *argcptr; int debuglevel = 0; - int usersa = 0; - int usedss = 0; int compress = 0; int cont = 1; size_t current = 0; @@ -1223,7 +1484,7 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) } opterr = 0; /* shut up getopt */ - while((opt = getopt(argc, argv, "c:i:Cl:p:vb:rd12")) != -1) { + while((opt = getopt(argc, argv, "c:i:Cl:p:vb:r12")) != -1) { switch(opt) { case 'l': user = optarg; @@ -1235,10 +1496,6 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) debuglevel++; break; case 'r': - usersa++; - break; - case 'd': - usedss++; break; case 'c': cipher = optarg; @@ -1302,11 +1559,6 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) optind++; } - if (usersa && usedss) { - ssh_set_error(session, SSH_FATAL, "Either RSA or DSS must be chosen"); - cont = 0; - } - ssh_set_log_level(debuglevel); optind = saveoptind; @@ -1375,13 +1627,14 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) * @param session SSH session handle * * @param filename The options file to use, if NULL the default - * ~/.ssh/config will be used. + * ~/.ssh/config and /etc/ssh/ssh_config will be used. * * @return 0 on success, < 0 on error. * * @see ssh_options_set() */ -int ssh_options_parse_config(ssh_session session, const char *filename) { +int ssh_options_parse_config(ssh_session session, const char *filename) +{ char *expanded_filename; int r; @@ -1426,8 +1679,8 @@ out: return r; } -int ssh_options_apply(ssh_session session) { - struct ssh_iterator *it; +int ssh_options_apply(ssh_session session) +{ char *tmp; int rc; @@ -1445,54 +1698,124 @@ int ssh_options_apply(ssh_session session) { } } - if (session->opts.knownhosts == NULL) { - tmp = ssh_path_expand_escape(session, "%d/known_hosts"); - } else { - tmp = ssh_path_expand_escape(session, session->opts.knownhosts); - } - if (tmp == NULL) { - return -1; + if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_KNOWNHOSTS) == 0) { + if (session->opts.knownhosts == NULL) { + tmp = ssh_path_expand_escape(session, "%d/known_hosts"); + } else { + tmp = ssh_path_expand_escape(session, session->opts.knownhosts); + } + if (tmp == NULL) { + return -1; + } + free(session->opts.knownhosts); + session->opts.knownhosts = tmp; + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_KNOWNHOSTS; } - free(session->opts.knownhosts); - session->opts.knownhosts = tmp; - if (session->opts.global_knownhosts == NULL) { - tmp = strdup("/etc/ssh/ssh_known_hosts"); - } else { - tmp = ssh_path_expand_escape(session, session->opts.global_knownhosts); + if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS) == 0) { + if (session->opts.global_knownhosts == NULL) { + tmp = strdup("/etc/ssh/ssh_known_hosts"); + } else { + tmp = ssh_path_expand_escape(session, + session->opts.global_knownhosts); + } + if (tmp == NULL) { + return -1; + } + free(session->opts.global_knownhosts); + session->opts.global_knownhosts = tmp; + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS; } - if (tmp == NULL) { - return -1; + + + if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_PROXYCOMMAND) == 0) { + if (session->opts.ProxyCommand != NULL) { + char *p = NULL; + size_t plen = strlen(session->opts.ProxyCommand) + + 5 /* strlen("exec ") */; + + if (strncmp(session->opts.ProxyCommand, "exec ", 5) != 0) { + p = malloc(plen + 1 /* \0 */); + if (p == NULL) { + return -1; + } + + rc = snprintf(p, plen + 1, "exec %s", session->opts.ProxyCommand); + if ((size_t)rc != plen) { + free(p); + return -1; + } + tmp = ssh_path_expand_escape(session, p); + free(p); + } else { + tmp = ssh_path_expand_escape(session, + session->opts.ProxyCommand); + } + + if (tmp == NULL) { + return -1; + } + free(session->opts.ProxyCommand); + session->opts.ProxyCommand = tmp; + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_PROXYCOMMAND; + } } - free(session->opts.global_knownhosts); - session->opts.global_knownhosts = tmp; - if (session->opts.ProxyCommand != NULL) { - tmp = ssh_path_expand_escape(session, session->opts.ProxyCommand); - if (tmp == NULL) { - return -1; + if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_CONTROL_PATH) == 0) { + if (session->opts.control_path != NULL) { + tmp = ssh_path_expand_escape(session, session->opts.control_path); + if (tmp == NULL) { + return -1; + } + free(session->opts.control_path); + session->opts.control_path = tmp; + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_CONTROL_PATH; } - free(session->opts.ProxyCommand); - session->opts.ProxyCommand = tmp; } - for (it = ssh_list_get_iterator(session->opts.identity); - it != NULL; - it = it->next) { - char *id = (char *) it->data; - if (strncmp(id, "pkcs11:", 6) == 0) { + for (tmp = ssh_list_pop_head(char *, session->opts.identity_non_exp); + tmp != NULL; + tmp = ssh_list_pop_head(char *, session->opts.identity_non_exp)) { + char *id = tmp; + if (strncmp(id, "pkcs11:", 6) != 0) { /* PKCS#11 URIs are using percent-encoding so we can not mix * it with ssh expansion of ssh escape characters. - * Skip these identities now, before we will have PKCS#11 support */ - continue; + tmp = ssh_path_expand_escape(session, id); + if (tmp == NULL) { + return -1; + } + free(id); } + + /* use append to keep the order at first call and use prepend + * to put anything that comes on the nth calls to the beginning */ + if (session->opts.exp_flags & SSH_OPT_EXP_FLAG_IDENTITY) { + rc = ssh_list_prepend(session->opts.identity, tmp); + } else { + rc = ssh_list_append(session->opts.identity, tmp); + } + if (rc != SSH_OK) { + return -1; + } + } + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_IDENTITY; + + for (tmp = ssh_list_pop_head(char *, session->opts.certificate_non_exp); + tmp != NULL; + tmp = ssh_list_pop_head(char *, session->opts.certificate_non_exp)) { + char *id = tmp; + tmp = ssh_path_expand_escape(session, id); if (tmp == NULL) { return -1; } free(id); - it->data = tmp; + + rc = ssh_list_append(session->opts.certificate, tmp); + if (rc != SSH_OK) { + return -1; + } } return 0; @@ -1501,12 +1824,27 @@ int ssh_options_apply(ssh_session session) { /** @} */ #ifdef WITH_SERVER +static bool ssh_bind_key_size_allowed(ssh_bind sshbind, ssh_key key) +{ + int min_size = 0; + + switch (ssh_key_type(key)) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + min_size = sshbind->rsa_min_size; + return ssh_key_size_allowed_rsa(min_size, key); + default: + return true; + } +} + /** * @addtogroup libssh_server * @{ */ -static int ssh_bind_set_key(ssh_bind sshbind, char **key_loc, - const void *value) { +static int +ssh_bind_set_key(ssh_bind sshbind, char **key_loc, const void *value) +{ if (value == NULL) { ssh_set_error_invalid(sshbind); return -1; @@ -1523,26 +1861,12 @@ static int ssh_bind_set_key(ssh_bind sshbind, char **key_loc, static int ssh_bind_set_algo(ssh_bind sshbind, enum ssh_kex_types_e algo, - const char *list) + const char *list, + char **place) { - char *p = NULL; - - if (ssh_fips_mode()) { - p = ssh_keep_fips_algos(algo, list); - } else { - p = ssh_keep_known_algos(algo, list); - } - if (p == NULL) { - ssh_set_error(sshbind, SSH_REQUEST_DENIED, - "Setting method: no algorithm for method \"%s\" (%s)", - ssh_kex_get_description(algo), list); - return -1; - } - - SAFE_FREE(sshbind->wanted_methods[algo]); - sshbind->wanted_methods[algo] = p; - - return 0; + /* sshbind is needed only for ssh_set_error which takes void* + * the typecast is only to satisfy function parameter type */ + return ssh_options_set_algo((ssh_session)sshbind, algo, list, place); } /** @@ -1556,7 +1880,7 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * - SSH_BIND_OPTIONS_HOSTKEY: * Set the path to an ssh host key, regardless * of type. Only one key from per key type - * (RSA, DSA, ECDSA) is allowed in an ssh_bind + * (RSA, ED25519 and ECDSA) is allowed in an ssh_bind * at a time, and later calls to this function * with this option for the same key type will * override prior calls (const char *). @@ -1580,8 +1904,9 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * - SSH_LOG_NOLOG: No logging * - SSH_LOG_WARNING: Only warnings * - SSH_LOG_PROTOCOL: High level protocol information - * - SSH_LOG_PACKET: Lower level protocol infomations, packet level + * - SSH_LOG_PACKET: Lower level protocol information, packet level * - SSH_LOG_FUNCTIONS: Every function path + * The default is SSH_LOG_NOLOG. * * - SSH_BIND_OPTIONS_LOG_VERBOSITY_STR: * Set the session logging verbosity via a @@ -1591,23 +1916,23 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * 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 *). + * Deprecated alias to SSH_BIND_OPTIONS_HOSTKEY + * (const char *). * * - SSH_BIND_OPTIONS_ECDSAKEY: - * Set the path to the ssh host ecdsa key, - * SSHv2 only (const char *). + * Deprecated alias to SSH_BIND_OPTIONS_HOSTKEY + * (const char *). * * - SSH_BIND_OPTIONS_BANNER: * Set the server banner sent to clients (const char *). * + * - SSH_BIND_OPTIONS_DSAKEY: + * This is DEPRECATED, please do not use. + * * - SSH_BIND_OPTIONS_IMPORT_KEY: - * Set the Private Key for the server directly (ssh_key) + * Set the Private Key for the server directly + * (ssh_key). It will be free'd by ssh_bind_free(). * * - SSH_BIND_OPTIONS_CIPHERS_C_S: * Set the symmetric cipher client to server (const char *, @@ -1655,11 +1980,22 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * possible algorithms is created from the list of keys * set and then filtered against this list. * (const char *, comma-separated list). - * + * * - SSH_BIND_OPTIONS_MODULI * Set the path to the moduli file. Defaults to * /etc/ssh/moduli if not specified (const char *). * + * - SSH_BIND_OPTIONS_RSA_MIN_SIZE + * Set the minimum RSA key size in bits to be accepted by + * the server for both authentication and hostkey + * operations. The values under 768 bits are not accepted + * even with this configuration option as they are + * considered completely broken. Setting 0 will revert + * the value to defaults. + * Default is 1024 bits or 2048 bits in FIPS mode. + * (int) + * + * * @param value The value to set. This is a generic pointer and the * datatype which should be used is described at the * corresponding value of type above. @@ -1669,15 +2005,20 @@ static int ssh_bind_set_algo(ssh_bind sshbind, int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, const void *value) { + bool allowed; char *p, *q; const char *v; int i, rc; + char **wanted_methods = sshbind->wanted_methods; if (sshbind == NULL) { return -1; } switch (type) { + case SSH_BIND_OPTIONS_RSAKEY: + case SSH_BIND_OPTIONS_ECDSAKEY: + /* deprecated */ case SSH_BIND_OPTIONS_HOSTKEY: if (value == NULL) { ssh_set_error_invalid(sshbind); @@ -1692,19 +2033,18 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, if (rc != SSH_OK) { return -1; } + allowed = ssh_bind_key_size_allowed(sshbind, key); + if (!allowed) { + ssh_set_error(sshbind, + SSH_FATAL, + "The host key size %d is too small.", + ssh_key_size(key)); + ssh_key_free(key); + 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: @@ -1723,9 +2063,9 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, bind_key_path_loc = &sshbind->rsakey; break; case SSH_KEYTYPE_ED25519: - bind_key_loc = &sshbind->ed25519; - bind_key_path_loc = &sshbind->ed25519key; - break; + bind_key_loc = &sshbind->ed25519; + bind_key_path_loc = &sshbind->ed25519key; + break; default: ssh_set_error(sshbind, SSH_FATAL, @@ -1757,18 +2097,17 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_key *bind_key_loc = NULL; ssh_key key = (ssh_key)value; + allowed = ssh_bind_key_size_allowed(sshbind, key); + if (!allowed) { + ssh_set_error(sshbind, + SSH_FATAL, + "The host key size %d is too small.", + ssh_key_size(key)); + return -1; + } + key_type = ssh_key_type(key); switch (key_type) { - case SSH_KEYTYPE_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: @@ -1831,7 +2170,9 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, } i = strtol(q, &p, 10); if (q == p) { - SAFE_FREE(q); + SSH_LOG(SSH_LOG_DEBUG, "No bind port was parsed"); + SAFE_FREE(q); + return -1; } SAFE_FREE(q); @@ -1858,31 +2199,15 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, } i = strtol(q, &p, 10); if (q == p) { - SAFE_FREE(q); + SSH_LOG(SSH_LOG_DEBUG, "No log verbositiy was parsed"); + SAFE_FREE(q); + return -1; } SAFE_FREE(q); ssh_set_log_level(i & 0xffffU); } break; - case SSH_BIND_OPTIONS_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); @@ -1902,8 +2227,13 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - if (ssh_bind_set_algo(sshbind, SSH_CRYPT_C_S, v) < 0) + rc = ssh_bind_set_algo(sshbind, + SSH_CRYPT_C_S, + v, + &wanted_methods[SSH_CRYPT_C_S]); + if (rc < 0) { return -1; + } } break; case SSH_BIND_OPTIONS_CIPHERS_S_C: @@ -1912,8 +2242,13 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - if (ssh_bind_set_algo(sshbind, SSH_CRYPT_S_C, v) < 0) + rc = ssh_bind_set_algo(sshbind, + SSH_CRYPT_S_C, + v, + &wanted_methods[SSH_CRYPT_S_C]); + if (rc < 0) { return -1; + } } break; case SSH_BIND_OPTIONS_KEY_EXCHANGE: @@ -1922,7 +2257,10 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - rc = ssh_bind_set_algo(sshbind, SSH_KEX, v); + rc = ssh_bind_set_algo(sshbind, + SSH_KEX, + v, + &wanted_methods[SSH_KEX]); if (rc < 0) { return -1; } @@ -1934,8 +2272,13 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - if (ssh_bind_set_algo(sshbind, SSH_MAC_C_S, v) < 0) + rc = ssh_bind_set_algo(sshbind, + SSH_MAC_C_S, + v, + &wanted_methods[SSH_MAC_C_S]); + if (rc < 0) { return -1; + } } break; case SSH_BIND_OPTIONS_HMAC_S_C: @@ -1944,8 +2287,13 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - if (ssh_bind_set_algo(sshbind, SSH_MAC_S_C, v) < 0) + rc = ssh_bind_set_algo(sshbind, + SSH_MAC_S_C, + v, + &wanted_methods[SSH_MAC_S_C]); + if (rc < 0) { return -1; + } } break; case SSH_BIND_OPTIONS_CONFIG_DIR: @@ -1970,20 +2318,13 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - if (ssh_fips_mode()) { - p = ssh_keep_fips_algos(SSH_HOSTKEYS, v); - } else { - p = ssh_keep_known_algos(SSH_HOSTKEYS, v); - } - if (p == NULL) { - ssh_set_error(sshbind, SSH_REQUEST_DENIED, - "Setting method: no known public key algorithm (%s)", - v); + rc = ssh_bind_set_algo(sshbind, + SSH_HOSTKEYS, + v, + &sshbind->pubkey_accepted_key_types); + if (rc < 0) { return -1; } - - SAFE_FREE(sshbind->pubkey_accepted_key_types); - sshbind->pubkey_accepted_key_types = p; } break; case SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS: @@ -1992,7 +2333,10 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_set_error_invalid(sshbind); return -1; } else { - rc = ssh_bind_set_algo(sshbind, SSH_HOSTKEYS, v); + rc = ssh_bind_set_algo(sshbind, + SSH_HOSTKEYS, + v, + &wanted_methods[SSH_HOSTKEYS]); if (rc < 0) { return -1; } @@ -2020,6 +2364,21 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, } } break; + case SSH_BIND_OPTIONS_RSA_MIN_SIZE: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + int *x = (int *)value; + if (*x > 0 && *x < 768) { + ssh_set_error(sshbind, SSH_REQUEST_DENIED, + "The provided value (%u) for minimal RSA key " + "size is too small. Use at least 768 bits.", *x); + return -1; + } + sshbind->rsa_min_size = *x; + } + break; default: ssh_set_error(sshbind, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); return -1; @@ -2132,7 +2491,7 @@ static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) * @param sshbind SSH bind handle * * @param filename The options file to use; if NULL only the global - * configuration is parsed and applied (if it haven't been + * configuration is parsed and applied (if it hasn't been * processed before). * * @return 0 on success, < 0 on error. diff --git a/src/packet.c b/src/packet.c index 93591565..8508c731 100644 --- a/src/packet.c +++ b/src/packet.c @@ -113,7 +113,7 @@ static ssh_packet_callback default_packet_handlers[]= { ssh_packet_global_request, // SSH2_MSG_GLOBAL_REQUEST 80 #else /* WITH_SERVER */ NULL, -#endif /* WITH_SERVER */ +#endif /* WITH_SERVER */ ssh_request_success, // SSH2_MSG_REQUEST_SUCCESS 81 ssh_request_denied, // SSH2_MSG_REQUEST_FAILURE 82 NULL, NULL, NULL, NULL, NULL, NULL, NULL,// 83-89 @@ -363,9 +363,14 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se * Transitions: * - session->dh_handshake_state = DH_STATE_INIT_SENT * then calls dh_handshake_server which triggers: - * - session->dh_handhsake_state = DH_STATE_NEWKEYS_SENT + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT * */ + if (!session->server) { + rc = SSH_PACKET_DENIED; + break; + } + if (session->session_state != SSH_SESSION_STATE_DH) { rc = SSH_PACKET_DENIED; break; @@ -391,7 +396,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se * or dh_handshake_state == DH_STATE_REQUEST_SENT (dh-gex) * * Transitions: - * - session->dh_handhsake_state = DH_STATE_NEWKEYS_SENT + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT * */ if (session->session_state != SSH_SESSION_STATE_DH) { @@ -425,7 +430,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se /* * States required: * - session_state == SSH_SESSION_STATE_AUTHENTICATING - * - dh_hanshake_state == DH_STATE_FINISHED + * - dh_handshake_state == DH_STATE_FINISHED * * Transitions: * - if authentication was successful: @@ -454,7 +459,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se /* * States required: * - session_state == SSH_SESSION_STATE_AUTHENTICATING - * - dh_hanshake_state == DH_STATE_FINISHED + * - dh_handshake_state == DH_STATE_FINISHED * - session->auth.state == SSH_AUTH_STATE_KBDINT_SENT * or session->auth.state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT * or session->auth.state == SSH_AUTH_STATE_PUBKEY_AUTH_SENT @@ -492,7 +497,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se /* * States required: * - session_state == SSH_SESSION_STATE_AUTHENTICATING - * - dh_hanshake_state == DH_STATE_FINISHED + * - dh_handshake_state == DH_STATE_FINISHED * - session->auth.state == SSH_AUTH_STATE_KBDINT_SENT * or session->auth.state == SSH_AUTH_STATE_PUBKEY_AUTH_SENT * or session->auth.state == SSH_AUTH_STATE_PASSWORD_AUTH_SENT @@ -1052,24 +1057,26 @@ static bool ssh_packet_need_rekey(ssh_session session, * @param user pointer to current ssh_session * @param data pointer to the data received * @len length of data received. It might not be enough for a complete packet - * @returns number of bytes read and processed. + * @returns number of bytes read and processed. Zero means only partial packet + * received and negative value means error. */ -int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) +size_t +ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) { ssh_session session = (ssh_session)user; uint32_t blocksize = 8; uint32_t lenfield_blocksize = 8; size_t current_macsize = 0; uint8_t *ptr = NULL; - int to_be_read; + long to_be_read; int rc; uint8_t *cleartext_packet = NULL; uint8_t *packet_second_block = NULL; uint8_t *mac = NULL; - size_t packet_remaining; + size_t packet_remaining, packet_offset; uint32_t packet_len, compsize, payloadsize; uint8_t padding; - size_t processed = 0; /* number of byte processed from the callback */ + size_t processed = 0; /* number of bytes processed from the callback */ enum ssh_packet_filter_result_e filter_result; struct ssh_crypto_struct *crypto = NULL; bool etm = false; @@ -1112,7 +1119,7 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) session->packet_state == PACKET_STATE_PROCESSING ? "PROCESSING" : "unknown"); #endif - switch(session->packet_state) { + switch (session->packet_state) { case PACKET_STATE_INIT: if (receivedlen < lenfield_blocksize + etm_packet_offset) { /* @@ -1145,11 +1152,13 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) } if (!etm) { - ptr = ssh_buffer_allocate(session->in_buffer, lenfield_blocksize); + ptr = ssh_buffer_allocate(session->in_buffer, + lenfield_blocksize); if (ptr == NULL) { goto error; } - packet_len = ssh_packet_decrypt_len(session, ptr, (uint8_t *)data); + packet_len = ssh_packet_decrypt_len(session, ptr, + (uint8_t *)data); to_be_read = packet_len - lenfield_blocksize + sizeof(uint32_t); } else { /* Length is unencrypted in case of Encrypt-then-MAC */ @@ -1161,7 +1170,7 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) if (packet_len > MAX_PACKET_LEN) { ssh_set_error(session, SSH_FATAL, - "read_packet(): Packet len too high(%u %.4x)", + "read_packet(): Packet len too high(%" PRIu32 " %.4" PRIx32 ")", packet_len, packet_len); goto error; } @@ -1169,7 +1178,7 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) /* remote sshd sends invalid sizes? */ ssh_set_error(session, SSH_FATAL, - "Given numbers of bytes left to be read < 0 (%d)!", + "Given numbers of bytes left to be read < 0 (%ld)!", to_be_read); goto error; } @@ -1179,28 +1188,27 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) FALL_THROUGH; case PACKET_STATE_SIZEREAD: packet_len = session->in_packet.len; - processed = lenfield_blocksize + etm_packet_offset; + packet_offset = processed = lenfield_blocksize + etm_packet_offset; to_be_read = packet_len + sizeof(uint32_t) + current_macsize; /* if to_be_read is zero, the whole packet was blocksize bytes. */ if (to_be_read != 0) { - if (receivedlen < (unsigned int)to_be_read) { + if (receivedlen < (unsigned long)to_be_read) { /* give up, not enough data in buffer */ SSH_LOG(SSH_LOG_PACKET, "packet: partial packet (read len) " - "[len=%d, receivedlen=%d, to_be_read=%d]", + "[len=%" PRIu32 ", receivedlen=%zu, to_be_read=%ld]", packet_len, - (int)receivedlen, + receivedlen, to_be_read); return 0; } - packet_second_block = (uint8_t*)data + lenfield_blocksize + etm_packet_offset; + packet_second_block = (uint8_t*)data + packet_offset; processed = to_be_read - current_macsize; } /* remaining encrypted bytes from the packet, MAC not included */ - packet_remaining = - packet_len - (lenfield_blocksize - sizeof(uint32_t) + etm_packet_offset); + packet_remaining = packet_len - (packet_offset - sizeof(uint32_t)); cleartext_packet = ssh_buffer_allocate(session->in_buffer, packet_remaining); if (cleartext_packet == NULL) { @@ -1223,16 +1231,16 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) } } /* - * Decrypt the packet. In case of EtM mode, the length is already - * known as it's unencrypted. In the other case, lenfield_blocksize bytes - * already have been decrypted. + * Decrypt the packet. In case of EtM mode, the length is + * already known as it's unencrypted. In the other case, + * lenfield_blocksize bytes already have been decrypted. */ if (packet_remaining > 0) { rc = ssh_packet_decrypt(session, cleartext_packet, (uint8_t *)data, - lenfield_blocksize + etm_packet_offset, - processed - (lenfield_blocksize + etm_packet_offset)); + packet_offset, + processed - packet_offset); if (rc < 0) { ssh_set_error(session, SSH_FATAL, @@ -1242,9 +1250,10 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) } if (crypto->in_hmac != SSH_HMAC_NONE && !etm) { + ssh_buffer in = session->in_buffer; rc = ssh_packet_hmac_verify(session, - ssh_buffer_get(session->in_buffer), - ssh_buffer_get_len(session->in_buffer), + ssh_buffer_get(in), + ssh_buffer_get_len(in), mac, crypto->in_hmac); if (rc < 0) { @@ -1286,7 +1295,7 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) if (padding > ssh_buffer_get_len(session->in_buffer)) { ssh_set_error(session, SSH_FATAL, - "Invalid padding: %d (%d left)", + "Invalid padding: %d (%" PRIu32 " left)", padding, ssh_buffer_get_len(session->in_buffer)); goto error; @@ -1295,15 +1304,29 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) compsize = ssh_buffer_get_len(session->in_buffer); #ifdef WITH_ZLIB - if (crypto && crypto->do_compress_in - && ssh_buffer_get_len(session->in_buffer) > 0) { - rc = decompress_buffer(session, session->in_buffer,MAX_PACKET_LEN); + if (crypto && crypto->do_compress_in && + ssh_buffer_get_len(session->in_buffer) > 0) { + rc = decompress_buffer(session, session->in_buffer, + MAX_PACKET_LEN); if (rc < 0) { goto error; } } #endif /* WITH_ZLIB */ payloadsize = ssh_buffer_get_len(session->in_buffer); + if (session->recv_seq == UINT32_MAX) { + /* Overflowing sequence numbers is always fishy */ + if (crypto == NULL) { + /* don't allow sequence number overflow when unencrypted */ + ssh_set_error(session, + SSH_FATAL, + "Incoming sequence number overflow"); + goto error; + } else { + SSH_LOG(SSH_LOG_WARNING, + "Incoming sequence number overflow"); + } + } session->recv_seq++; if (crypto != NULL) { struct ssh_cipher_struct *cipher = NULL; @@ -1324,13 +1347,27 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) session->packet_state = PACKET_STATE_PROCESSING; ssh_packet_parse_type(session); SSH_LOG(SSH_LOG_PACKET, - "packet: read type %hhd [len=%d,padding=%hhd,comp=%d,payload=%d]", - session->in_packet.type, packet_len, padding, compsize, payloadsize); + "packet: read type %hhd [len=%" PRIu32 ",padding=%hhd," + "comp=%" PRIu32 ",payload=%" PRIu32 "]", + session->in_packet.type, packet_len, padding, compsize, + payloadsize); + if (crypto == NULL) { + /* In strict kex, only a few packets are allowed. Taint the session + * if we received packets that are normally allowed but to be + * refused if we are in strict kex when KEX is over. + */ + uint8_t type = session->in_packet.type; + if (type != SSH2_MSG_KEXINIT && type != SSH2_MSG_NEWKEYS && + (type < SSH2_MSG_KEXDH_INIT || + type > SSH2_MSG_KEX_DH_GEX_REQUEST)) { + session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; + } + } /* Check if the packet is expected */ filter_result = ssh_packet_incoming_filter(session); - switch(filter_result) { + switch (filter_result) { case SSH_PACKET_ALLOWED: /* Execute callbacks */ ssh_packet_process(session, session->in_packet.type); @@ -1342,6 +1379,9 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) session->in_packet.type); goto error; case SSH_PACKET_UNKNOWN: + if (crypto == NULL) { + session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; + } ssh_packet_send_unimplemented(session, session->recv_seq - 1); break; } @@ -1355,7 +1395,8 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) ptr = ((uint8_t*)data) + processed; - rc = ssh_packet_socket_callback(ptr, receivedlen - processed,user); + rc = ssh_packet_socket_callback(ptr, receivedlen - processed, + user); processed += rc; } @@ -1381,8 +1422,8 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) session->packet_state); error: - session->session_state= SSH_SESSION_STATE_ERROR; - SSH_LOG(SSH_LOG_PACKET,"Packet: processed %zu bytes", processed); + session->session_state = SSH_SESSION_STATE_ERROR; + SSH_LOG(SSH_LOG_PACKET, "Packet: processed %zu bytes", processed); return processed; } @@ -1410,18 +1451,23 @@ static void ssh_packet_socket_controlflow_callback(int code, void *userdata) } } -void ssh_packet_register_socket_callback(ssh_session session, ssh_socket s){ - session->socket_callbacks.data=ssh_packet_socket_callback; - session->socket_callbacks.connected=NULL; - session->socket_callbacks.controlflow = ssh_packet_socket_controlflow_callback; - session->socket_callbacks.userdata=session; - ssh_socket_set_callbacks(s,&session->socket_callbacks); +void ssh_packet_register_socket_callback(ssh_session session, ssh_socket s) +{ + struct ssh_socket_callbacks_struct *callbacks = &session->socket_callbacks; + + callbacks->data = ssh_packet_socket_callback; + callbacks->connected = NULL; + callbacks->controlflow = ssh_packet_socket_controlflow_callback; + callbacks->userdata = session; + ssh_socket_set_callbacks(s, callbacks); } /** @internal * @brief sets the callbacks for the packet layer */ -void ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks){ +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) { @@ -1435,8 +1481,11 @@ void ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callback /** @internal * @brief remove the callbacks from the packet layer */ -void ssh_packet_remove_callbacks(ssh_session session, ssh_packet_callbacks callbacks){ +void +ssh_packet_remove_callbacks(ssh_session session, ssh_packet_callbacks callbacks) +{ struct ssh_iterator *it = NULL; + it = ssh_list_find(session->packet_callbacks, callbacks); if (it != NULL) { ssh_list_remove(session->packet_callbacks, it); @@ -1446,12 +1495,15 @@ void ssh_packet_remove_callbacks(ssh_session session, ssh_packet_callbacks callb /** @internal * @brief sets the default packet handlers */ -void ssh_packet_set_default_callbacks(ssh_session session){ - session->default_packet_callbacks.start=1; - session->default_packet_callbacks.n_callbacks=sizeof(default_packet_handlers)/sizeof(ssh_packet_callback); - session->default_packet_callbacks.user=session; - session->default_packet_callbacks.callbacks=default_packet_handlers; - ssh_packet_set_callbacks(session, &session->default_packet_callbacks); +void ssh_packet_set_default_callbacks(ssh_session session) +{ + struct ssh_packet_callbacks_struct *c = &session->default_packet_callbacks; + + c->start = 1; + c->n_callbacks = sizeof(default_packet_handlers) / sizeof(ssh_packet_callback); + c->user = session; + c->callbacks = default_packet_handlers; + ssh_packet_set_callbacks(session, c); } /** @internal @@ -1505,7 +1557,33 @@ void ssh_packet_process(ssh_session session, uint8_t type) SSH_LOG(SSH_LOG_RARE, "Failed to send unimplemented: %s", ssh_get_error(session)); } + if (session->current_crypto == NULL) { + session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; + } + } +} + +/** @internal + * @brief sends a SSH_MSG_NEWKEYS when enabling the new negotiated ciphers + * @param session the SSH session + * @return SSH_ERROR on error, else SSH_OK + */ +int ssh_packet_send_newkeys(ssh_session session) +{ + int rc; + + /* Send the MSG_NEWKEYS */ + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); + if (rc < 0) { + return rc; + } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return rc; } + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); + return rc; } /** @internal @@ -1543,12 +1621,12 @@ SSH_PACKET_CALLBACK(ssh_packet_unimplemented){ rc = ssh_buffer_unpack(packet, "d", &seq); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARNING, + SSH_LOG(SSH_LOG_TRACE, "Could not unpack SSH_MSG_UNIMPLEMENTED packet"); } SSH_LOG(SSH_LOG_RARE, - "Received SSH_MSG_UNIMPLEMENTED (sequence number %d)",seq); + "Received SSH_MSG_UNIMPLEMENTED (sequence number %" PRIu32 ")",seq); return SSH_PACKET_USED; } @@ -1714,8 +1792,8 @@ static int packet_send2(ssh_session session) } SSH_LOG(SSH_LOG_PACKET, - "packet: wrote [type=%u, len=%u, padding_size=%hhd, comp=%u, " - "payload=%u]", + "packet: wrote [type=%u, len=%" PRIu32 ", padding_size=%hhd, comp=%" PRIu32 ", " + "payload=%" PRIu32 "]", type, finallen, padding_size, @@ -1755,10 +1833,12 @@ static bool ssh_packet_in_rekey(ssh_session session) { /* We know we are rekeying if we are authenticated and the DH - * status is not finished + * status is not finished, but we only queue packets until we've + * sent our NEWKEYS. */ return (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) && - (session->dh_handshake_state != DH_STATE_FINISHED); + (session->dh_handshake_state != DH_STATE_FINISHED) && + (session->dh_handshake_state != DH_STATE_NEWKEYS_SENT); } int ssh_packet_send(ssh_session session) @@ -1799,7 +1879,7 @@ int ssh_packet_send(ssh_session session) if (need_rekey) { /* Send the KEXINIT packet instead. - * This recursivelly calls the packet_send(), but it should + * This recursively calls the packet_send(), but it should * not get into rekeying again. * After that we need to handle the key exchange responses * up to the point where we can send the rest of the queue. @@ -1816,6 +1896,10 @@ int ssh_packet_send(ssh_session session) if (rc == SSH_OK && type == SSH2_MSG_NEWKEYS) { struct ssh_iterator *it; + if (session->flags & SSH_SESSION_FLAG_KEX_STRICT) { + /* reset packet sequence number when running in strict kex mode */ + session->send_seq = 0; + } for (it = ssh_list_get_iterator(session->out_queue); it != NULL; it = ssh_list_get_iterator(session->out_queue)) { @@ -1867,7 +1951,7 @@ ssh_init_rekey_state(struct ssh_session_struct *session, session->opts.rekey_data / cipher->blocksize); } - SSH_LOG(SSH_LOG_PROTOCOL, + SSH_LOG(SSH_LOG_DEBUG, "Set rekey after %" PRIu64 " blocks", cipher->max_blocks); } @@ -1895,7 +1979,7 @@ ssh_packet_set_newkeys(ssh_session session, session->next_crypto->used |= direction; if (session->current_crypto != NULL) { if (session->current_crypto->used & direction) { - SSH_LOG(SSH_LOG_WARNING, "This direction isn't used anymore."); + SSH_LOG(SSH_LOG_TRACE, "This direction isn't used anymore."); } /* Mark the current requested direction unused */ session->current_crypto->used &= ~direction; @@ -1903,7 +1987,7 @@ ssh_packet_set_newkeys(ssh_session session, /* Both sides switched: do the actual switch now */ if (session->next_crypto->used == SSH_DIRECTION_BOTH) { - size_t digest_len; + size_t session_id_len; if (session->current_crypto != NULL) { crypto_free(session->current_crypto); @@ -1920,8 +2004,8 @@ ssh_packet_set_newkeys(ssh_session session, return SSH_ERROR; } - digest_len = session->current_crypto->digest_len; - session->next_crypto->session_id = malloc(digest_len); + session_id_len = session->current_crypto->session_id_len; + session->next_crypto->session_id = malloc(session_id_len); if (session->next_crypto->session_id == NULL) { ssh_set_error_oom(session); return SSH_ERROR; @@ -1929,7 +2013,8 @@ ssh_packet_set_newkeys(ssh_session session, memcpy(session->next_crypto->session_id, session->current_crypto->session_id, - digest_len); + session_id_len); + session->next_crypto->session_id_len = session_id_len; return SSH_OK; } @@ -1968,7 +2053,7 @@ ssh_packet_set_newkeys(ssh_session session, ssh_init_rekey_state(session, in_cipher); if (session->opts.rekey_time != 0) { ssh_timestamp_init(&session->last_rekey_time); - SSH_LOG(SSH_LOG_PROTOCOL, "Set rekey after %" PRIu32 " seconds", + SSH_LOG(SSH_LOG_DEBUG, "Set rekey after %" PRIu32 " seconds", session->opts.rekey_time/1000); } diff --git a/src/packet_cb.c b/src/packet_cb.c index 39575b17..7edb6791 100644 --- a/src/packet_cb.c +++ b/src/packet_cb.c @@ -45,36 +45,48 @@ * * @brief Handle a SSH_DISCONNECT packet. */ -SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback){ - int rc; - uint32_t code = 0; - char *error = NULL; - ssh_string error_s; - (void)user; - (void)type; - - rc = ssh_buffer_get_u32(packet, &code); - if (rc != 0) { - code = ntohl(code); - } - - error_s = ssh_buffer_get_ssh_string(packet); - if (error_s != NULL) { - error = ssh_string_to_char(error_s); - SSH_STRING_FREE(error_s); - } - SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_DISCONNECT %d:%s", - code, error != NULL ? error : "no error"); - ssh_set_error(session, SSH_FATAL, - "Received SSH_MSG_DISCONNECT: %d:%s", - code, error != NULL ? error : "no error"); - SAFE_FREE(error); - - ssh_socket_close(session->socket); - session->alive = 0; - session->session_state = SSH_SESSION_STATE_ERROR; - /* TODO: handle a graceful disconnect */ - return SSH_PACKET_USED; +SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback) +{ + int rc; + uint32_t code = 0; + char *error = NULL; + ssh_string error_s = NULL; + + (void)user; + (void)type; + + rc = ssh_buffer_get_u32(packet, &code); + if (rc != 0) { + code = ntohl(code); + } + + error_s = ssh_buffer_get_ssh_string(packet); + if (error_s != NULL) { + error = ssh_string_to_char(error_s); + SSH_STRING_FREE(error_s); + } + + if (error != NULL) { + session->peer_discon_msg = strdup(error); + } + + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_MSG_DISCONNECT %" PRIu32 ":%s", + code, + error != NULL ? error : "no error"); + ssh_set_error(session, + SSH_FATAL, + "Received SSH_MSG_DISCONNECT: %" PRIu32 ":%s", + code, + error != NULL ? error : "no error"); + SAFE_FREE(error); + + ssh_session_socket_close(session); + /* correctly handle disconnect during authorization */ + session->auth.state = SSH_AUTH_STATE_FAILED; + + /* TODO: handle a graceful disconnect */ + return SSH_PACKET_USED; } /** @@ -82,99 +94,125 @@ SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback){ * * @brief Handle a SSH_IGNORE and SSH_DEBUG packet. */ -SSH_PACKET_CALLBACK(ssh_packet_ignore_callback){ +SSH_PACKET_CALLBACK(ssh_packet_ignore_callback) +{ (void)session; /* unused */ - (void)user; - (void)type; - (void)packet; - SSH_LOG(SSH_LOG_PROTOCOL,"Received %s packet",type==SSH2_MSG_IGNORE ? "SSH_MSG_IGNORE" : "SSH_MSG_DEBUG"); - /* TODO: handle a graceful disconnect */ - return SSH_PACKET_USED; + (void)user; + (void)type; + (void)packet; + + SSH_LOG(SSH_LOG_DEBUG, + "Received %s packet", + type == SSH2_MSG_IGNORE ? "SSH_MSG_IGNORE" : "SSH_MSG_DEBUG"); + + /* TODO: handle a graceful disconnect */ + return SSH_PACKET_USED; } -SSH_PACKET_CALLBACK(ssh_packet_newkeys){ - ssh_string sig_blob = NULL; - ssh_signature sig = NULL; - int rc; - (void)packet; - (void)user; - (void)type; - SSH_LOG(SSH_LOG_PROTOCOL, "Received SSH_MSG_NEWKEYS"); - - if (session->session_state != SSH_SESSION_STATE_DH || - session->dh_handshake_state != DH_STATE_NEWKEYS_SENT) { - ssh_set_error(session, - SSH_FATAL, - "ssh_packet_newkeys called in wrong state : %d:%d", - session->session_state,session->dh_handshake_state); - goto error; - } - - if(session->server){ - /* server things are done in server.c */ - session->dh_handshake_state=DH_STATE_FINISHED; - } else { - ssh_key server_key; - - /* client */ - - /* Verify the host's signature. FIXME do it sooner */ - sig_blob = session->next_crypto->dh_server_signature; - session->next_crypto->dh_server_signature = NULL; - - /* get the server public key */ - server_key = ssh_dh_get_next_server_publickey(session); - if (server_key == NULL) { - goto error; - } +SSH_PACKET_CALLBACK(ssh_packet_newkeys) +{ + ssh_string sig_blob = NULL; + ssh_signature sig = NULL; + int rc; - rc = ssh_pki_import_signature_blob(sig_blob, server_key, &sig); - ssh_string_burn(sig_blob); - SSH_STRING_FREE(sig_blob); - if (rc != SSH_OK) { + (void)packet; + (void)user; + (void)type; + + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_NEWKEYS"); + + if (session->session_state != SSH_SESSION_STATE_DH || + session->dh_handshake_state != DH_STATE_NEWKEYS_SENT) { + ssh_set_error(session, + SSH_FATAL, + "ssh_packet_newkeys called in wrong state : %d:%d", + session->session_state, + session->dh_handshake_state); goto error; } - /* Check if signature from server matches user preferences */ - if (session->opts.wanted_methods[SSH_HOSTKEYS]) { - if (!ssh_match_group(session->opts.wanted_methods[SSH_HOSTKEYS], - sig->type_c)) { + if (session->flags & SSH_SESSION_FLAG_KEX_STRICT) { + /* reset packet sequence number when running in strict kex mode */ + session->recv_seq = 0; + /* Check that we aren't tainted */ + if (session->flags & SSH_SESSION_FLAG_KEX_TAINTED) { ssh_set_error(session, SSH_FATAL, - "Public key from server (%s) doesn't match user " - "preference (%s)", - sig->type_c, - session->opts.wanted_methods[SSH_HOSTKEYS]); + "Received unexpected packets in strict KEX mode."); goto error; } } - rc = ssh_pki_signature_verify(session, - sig, - server_key, - session->next_crypto->secret_hash, - session->next_crypto->digest_len); - SSH_SIGNATURE_FREE(sig); - if (rc == SSH_ERROR) { - goto error; - } - SSH_LOG(SSH_LOG_PROTOCOL,"Signature verified and valid"); + if (session->server) { + /* server things are done in server.c */ + session->dh_handshake_state=DH_STATE_FINISHED; + } else { + ssh_key server_key = NULL; - /* When receiving this packet, we switch on the incomming crypto. */ - rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); - if (rc != SSH_OK) { - goto error; + /* client */ + + /* Verify the host's signature. FIXME do it sooner */ + sig_blob = session->next_crypto->dh_server_signature; + session->next_crypto->dh_server_signature = NULL; + + /* get the server public key */ + server_key = ssh_dh_get_next_server_publickey(session); + if (server_key == NULL) { + goto error; + } + + rc = ssh_pki_import_signature_blob(sig_blob, server_key, &sig); + ssh_string_burn(sig_blob); + SSH_STRING_FREE(sig_blob); + if (rc != SSH_OK) { + goto error; + } + + /* Check if signature from server matches user preferences */ + if (session->opts.wanted_methods[SSH_HOSTKEYS]) { + rc = ssh_match_group(session->opts.wanted_methods[SSH_HOSTKEYS], + sig->type_c); + if (rc == 0) { + ssh_set_error(session, + SSH_FATAL, + "Public key from server (%s) doesn't match user " + "preference (%s)", + sig->type_c, + session->opts.wanted_methods[SSH_HOSTKEYS]); + goto error; + } + } + + rc = ssh_pki_signature_verify(session, + sig, + server_key, + session->next_crypto->secret_hash, + session->next_crypto->digest_len); + SSH_SIGNATURE_FREE(sig); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Failed to verify server hostkey signature"); + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Signature verified and valid"); + + /* When receiving this packet, we switch on the incoming crypto. */ + rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); + if (rc != SSH_OK) { + goto error; + } } - } - session->dh_handshake_state = DH_STATE_FINISHED; - session->ssh_connection_callback(session); - return SSH_PACKET_USED; + session->dh_handshake_state = DH_STATE_FINISHED; + session->ssh_connection_callback(session); + return SSH_PACKET_USED; + error: - SSH_SIGNATURE_FREE(sig); - ssh_string_burn(sig_blob); - SSH_STRING_FREE(sig_blob); - session->session_state = SSH_SESSION_STATE_ERROR; - return SSH_PACKET_USED; + SSH_SIGNATURE_FREE(sig); + ssh_string_burn(sig_blob); + SSH_STRING_FREE(sig_blob); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; } /** @@ -182,16 +220,16 @@ error: * @brief handles a SSH_SERVICE_ACCEPT packet * */ -SSH_PACKET_CALLBACK(ssh_packet_service_accept){ - (void)packet; - (void)type; - (void)user; +SSH_PACKET_CALLBACK(ssh_packet_service_accept) +{ + (void)packet; + (void)type; + (void)user; session->auth.service_state = SSH_AUTH_SERVICE_ACCEPTED; - SSH_LOG(SSH_LOG_PACKET, - "Received SSH_MSG_SERVICE_ACCEPT"); + SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_SERVICE_ACCEPT"); - return SSH_PACKET_USED; + return SSH_PACKET_USED; } /** @@ -204,6 +242,7 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info) int rc; uint32_t nr_extensions = 0; uint32_t i; + (void)type; (void)user; @@ -221,7 +260,7 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info) return SSH_PACKET_USED; } - SSH_LOG(SSH_LOG_PACKET, "Follows %u extensions", nr_extensions); + SSH_LOG(SSH_LOG_PACKET, "Follows %" PRIu32 " extensions", nr_extensions); for (i = 0; i < nr_extensions; i++) { char *name = NULL; @@ -244,6 +283,8 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info) if (ssh_match_group(value, "rsa-sha2-256")) { session->extensions |= SSH_EXT_SIG_RSA_SHA256; } + } else { + SSH_LOG(SSH_LOG_PACKET, "Unknown extension: %s", name); } free(name); free(value); diff --git a/src/packet_crypt.c b/src/packet_crypt.c index 734ccafc..fe3f489e 100644 --- a/src/packet_crypt.c +++ b/src/packet_crypt.c @@ -130,14 +130,15 @@ int ssh_packet_decrypt(ssh_session session, return 0; } -unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_t len) +unsigned char *ssh_packet_encrypt(ssh_session session, void *data, size_t len) { struct ssh_crypto_struct *crypto = NULL; struct ssh_cipher_struct *cipher = NULL; HMACCTX ctx = NULL; char *out = NULL; - int etm_packet_offset = 0; - unsigned int finallen, blocksize; + int etm_packet_offset = 0, rc; + unsigned int blocksize; + size_t finallen = DIGEST_MAX_LEN; uint32_t seq, lenfield_blocksize; enum ssh_hmac_e type; bool etm; @@ -161,7 +162,7 @@ unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_t len) if ((len - lenfield_blocksize - etm_packet_offset) % blocksize != 0) { ssh_set_error(session, SSH_FATAL, "Cryptographic functions must be set" - " on at least one blocksize (received %d)", len); + " on at least one blocksize (received %zu)", len); return NULL; } out = calloc(1, len); @@ -185,9 +186,21 @@ unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_t len) } if (!etm) { - hmac_update(ctx, (unsigned char *)&seq, sizeof(uint32_t)); - hmac_update(ctx, data, len); - hmac_final(ctx, crypto->hmacbuf, &finallen); + rc = hmac_update(ctx, (unsigned char *)&seq, sizeof(uint32_t)); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_update(ctx, data, len); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_final(ctx, crypto->hmacbuf, &finallen); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } } } @@ -197,14 +210,26 @@ unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_t len) if (type != SSH_HMAC_NONE) { if (etm) { PUSH_BE_U32(data, 0, len - etm_packet_offset); - hmac_update(ctx, (unsigned char *)&seq, sizeof(uint32_t)); - hmac_update(ctx, data, len); - hmac_final(ctx, crypto->hmacbuf, &finallen); + rc = hmac_update(ctx, (unsigned char *)&seq, sizeof(uint32_t)); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_update(ctx, data, len); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_final(ctx, crypto->hmacbuf, &finallen); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } } #ifdef DEBUG_CRYPTO ssh_log_hexdump("mac: ", data, len); if (finallen != hmac_digest_len(type)) { - printf("Final len is %d\n", finallen); + printf("Final len is %zu\n", finallen); } ssh_log_hexdump("Packet hmac", crypto->hmacbuf, hmac_digest_len(type)); #endif @@ -235,42 +260,71 @@ int ssh_packet_hmac_verify(ssh_session session, uint8_t *mac, enum ssh_hmac_e type) { - struct ssh_crypto_struct *crypto = NULL; - unsigned char hmacbuf[DIGEST_MAX_LEN] = {0}; - HMACCTX ctx; - unsigned int hmaclen; - uint32_t seq; - - /* AEAD types have no mac checking */ - if (type == SSH_HMAC_AEAD_POLY1305 || - type == SSH_HMAC_AEAD_GCM) { - return SSH_OK; - } + struct ssh_crypto_struct *crypto = NULL; + unsigned char hmacbuf[DIGEST_MAX_LEN] = {0}; + HMACCTX ctx; + size_t hmaclen = DIGEST_MAX_LEN; + uint32_t seq; + int cmp; + int rc; - crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); - if (crypto == NULL) { - return SSH_ERROR; - } + /* AEAD types have no mac checking */ + if (type == SSH_HMAC_AEAD_POLY1305 || + type == SSH_HMAC_AEAD_GCM) { + return SSH_OK; + } - ctx = hmac_init(crypto->decryptMAC, hmac_digest_len(type), type); - if (ctx == NULL) { - return -1; - } + crypto = ssh_packet_get_current_crypto(session, + SSH_DIRECTION_IN); + if (crypto == NULL) { + return SSH_ERROR; + } - seq = htonl(session->recv_seq); + ctx = hmac_init(crypto->decryptMAC, + hmac_digest_len(type), + type); + if (ctx == NULL) { + return SSH_ERROR; + } - hmac_update(ctx, (unsigned char *) &seq, sizeof(uint32_t)); - hmac_update(ctx, data, len); - hmac_final(ctx, hmacbuf, &hmaclen); + seq = htonl(session->recv_seq); + + rc = hmac_update(ctx, + (unsigned char *) &seq, + sizeof(uint32_t)); + if (rc != 1) { + return SSH_ERROR; + } + rc = hmac_update(ctx, + data, + len); + if (rc != 1) { + return SSH_ERROR; + } + rc = hmac_final(ctx, + hmacbuf, + &hmaclen); + if (rc != 1) { + return SSH_ERROR; + } #ifdef DEBUG_CRYPTO - ssh_log_hexdump("received mac",mac,hmaclen); - ssh_log_hexdump("Computed mac",hmacbuf,hmaclen); - ssh_log_hexdump("seq",(unsigned char *)&seq,sizeof(uint32_t)); + ssh_log_hexdump("received mac", + mac, + hmaclen); + ssh_log_hexdump("Computed mac", + hmacbuf, + hmaclen); + ssh_log_hexdump("seq", + (unsigned char *)&seq, + sizeof(uint32_t)); #endif - if (secure_memcmp(mac, hmacbuf, hmaclen) == 0) { - return 0; - } + cmp = secure_memcmp(mac, + hmacbuf, + hmaclen); + if (cmp == 0) { + return SSH_OK; + } - return -1; + return SSH_ERROR; } @@ -44,14 +44,11 @@ #include "libssh/socket.h" /** - * @internal - * * @defgroup libssh_pcap The libssh pcap functions * @ingroup libssh * * The pcap file generation * - * * @{ */ @@ -63,7 +60,7 @@ struct pcap_hdr_s { uint32_t magic_number; /* magic number */ uint16_t version_major; /* major version number */ uint16_t version_minor; /* minor version number */ - int32_t thiszone; /* GMT to local correction */ + int32_t thiszone; /* GMT to local correction */ uint32_t sigfigs; /* accuracy of timestamps */ uint32_t snaplen; /* max length of captured packets, in octets */ uint32_t network; /* data link type */ @@ -98,12 +95,11 @@ struct pcaprec_hdr_s { * in a SSH session only. Multiple pcap contexts may be used into * a single pcap file. */ - struct ssh_pcap_context_struct { ssh_session session; ssh_pcap_file file; int connected; - /* All of these information are useful to generate + /* All of this information is useful to generate * the dummy IP and TCP packets */ uint32_t ipsource; @@ -126,10 +122,11 @@ struct ssh_pcap_file_struct { /** * @brief create a new ssh_pcap_file object */ -ssh_pcap_file ssh_pcap_file_new(void) { - struct ssh_pcap_file_struct *pcap; +ssh_pcap_file ssh_pcap_file_new(void) +{ + struct ssh_pcap_file_struct *pcap = NULL; - pcap = (struct ssh_pcap_file_struct *) malloc(sizeof(struct ssh_pcap_file_struct)); + pcap = malloc(sizeof(struct ssh_pcap_file_struct)); if (pcap == NULL) { return NULL; } @@ -141,163 +138,182 @@ ssh_pcap_file ssh_pcap_file_new(void) { /** @internal * @brief writes a packet on file */ -static int ssh_pcap_file_write(ssh_pcap_file pcap, ssh_buffer packet){ - int err; - uint32_t len; - if(pcap == NULL || pcap->output==NULL) - return SSH_ERROR; - len=ssh_buffer_get_len(packet); - err=fwrite(ssh_buffer_get(packet),len,1,pcap->output); - if(err<0) - return SSH_ERROR; - else - return SSH_OK; +static int ssh_pcap_file_write(ssh_pcap_file pcap, ssh_buffer packet) +{ + int err; + uint32_t len; + if (pcap == NULL || pcap->output == NULL) { + return SSH_ERROR; + } + len = ssh_buffer_get_len(packet); + err = fwrite(ssh_buffer_get(packet), len, 1, pcap->output); + if (err < 0) { + return SSH_ERROR; + } else { + return SSH_OK; + } } /** @internal * @brief prepends a packet with the pcap header and writes packet * on file */ -int ssh_pcap_file_write_packet(ssh_pcap_file pcap, ssh_buffer packet, uint32_t original_len){ - ssh_buffer header=ssh_buffer_new(); - struct timeval now; - int err; - if(header == NULL) - return SSH_ERROR; - gettimeofday(&now,NULL); +int ssh_pcap_file_write_packet(ssh_pcap_file pcap, ssh_buffer packet, uint32_t original_len) +{ + ssh_buffer header = ssh_buffer_new(); + struct timeval now; + int err; + + if (header == NULL) { + return SSH_ERROR; + } + + gettimeofday(&now, NULL); err = ssh_buffer_allocate_size(header, sizeof(uint32_t) * 4 + ssh_buffer_get_len(packet)); if (err < 0) { goto error; } - err = ssh_buffer_add_u32(header,htonl(now.tv_sec)); + err = ssh_buffer_add_u32(header, htonl(now.tv_sec)); if (err < 0) { goto error; } - err = ssh_buffer_add_u32(header,htonl(now.tv_usec)); + err = ssh_buffer_add_u32(header, htonl(now.tv_usec)); if (err < 0) { goto error; } - err = ssh_buffer_add_u32(header,htonl(ssh_buffer_get_len(packet))); + err = ssh_buffer_add_u32(header, htonl(ssh_buffer_get_len(packet))); if (err < 0) { goto error; } - err = ssh_buffer_add_u32(header,htonl(original_len)); + err = ssh_buffer_add_u32(header, htonl(original_len)); if (err < 0) { goto error; } - err = ssh_buffer_add_buffer(header,packet); + err = ssh_buffer_add_buffer(header, packet); if (err < 0) { goto error; } - err=ssh_pcap_file_write(pcap,header); + err = ssh_pcap_file_write(pcap, header); error: - SSH_BUFFER_FREE(header); - return err; + SSH_BUFFER_FREE(header); + return err; } /** - * @brief opens a new pcap file and create header + * @brief opens a new pcap file and creates header */ -int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename){ - ssh_buffer header; - int err; - if(pcap == NULL) - return SSH_ERROR; - if(pcap->output){ - fclose(pcap->output); - pcap->output=NULL; - } - pcap->output=fopen(filename,"wb"); - if(pcap->output==NULL) - return SSH_ERROR; - header=ssh_buffer_new(); - if(header==NULL) - return SSH_ERROR; +int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename) +{ + ssh_buffer header = NULL; + int err; + + if (pcap == NULL) { + return SSH_ERROR; + } + if (pcap->output) { + fclose(pcap->output); + pcap->output = NULL; + } + pcap->output = fopen(filename, "wb"); + if (pcap->output == NULL) { + return SSH_ERROR; + } + header = ssh_buffer_new(); + if (header == NULL) { + return SSH_ERROR; + } err = ssh_buffer_allocate_size(header, sizeof(uint32_t) * 5 + sizeof(uint16_t) * 2); if (err < 0) { goto error; } - err = ssh_buffer_add_u32(header,htonl(PCAP_MAGIC)); + err = ssh_buffer_add_u32(header, htonl(PCAP_MAGIC)); if (err < 0) { goto error; } - err = ssh_buffer_add_u16(header,htons(PCAP_VERSION_MAJOR)); + err = ssh_buffer_add_u16(header, htons(PCAP_VERSION_MAJOR)); if (err < 0) { goto error; } - err = ssh_buffer_add_u16(header,htons(PCAP_VERSION_MINOR)); + err = ssh_buffer_add_u16(header, htons(PCAP_VERSION_MINOR)); if (err < 0) { goto error; } - /* currently hardcode GMT to 0 */ - err = ssh_buffer_add_u32(header,htonl(0)); + /* currently hardcode GMT to 0 */ + err = ssh_buffer_add_u32(header, htonl(0)); if (err < 0) { goto error; } - /* accuracy */ - err = ssh_buffer_add_u32(header,htonl(0)); + /* accuracy */ + err = ssh_buffer_add_u32(header, htonl(0)); if (err < 0) { goto error; } - /* size of the biggest packet */ - err = ssh_buffer_add_u32(header,htonl(MAX_PACKET_LEN)); + /* size of the biggest packet */ + err = ssh_buffer_add_u32(header, htonl(MAX_PACKET_LEN)); if (err < 0) { goto error; } - /* we will write sort-of IP */ - err = ssh_buffer_add_u32(header,htonl(DLT_RAW)); + /* we will write sort-of IP */ + err = ssh_buffer_add_u32(header, htonl(DLT_RAW)); if (err < 0) { goto error; } - err=ssh_pcap_file_write(pcap,header); + err = ssh_pcap_file_write(pcap,header); error: - SSH_BUFFER_FREE(header); - return err; + SSH_BUFFER_FREE(header); + return err; } -int ssh_pcap_file_close(ssh_pcap_file pcap){ - int err; - if(pcap ==NULL || pcap->output==NULL) - return SSH_ERROR; - err=fclose(pcap->output); - pcap->output=NULL; - if(err != 0) - return SSH_ERROR; - else - return SSH_OK; +int ssh_pcap_file_close(ssh_pcap_file pcap) +{ + int err; + + if (pcap == NULL || pcap->output == NULL) { + return SSH_ERROR; + } + err = fclose(pcap->output); + pcap->output = NULL; + if (err != 0) { + return SSH_ERROR; + } else { + return SSH_OK; + } } -void ssh_pcap_file_free(ssh_pcap_file pcap){ - ssh_pcap_file_close(pcap); - SAFE_FREE(pcap); +void ssh_pcap_file_free(ssh_pcap_file pcap) +{ + ssh_pcap_file_close(pcap); + SAFE_FREE(pcap); } /** @internal * @brief allocates a new ssh_pcap_context object */ - -ssh_pcap_context ssh_pcap_context_new(ssh_session session){ - ssh_pcap_context ctx = (struct ssh_pcap_context_struct *) malloc(sizeof(struct ssh_pcap_context_struct)); - if(ctx==NULL){ - ssh_set_error_oom(session); - return NULL; - } - ZERO_STRUCTP(ctx); - ctx->session=session; - return ctx; +ssh_pcap_context ssh_pcap_context_new(ssh_session session) +{ + ssh_pcap_context ctx = (struct ssh_pcap_context_struct *)malloc(sizeof(struct ssh_pcap_context_struct)); + if (ctx == NULL) { + ssh_set_error_oom(session); + return NULL; + } + ZERO_STRUCTP(ctx); + ctx->session = session; + return ctx; } -void ssh_pcap_context_free(ssh_pcap_context ctx){ - SAFE_FREE(ctx); +void ssh_pcap_context_free(ssh_pcap_context ctx) +{ + SAFE_FREE(ctx); } -void ssh_pcap_context_set_file(ssh_pcap_context ctx, ssh_pcap_file pcap){ - ctx->file=pcap; +void ssh_pcap_context_set_file(ssh_pcap_context ctx, ssh_pcap_file pcap) +{ + ctx->file = pcap; } /** @internal @@ -315,6 +331,7 @@ static int ssh_pcap_context_connect(ssh_pcap_context ctx) socket_t fd; socklen_t len; int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; if (session == NULL) { return SSH_ERROR; @@ -337,7 +354,7 @@ static int ssh_pcap_context_connect(ssh_pcap_context ctx) ssh_set_error(session, SSH_REQUEST_DENIED, "Getting local IP address: %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } @@ -347,7 +364,7 @@ static int ssh_pcap_context_connect(ssh_pcap_context ctx) ssh_set_error(session, SSH_REQUEST_DENIED, "Getting remote IP address: %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } @@ -383,7 +400,7 @@ static int ssh_pcap_context_connect(ssh_pcap_context ctx) */ int ssh_pcap_context_write(ssh_pcap_context ctx, enum ssh_pcap_direction direction, - void *data, + void *data, uint32_t len, uint32_t origlen) { @@ -417,7 +434,7 @@ int ssh_pcap_context_write(ssh_pcap_context ctx, 0); /* checksum */ ctx->file->ipsequence++; - if (rc != SSH_OK){ + if (rc != SSH_OK) { goto error; } if (direction == SSH_PCAP_DIR_OUT) { @@ -506,23 +523,25 @@ error: /** @brief sets the pcap file used to trace the session * @param current session - * @param pcap an handler to a pcap file. A pcap file may be used in several + * @param pcap a handler to a pcap file. A pcap file may be used in several * sessions. * @returns SSH_ERROR in case of error, SSH_OK otherwise. */ -int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcap){ - ssh_pcap_context ctx=ssh_pcap_context_new(session); - if(ctx==NULL){ - ssh_set_error_oom(session); - return SSH_ERROR; - } - ctx->file=pcap; - if(session->pcap_ctx) - ssh_pcap_context_free(session->pcap_ctx); - session->pcap_ctx=ctx; - return SSH_OK; +int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcap) +{ + ssh_pcap_context ctx = ssh_pcap_context_new(session); + if (ctx == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + ctx->file = pcap; + if (session->pcap_ctx) { + ssh_pcap_context_free(session->pcap_ctx); + } + session->pcap_ctx = ctx; + return SSH_OK; } - +/** @} */ #else /* WITH_PCAP */ @@ -531,30 +550,36 @@ int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcap){ #include "libssh/libssh.h" #include "libssh/priv.h" -int ssh_pcap_file_close(ssh_pcap_file pcap){ - (void) pcap; - return SSH_ERROR; +int ssh_pcap_file_close(ssh_pcap_file pcap) +{ + (void)pcap; + + return SSH_ERROR; } -void ssh_pcap_file_free(ssh_pcap_file pcap){ - (void) pcap; +void ssh_pcap_file_free(ssh_pcap_file pcap) +{ + (void)pcap; } -ssh_pcap_file ssh_pcap_file_new(void){ - return NULL; +ssh_pcap_file ssh_pcap_file_new(void) +{ + return NULL; } -int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename){ - (void) pcap; - (void) filename; - return SSH_ERROR; +int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename) +{ + (void)pcap; + (void)filename; + + return SSH_ERROR; } -int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcapfile){ - (void) pcapfile; - ssh_set_error(session,SSH_REQUEST_DENIED,"Pcap support not compiled in"); - return SSH_ERROR; +int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcapfile) +{ + (void)pcapfile; + + ssh_set_error(session, SSH_REQUEST_DENIED, "Pcap support not compiled in"); + return SSH_ERROR; } #endif - -/** @} */ @@ -43,20 +43,6 @@ #include <sys/stat.h> #include <sys/types.h> -#ifdef _WIN32 -# ifdef HAVE_IO_H -# include <io.h> -# undef open -# define open _open -# undef close -# define close _close -# undef read -# define read _read -# undef unlink -# define unlink _unlink -# endif /* HAVE_IO_H */ -#endif - #include "libssh/libssh.h" #include "libssh/session.h" #include "libssh/priv.h" @@ -69,7 +55,7 @@ #ifndef MAX_LINE_SIZE #define MAX_LINE_SIZE 4096 -#endif +#endif /* NOT MAX_LINE_SIZE */ #define PKCS11_URI "pkcs11:" @@ -77,11 +63,6 @@ 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; @@ -117,22 +98,31 @@ const char *ssh_pki_key_ecdsa_name(const ssh_key key) return pki_key_ecdsa_nid_to_name(key->ecdsa_nid); #else return NULL; -#endif +#endif /* HAVE_ECC */ } /** * @brief creates a new empty SSH key + * * @returns an empty ssh_key handle, or NULL on error. */ -ssh_key ssh_key_new (void) { - ssh_key ptr = malloc (sizeof (struct ssh_key_struct)); - if (ptr == NULL) { - return NULL; - } - ZERO_STRUCTP(ptr); - return ptr; +ssh_key ssh_key_new (void) +{ + ssh_key ptr = malloc (sizeof (struct ssh_key_struct)); + if (ptr == NULL) { + return NULL; + } + ZERO_STRUCTP(ptr); + return ptr; } +/** + * @brief duplicates the key + * + * @param key An ssh_key to duplicate + * + * @return A duplicated ssh_key key + */ ssh_key ssh_key_dup(const ssh_key key) { if (key == NULL) { @@ -146,39 +136,22 @@ ssh_key ssh_key_dup(const ssh_key key) * @brief clean up the key and deallocate all existing keys * @param[in] key ssh_key to clean */ -void ssh_key_clean (ssh_key key){ - if(key == NULL) +void ssh_key_clean (ssh_key key) +{ + if (key == NULL) return; -#ifdef HAVE_LIBGCRYPT - if(key->dsa) gcry_sexp_release(key->dsa); - if(key->rsa) gcry_sexp_release(key->rsa); - if(key->ecdsa) gcry_sexp_release(key->ecdsa); -#elif defined HAVE_LIBCRYPTO - if(key->dsa) DSA_free(key->dsa); - if(key->rsa) RSA_free(key->rsa); -#ifdef HAVE_OPENSSL_ECC - if(key->ecdsa) EC_KEY_free(key->ecdsa); -#endif /* HAVE_OPENSSL_ECC */ -#elif defined HAVE_LIBMBEDCRYPTO - if (key->rsa != NULL) { - mbedtls_pk_free(key->rsa); - SAFE_FREE(key->rsa); - } - if (key->ecdsa != NULL) { - mbedtls_ecdsa_free(key->ecdsa); - SAFE_FREE(key->ecdsa); - } -#endif + pki_key_clean(key); + if (key->ed25519_privkey != NULL){ -#ifdef HAVE_OPENSSL_ED25519 +#ifdef HAVE_LIBCRYPTO /* In OpenSSL implementation the private key is only the private * original seed. In the internal implementation the private key is the * concatenation of the original private seed with the public key.*/ explicit_bzero(key->ed25519_privkey, ED25519_KEY_LEN); #else explicit_bzero(key->ed25519_privkey, sizeof(ed25519_privkey)); -#endif +#endif /* HAVE_LIBCRYPTO*/ SAFE_FREE(key->ed25519_privkey); } SAFE_FREE(key->ed25519_pubkey); @@ -193,21 +166,19 @@ void ssh_key_clean (ssh_key key){ ssh_string_free(key->sk_application); } key->cert_type = SSH_KEYTYPE_UNKNOWN; - key->flags=SSH_KEY_FLAG_EMPTY; - key->type=SSH_KEYTYPE_UNKNOWN; + key->flags = SSH_KEY_FLAG_EMPTY; + key->type = SSH_KEYTYPE_UNKNOWN; key->ecdsa_nid = 0; - key->type_c=NULL; - key->dsa = NULL; - key->rsa = NULL; - key->ecdsa = NULL; + key->type_c = NULL; } /** * @brief deallocate a SSH key * @param[in] key ssh_key handle to free */ -void ssh_key_free (ssh_key key){ - if(key){ +void ssh_key_free (ssh_key key) +{ + if (key) { ssh_key_clean(key); SAFE_FREE(key); } @@ -216,15 +187,16 @@ void ssh_key_free (ssh_key key){ /** * @brief returns the type of a ssh key * @param[in] key the ssh_key handle - * @returns one of SSH_KEYTYPE_RSA, SSH_KEYTYPE_DSS, + * @returns one of SSH_KEYTYPE_RSA, * SSH_KEYTYPE_ECDSA_P256, SSH_KEYTYPE_ECDSA_P384, - * SSH_KEYTYPE_ECDSA_P521, SSH_KEYTYPE_ED25519, SSH_KEYTYPE_DSS_CERT01, + * SSH_KEYTYPE_ECDSA_P521, SSH_KEYTYPE_ED25519, * SSH_KEYTYPE_RSA_CERT01, SSH_KEYTYPE_ECDSA_P256_CERT01, * SSH_KEYTYPE_ECDSA_P384_CERT01, SSH_KEYTYPE_ECDSA_P521_CERT01, or * SSH_KEYTYPE_ED25519_CERT01. * @returns SSH_KEYTYPE_UNKNOWN if the type is unknown */ -enum ssh_keytypes_e ssh_key_type(const ssh_key key){ +enum ssh_keytypes_e ssh_key_type(const ssh_key key) +{ if (key == NULL) { return SSH_KEYTYPE_UNKNOWN; } @@ -236,6 +208,8 @@ enum ssh_keytypes_e ssh_key_type(const ssh_key key){ * * @param[in] type The algorithm type to convert. * + * @param[in] hash_type The hash type to convert + * * @return A string for the keytype or NULL if unknown. */ const char * @@ -286,8 +260,6 @@ ssh_key_signature_to_char(enum ssh_keytypes_e type, */ const char *ssh_key_type_to_char(enum ssh_keytypes_e type) { switch (type) { - case SSH_KEYTYPE_DSS: - return "ssh-dss"; case SSH_KEYTYPE_RSA: return "ssh-rsa"; case SSH_KEYTYPE_ECDSA: @@ -300,8 +272,6 @@ const char *ssh_key_type_to_char(enum ssh_keytypes_e type) { return "ecdsa-sha2-nistp521"; case SSH_KEYTYPE_ED25519: return "ssh-ed25519"; - case SSH_KEYTYPE_DSS_CERT01: - return "ssh-dss-cert-v01@openssh.com"; case SSH_KEYTYPE_RSA_CERT01: return "ssh-rsa-cert-v01@openssh.com"; case SSH_KEYTYPE_ECDSA_P256_CERT01: @@ -320,7 +290,9 @@ const char *ssh_key_type_to_char(enum ssh_keytypes_e type) { return "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com"; case SSH_KEYTYPE_SK_ED25519_CERT01: return "sk-ssh-ed25519-cert-v01@openssh.com"; + case SSH_KEYTYPE_DSS: /* deprecated */ case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ case SSH_KEYTYPE_UNKNOWN: return NULL; } @@ -338,8 +310,6 @@ enum ssh_digest_e ssh_key_hash_from_name(const char *name) if (strcmp(name, "ssh-rsa") == 0) { return SSH_DIGEST_SHA1; - } else if (strcmp(name, "ssh-dss") == 0) { - return SSH_DIGEST_SHA1; } else if (strcmp(name, "rsa-sha2-256") == 0) { return SSH_DIGEST_SHA256; } else if (strcmp(name, "rsa-sha2-512") == 0) { @@ -358,7 +328,7 @@ enum ssh_digest_e ssh_key_hash_from_name(const char *name) return SSH_DIGEST_AUTO; } - SSH_LOG(SSH_LOG_WARN, "Unknown signature name %s", name); + SSH_LOG(SSH_LOG_TRACE, "Unknown signature name %s", name); /* TODO we should rather fail */ return SSH_DIGEST_AUTO; @@ -390,13 +360,13 @@ int ssh_key_algorithm_allowed(ssh_session session, const char *type) else if (session->server) { allowed_list = session->opts.wanted_methods[SSH_HOSTKEYS]; if (allowed_list == NULL) { - SSH_LOG(SSH_LOG_WARN, "Session invalid: no host key available"); + SSH_LOG(SSH_LOG_TRACE, "Session invalid: no host key available"); return 0; } } -#endif +#endif /* WITH_SERVER */ else { - SSH_LOG(SSH_LOG_WARN, "Session invalid: not set as client nor server"); + SSH_LOG(SSH_LOG_TRACE, "Session invalid: not set as client nor server"); return 0; } @@ -404,6 +374,42 @@ int ssh_key_algorithm_allowed(ssh_session session, const char *type) return ssh_match_group(allowed_list, type); } +bool ssh_key_size_allowed_rsa(int min_size, ssh_key key) +{ + int key_size = ssh_key_size(key); + + if (min_size < 768) { + if (ssh_fips_mode()) { + min_size = 2048; + } else { + min_size = 1024; + } + } + return (key_size >= min_size); +} + +/** + * @brief Check the given key is acceptable in regards to the key size policy + * specified by the configuration + * + * @param[in] session The SSH session + * @param[in] key The SSH key + * @returns true if the key is allowed, false otherwise + */ +bool ssh_key_size_allowed(ssh_session session, ssh_key key) +{ + int min_size = 0; + + switch (ssh_key_type(key)) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + min_size = session->opts.rsa_min_size; + return ssh_key_size_allowed_rsa(min_size, key); + default: + return true; + } +} + /** * @brief Convert a key type to a hash type. This is usually unambiguous * for all the key types, unless the SHA2 extension (RFC 8332) is @@ -419,9 +425,6 @@ enum ssh_digest_e ssh_key_type_to_hash(ssh_session session, enum ssh_keytypes_e type) { switch (type) { - case SSH_KEYTYPE_DSS_CERT01: - case SSH_KEYTYPE_DSS: - return SSH_DIGEST_SHA1; case SSH_KEYTYPE_RSA_CERT01: /* If we are talking to an old OpenSSH version which does not support * SHA2 in certificates */ @@ -463,10 +466,12 @@ enum ssh_digest_e ssh_key_type_to_hash(ssh_session session, case SSH_KEYTYPE_ED25519: return SSH_DIGEST_AUTO; case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ case SSH_KEYTYPE_ECDSA: case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_WARN, "Digest algorithm to be used with key type %u " + SSH_LOG(SSH_LOG_TRACE, "Digest algorithm to be used with key type %u " "is not defined", type); } @@ -537,19 +542,16 @@ enum ssh_keytypes_e ssh_key_type_from_signature_name(const char *name) { * * @return The enum ssh key type. */ -enum ssh_keytypes_e ssh_key_type_from_name(const char *name) { +enum ssh_keytypes_e ssh_key_type_from_name(const char *name) +{ if (name == NULL) { return SSH_KEYTYPE_UNKNOWN; } if (strcmp(name, "rsa") == 0) { return SSH_KEYTYPE_RSA; - } else if (strcmp(name, "dsa") == 0) { - return SSH_KEYTYPE_DSS; } else if (strcmp(name, "ssh-rsa") == 0) { return SSH_KEYTYPE_RSA; - } else if (strcmp(name, "ssh-dss") == 0) { - return SSH_KEYTYPE_DSS; } else if (strcmp(name, "ssh-ecdsa") == 0 || strcmp(name, "ecdsa") == 0 || strcmp(name, "ecdsa-sha2-nistp256") == 0) { @@ -560,8 +562,6 @@ enum ssh_keytypes_e ssh_key_type_from_name(const char *name) { return SSH_KEYTYPE_ECDSA_P521; } else if (strcmp(name, "ssh-ed25519") == 0){ return SSH_KEYTYPE_ED25519; - } else if (strcmp(name, "ssh-dss-cert-v01@openssh.com") == 0) { - return SSH_KEYTYPE_DSS_CERT01; } else if (strcmp(name, "ssh-rsa-cert-v01@openssh.com") == 0) { return SSH_KEYTYPE_RSA_CERT01; } else if (strcmp(name, "ecdsa-sha2-nistp256-cert-v01@openssh.com") == 0) { @@ -586,16 +586,15 @@ enum ssh_keytypes_e ssh_key_type_from_name(const char *name) { } /** - * @brief Get the pubic key type corresponding to a certificate type. + * @brief Get the public key type corresponding to a certificate type. * * @param[in] type The certificate or public key type. * * @return The matching public key type. */ -enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type) { +enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type) +{ switch (type) { - case SSH_KEYTYPE_DSS_CERT01: - return SSH_KEYTYPE_DSS; case SSH_KEYTYPE_RSA_CERT01: return SSH_KEYTYPE_RSA; case SSH_KEYTYPE_ECDSA_P256_CERT01: @@ -622,7 +621,8 @@ enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type) { * * @return 1 if it is a public key, 0 if not. */ -int ssh_key_is_public(const ssh_key k) { +int ssh_key_is_public(const ssh_key k) +{ if (k == NULL) { return 0; } @@ -664,8 +664,8 @@ int ssh_key_cmp(const ssh_key k1, return 1; } - if (k1->type != k2->type) { - SSH_LOG(SSH_LOG_WARN, "key types don't match!"); + if (ssh_key_type_plain(k1->type) != ssh_key_type_plain(k2->type)) { + SSH_LOG(SSH_LOG_DEBUG, "key types don't match!"); return 1; } @@ -685,6 +685,22 @@ int ssh_key_cmp(const ssh_key k1, } } + if (what == SSH_KEY_CMP_CERTIFICATE) { + if (!is_cert_type(k1->type) || + !is_cert_type(k2->type)) { + return 1; + } + if (k1->cert == NULL || k2->cert == NULL) { + return 1; + } + if (ssh_buffer_get_len(k1->cert) != ssh_buffer_get_len(k2->cert)) { + return 1; + } + return memcmp(ssh_buffer_get(k1->cert), + ssh_buffer_get(k2->cert), + ssh_buffer_get_len(k1->cert)); + } + if (k1->type == SSH_KEYTYPE_ED25519 || k1->type == SSH_KEYTYPE_SK_ED25519) { return pki_ed25519_key_cmp(k1, k2, what); @@ -713,17 +729,12 @@ void ssh_signature_free(ssh_signature sig) } switch(sig->type) { - case SSH_KEYTYPE_DSS: -#ifdef HAVE_LIBGCRYPT - gcry_sexp_release(sig->dsa_sig); -#endif - break; case SSH_KEYTYPE_RSA: #ifdef HAVE_LIBGCRYPT gcry_sexp_release(sig->rsa_sig); #elif defined HAVE_LIBMBEDCRYPTO SAFE_FREE(sig->rsa_sig); -#endif +#endif /* HAVE_LIBGCRYPT */ break; case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: @@ -734,16 +745,17 @@ void ssh_signature_free(ssh_signature sig) #elif defined HAVE_LIBMBEDCRYPTO bignum_safe_free(sig->ecdsa_sig.r); bignum_safe_free(sig->ecdsa_sig.s); -#endif +#endif /* HAVE_GCRYPT_ECC */ break; case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: -#ifndef HAVE_OPENSSL_ED25519 +#ifndef HAVE_LIBCRYPTO /* When using OpenSSL, the signature is stored in sig->raw_sig */ SAFE_FREE(sig->ed25519_sig); -#endif +#endif /* HAVE_LIBCRYPTO */ break; - case SSH_KEYTYPE_DSS_CERT01: + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_ECDSA_P384_CERT01: @@ -764,7 +776,7 @@ void ssh_signature_free(ssh_signature sig) } /** - * @brief import a base64 formated key from a memory c-string + * @brief import a base64 formatted key from a memory c-string * * @param[in] b64_key The c-string holding the base64 encoded key * @@ -775,7 +787,7 @@ void ssh_signature_free(ssh_signature sig) * @param[in] auth_data Private data passed to the auth function. * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free() * * @return SSH_ERROR in case of error, SSH_OK otherwise. * @@ -798,7 +810,7 @@ int ssh_pki_import_privkey_base64(const char *b64_key, return SSH_ERROR; } - SSH_LOG(SSH_LOG_INFO, + SSH_LOG(SSH_LOG_DEBUG, "Trying to decode privkey passphrase=%s", passphrase ? "true" : "false"); @@ -824,9 +836,10 @@ int ssh_pki_import_privkey_base64(const char *b64_key, return SSH_OK; } + + /** - * @brief Convert a private key to a pem base64 encoded key, or OpenSSH format for - * keytype ssh-ed25519 + * @brief Convert a private key to a base64 encoded key in given format * * @param[in] privkey The private key to export. * @@ -838,15 +851,21 @@ int ssh_pki_import_privkey_base64(const char *b64_key, * @param[in] auth_data Private data passed to the auth function. * * @param[out] b64_key A pointer to store the allocated base64 encoded key. You - * need to free the buffer. + * need to free the buffer using ssh_string_from_char(). + * + * @param[in] format The file format (OpenSSH, PEM, or default) * * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_string_free_char() */ -int ssh_pki_export_privkey_base64(const ssh_key privkey, - const char *passphrase, - ssh_auth_callback auth_fn, - void *auth_data, - char **b64_key) +int +ssh_pki_export_privkey_base64_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key, + enum ssh_file_format_e format) { ssh_string blob = NULL; char *b64 = NULL; @@ -855,16 +874,32 @@ int ssh_pki_export_privkey_base64(const ssh_key privkey, return SSH_ERROR; } - if (privkey->type == SSH_KEYTYPE_ED25519){ - blob = ssh_pki_openssh_privkey_export(privkey, - passphrase, - auth_fn, - auth_data); - } else { + /* + * For historic reasons, the Ed25519 keys are exported in OpenSSH file + * format by default also when built with OpenSSL. + */ +#ifdef HAVE_LIBCRYPTO + if (format == SSH_FILE_FORMAT_DEFAULT && + privkey->type != SSH_KEYTYPE_ED25519) { + format = SSH_FILE_FORMAT_PEM; + } +#endif /* HAVE_LIBCRYPTO */ + + switch (format) { + case SSH_FILE_FORMAT_PEM: blob = pki_private_key_to_pem(privkey, passphrase, auth_fn, auth_data); + break; + case SSH_FILE_FORMAT_DEFAULT: + /* default except (OpenSSL && !ED25519) handled above */ + case SSH_FILE_FORMAT_OPENSSH: + blob = ssh_pki_openssh_privkey_export(privkey, + passphrase, + auth_fn, + auth_data); + break; } if (blob == NULL) { return SSH_ERROR; @@ -881,6 +916,42 @@ int ssh_pki_export_privkey_base64(const ssh_key privkey, return SSH_OK; } + /** + * @brief Convert a private key to a pem base64 encoded key, or OpenSSH format for + * keytype ssh-ed25519 + * + * @param[in] privkey The private key to export. + * + * @param[in] passphrase The passphrase to use to encrypt the key with or + * NULL. An empty string means no passphrase. + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[out] b64_key A pointer to store the allocated base64 encoded key. You + * need to free the buffer using ssh_string_from_char(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_string_free_char() + */ +int ssh_pki_export_privkey_base64(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key) +{ + return ssh_pki_export_privkey_base64_format(privkey, + passphrase, + auth_fn, + auth_data, + b64_key, + SSH_FILE_FORMAT_DEFAULT); +} + + + /** * @brief Import a private key from a file or a PKCS #11 device. * @@ -895,7 +966,7 @@ int ssh_pki_export_privkey_base64(const ssh_key privkey, * @param[in] auth_data Private data passed to the auth function. * * @param[out] pkey A pointer to store the allocated ssh_key. You need to - * free the key. + * free the key using ssh_key_free(). * * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission * denied, SSH_ERROR otherwise. @@ -912,6 +983,7 @@ int ssh_pki_import_privkey_file(const char *filename, FILE *file; off_t size; int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; if (pkey == NULL || filename == NULL || *filename == '\0') { return SSH_ERROR; @@ -922,24 +994,24 @@ int ssh_pki_import_privkey_file(const char *filename, rc = pki_uri_import(filename, pkey, SSH_KEY_PRIVATE); return rc; } -#endif +#endif /* WITH_PKCS11_URI */ file = fopen(filename, "rb"); if (file == NULL) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Error opening %s: %s", filename, - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_EOF; } rc = fstat(fileno(file), &sb); if (rc < 0) { fclose(file); - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Error getting stat of %s: %s", filename, - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); switch (errno) { case ENOENT: case EACCES: @@ -950,7 +1022,7 @@ int ssh_pki_import_privkey_file(const char *filename, } if (sb.st_size > MAX_PRIVKEY_SIZE) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Private key is bigger than 4M."); fclose(file); return SSH_ERROR; @@ -959,7 +1031,7 @@ int ssh_pki_import_privkey_file(const char *filename, key_buf = malloc(sb.st_size + 1); if (key_buf == NULL) { fclose(file); - SSH_LOG(SSH_LOG_WARN, "Out of memory!"); + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); return SSH_ERROR; } @@ -968,10 +1040,10 @@ int ssh_pki_import_privkey_file(const char *filename, if (size != sb.st_size) { SAFE_FREE(key_buf); - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Error reading %s: %s", filename, - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } key_buf[size] = 0; @@ -987,8 +1059,7 @@ int ssh_pki_import_privkey_file(const char *filename, } /** - * @brief Export a private key to a pem file on disk, or OpenSSH format for - * keytype ssh-ed25519 + * @brief Export a private key to a file in format specified in the argument * * @param[in] privkey The private key to export. * @@ -1001,16 +1072,21 @@ int ssh_pki_import_privkey_file(const char *filename, * * @param[in] filename The path where to store the pem file. * + * @param[in] format The file format (OpenSSH, PEM, or default) + * * @return SSH_OK on success, SSH_ERROR on error. */ -int ssh_pki_export_privkey_file(const ssh_key privkey, - const char *passphrase, - ssh_auth_callback auth_fn, - void *auth_data, - const char *filename) + +int +ssh_pki_export_privkey_file_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename, + enum ssh_file_format_e format) { - ssh_string blob; - FILE *fp; + ssh_string blob = NULL; + FILE *fp = NULL; int rc; if (privkey == NULL || !ssh_key_is_private(privkey)) { @@ -1019,21 +1095,38 @@ int ssh_pki_export_privkey_file(const ssh_key privkey, fp = fopen(filename, "wb"); if (fp == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; SSH_LOG(SSH_LOG_FUNCTIONS, "Error opening %s: %s", - filename, strerror(errno)); + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_EOF; } - if (privkey->type == SSH_KEYTYPE_ED25519){ - blob = ssh_pki_openssh_privkey_export(privkey, - passphrase, - auth_fn, - auth_data); - } else { + /* + * For historic reasons, the Ed25519 keys are exported in OpenSSH file + * format by default also when built with OpenSSL. + */ +#ifdef HAVE_LIBCRYPTO + if (format == SSH_FILE_FORMAT_DEFAULT && + privkey->type != SSH_KEYTYPE_ED25519) { + format = SSH_FILE_FORMAT_PEM; + } +#endif /* HAVE_LIBCRYPTO */ + + switch (format) { + case SSH_FILE_FORMAT_PEM: blob = pki_private_key_to_pem(privkey, passphrase, auth_fn, auth_data); + break; + case SSH_FILE_FORMAT_DEFAULT: + /* default except (OpenSSL && !ED25519) handled above */ + case SSH_FILE_FORMAT_OPENSSH: + blob = ssh_pki_openssh_privkey_export(privkey, + passphrase, + auth_fn, + auth_data); + break; } if (blob == NULL) { fclose(fp); @@ -1052,12 +1145,45 @@ int ssh_pki_export_privkey_file(const ssh_key privkey, return SSH_OK; } -/* temporary function to migrate seemlessly to ssh_key */ -ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key) { +/** + * @brief Export a private key to a pem file on disk, or OpenSSH format for + * keytype ssh-ed25519 + * + * @param[in] privkey The private key to export. + * + * @param[in] passphrase The passphrase to use to encrypt the key with or + * NULL. An empty string means no passphrase. + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[in] filename The path where to store the pem file. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int +ssh_pki_export_privkey_file(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename) +{ + return ssh_pki_export_privkey_file_format(privkey, + passphrase, + auth_fn, + auth_data, + filename, + SSH_FILE_FORMAT_DEFAULT); +} + +/* temporary function to migrate seamlessly to ssh_key */ +ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key) +{ ssh_public_key pub; ssh_key tmp; - if(key == NULL) { + if (key == NULL) { return NULL; } @@ -1066,38 +1192,44 @@ ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key) { return NULL; } - pub = malloc(sizeof(struct ssh_public_key_struct)); + pub = calloc(1, sizeof(struct ssh_public_key_struct)); if (pub == NULL) { ssh_key_free(tmp); return NULL; } - ZERO_STRUCTP(pub); pub->type = tmp->type; pub->type_c = tmp->type_c; - pub->dsa_pub = tmp->dsa; - tmp->dsa = NULL; +#ifndef HAVE_LIBCRYPTO pub->rsa_pub = tmp->rsa; tmp->rsa = NULL; +#else + pub->key_pub = tmp->key; + tmp->key = NULL; +#endif /* HAVE_LIBCRYPTO */ ssh_key_free(tmp); return pub; } -ssh_private_key ssh_pki_convert_key_to_privatekey(const ssh_key key) { +ssh_private_key ssh_pki_convert_key_to_privatekey(const ssh_key key) +{ ssh_private_key privkey; - privkey = malloc(sizeof(struct ssh_private_key_struct)); + privkey = calloc(1, sizeof(struct ssh_private_key_struct)); if (privkey == NULL) { ssh_key_free(key); return NULL; } privkey->type = key->type; - privkey->dsa_priv = key->dsa; +#ifndef HAVE_LIBCRYPTO privkey->rsa_priv = key->rsa; +#else + privkey->key_priv = key->key; +#endif /* HAVE_LIBCRYPTO */ return privkey; } @@ -1119,46 +1251,6 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; switch (type) { - case SSH_KEYTYPE_DSS: - { - ssh_string p = NULL; - ssh_string q = NULL; - ssh_string g = NULL; - ssh_string pubkey = NULL; - ssh_string privkey = NULL; - - rc = ssh_buffer_unpack(buffer, "SSSSS", &p, &q, &g, - &pubkey, &privkey); - if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Unpack error"); - goto fail; - } - - rc = pki_privkey_build_dss(key, p, q, g, pubkey, privkey); -#ifdef DEBUG_CRYPTO - ssh_log_hexdump("p", ssh_string_data(p), ssh_string_len(p)); - ssh_log_hexdump("q", ssh_string_data(q), ssh_string_len(q)); - ssh_log_hexdump("g", ssh_string_data(g), ssh_string_len(g)); - ssh_log_hexdump("pubkey", ssh_string_data(pubkey), - ssh_string_len(pubkey)); - ssh_log_hexdump("privkey", ssh_string_data(privkey), - ssh_string_len(privkey)); -#endif - ssh_string_burn(p); - SSH_STRING_FREE(p); - ssh_string_burn(q); - SSH_STRING_FREE(q); - ssh_string_burn(g); - SSH_STRING_FREE(g); - ssh_string_burn(pubkey); - SSH_STRING_FREE(pubkey); - ssh_string_burn(privkey); - SSH_STRING_FREE(privkey); - if (rc == SSH_ERROR) { - goto fail; - } - } - break; case SSH_KEYTYPE_RSA: { ssh_string n = NULL; @@ -1171,7 +1263,7 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, rc = ssh_buffer_unpack(buffer, "SSSSSS", &n, &e, &d, &iqmp, &p, &q); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Unpack error"); + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); goto fail; } @@ -1180,11 +1272,12 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, ssh_log_hexdump("n", ssh_string_data(n), ssh_string_len(n)); ssh_log_hexdump("e", ssh_string_data(e), ssh_string_len(e)); ssh_log_hexdump("d", ssh_string_data(d), ssh_string_len(d)); - ssh_log_hexdump("iqmp", ssh_string_data(iqmp), - ssh_string_len(iqmp)); + ssh_log_hexdump("iqmp", + ssh_string_data(iqmp), + ssh_string_len(iqmp)); ssh_log_hexdump("p", ssh_string_data(p), ssh_string_len(p)); ssh_log_hexdump("q", ssh_string_data(q), ssh_string_len(q)); -#endif +#endif /* DEBUG_CRYPTO */ ssh_string_burn(n); SSH_STRING_FREE(n); ssh_string_burn(e); @@ -1198,7 +1291,7 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, ssh_string_burn(q); SSH_STRING_FREE(q); if (rc == SSH_ERROR) { - SSH_LOG(SSH_LOG_WARN, "Failed to build RSA private key"); + SSH_LOG(SSH_LOG_TRACE, "Failed to build RSA private key"); goto fail; } } @@ -1215,7 +1308,7 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, rc = ssh_buffer_unpack(buffer, "SSS", &i, &e, &exp); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Unpack error"); + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); goto fail; } @@ -1235,19 +1328,19 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, ssh_string_burn(exp); SSH_STRING_FREE(exp); if (rc < 0) { - SSH_LOG(SSH_LOG_WARN, "Failed to build ECDSA private key"); + SSH_LOG(SSH_LOG_TRACE, "Failed to build ECDSA private key"); goto fail; } } break; -#endif +#endif /* HAVE_ECC */ case SSH_KEYTYPE_ED25519: { ssh_string pubkey = NULL, privkey = NULL; rc = ssh_buffer_unpack(buffer, "SS", &pubkey, &privkey); if (rc != SSH_OK){ - SSH_LOG(SSH_LOG_WARN, "Unpack error"); + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); goto fail; } @@ -1256,12 +1349,11 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, SSH_STRING_FREE(privkey); SSH_STRING_FREE(pubkey); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Failed to build ed25519 key"); + SSH_LOG(SSH_LOG_TRACE, "Failed to build ed25519 key"); goto fail; } } break; - case SSH_KEYTYPE_DSS_CERT01: case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_ECDSA_P384_CERT01: @@ -1274,7 +1366,7 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_WARN, "Unknown private key type (%d)", type); + SSH_LOG(SSH_LOG_TRACE, "Unknown private key type (%d)", type); goto fail; } @@ -1288,7 +1380,8 @@ fail: static int pki_import_pubkey_buffer(ssh_buffer buffer, enum ssh_keytypes_e type, - ssh_key *pkey) { + ssh_key *pkey) +{ ssh_key key = NULL; int rc; @@ -1302,39 +1395,6 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, key->flags = SSH_KEY_FLAG_PUBLIC; switch (type) { - case SSH_KEYTYPE_DSS: - { - ssh_string p = NULL; - ssh_string q = NULL; - ssh_string g = NULL; - ssh_string pubkey = NULL; - - rc = ssh_buffer_unpack(buffer, "SSSS", &p, &q, &g, &pubkey); - if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Unpack error"); - goto fail; - } - - rc = pki_pubkey_build_dss(key, p, q, g, pubkey); -#ifdef DEBUG_CRYPTO - ssh_log_hexdump("p", ssh_string_data(p), ssh_string_len(p)); - ssh_log_hexdump("q", ssh_string_data(q), ssh_string_len(q)); - ssh_log_hexdump("g", ssh_string_data(g), ssh_string_len(g)); -#endif - ssh_string_burn(p); - SSH_STRING_FREE(p); - ssh_string_burn(q); - SSH_STRING_FREE(q); - ssh_string_burn(g); - SSH_STRING_FREE(g); - ssh_string_burn(pubkey); - SSH_STRING_FREE(pubkey); - if (rc == SSH_ERROR) { - SSH_LOG(SSH_LOG_WARN, "Failed to build DSA public key"); - goto fail; - } - } - break; case SSH_KEYTYPE_RSA: { ssh_string e = NULL; @@ -1342,7 +1402,7 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, rc = ssh_buffer_unpack(buffer, "SS", &e, &n); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Unpack error"); + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); goto fail; } @@ -1350,13 +1410,13 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, #ifdef DEBUG_CRYPTO ssh_log_hexdump("e", ssh_string_data(e), ssh_string_len(e)); ssh_log_hexdump("n", ssh_string_data(n), ssh_string_len(n)); -#endif +#endif /* DEBUG_CRYPTO */ ssh_string_burn(e); SSH_STRING_FREE(e); ssh_string_burn(n); SSH_STRING_FREE(n); if (rc == SSH_ERROR) { - SSH_LOG(SSH_LOG_WARN, "Failed to build RSA public key"); + SSH_LOG(SSH_LOG_TRACE, "Failed to build RSA public key"); goto fail; } } @@ -1374,7 +1434,7 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, rc = ssh_buffer_unpack(buffer, "SS", &i, &e); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Unpack error"); + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); goto fail; } @@ -1390,7 +1450,7 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, ssh_string_burn(e); SSH_STRING_FREE(e); if (rc < 0) { - SSH_LOG(SSH_LOG_WARN, "Failed to build ECDSA public key"); + SSH_LOG(SSH_LOG_TRACE, "Failed to build ECDSA public key"); goto fail; } @@ -1403,7 +1463,7 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, if (type == SSH_KEYTYPE_SK_ECDSA) { ssh_string application = ssh_buffer_get_ssh_string(buffer); if (application == NULL) { - SSH_LOG(SSH_LOG_WARN, "SK Unpack error"); + SSH_LOG(SSH_LOG_TRACE, "SK Unpack error"); goto fail; } key->sk_application = application; @@ -1411,14 +1471,14 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, } } break; -#endif +#endif /* HAVE_ECC */ case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: { ssh_string pubkey = ssh_buffer_get_ssh_string(buffer); if (ssh_string_len(pubkey) != ED25519_KEY_LEN) { - SSH_LOG(SSH_LOG_WARN, "Invalid public key length"); + SSH_LOG(SSH_LOG_TRACE, "Invalid public key length"); ssh_string_burn(pubkey); SSH_STRING_FREE(pubkey); goto fail; @@ -1438,14 +1498,13 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, if (type == SSH_KEYTYPE_SK_ED25519) { ssh_string application = ssh_buffer_get_ssh_string(buffer); if (application == NULL) { - SSH_LOG(SSH_LOG_WARN, "SK Unpack error"); + SSH_LOG(SSH_LOG_TRACE, "SK Unpack error"); goto fail; } key->sk_application = application; } } break; - case SSH_KEYTYPE_DSS_CERT01: case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_ECDSA_P384_CERT01: @@ -1456,7 +1515,7 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_WARN, "Unknown public key protocol %d", type); + SSH_LOG(SSH_LOG_TRACE, "Unknown public key protocol %d", type); goto fail; } @@ -1470,7 +1529,8 @@ fail: static int pki_import_cert_buffer(ssh_buffer buffer, enum ssh_keytypes_e type, - ssh_key *pkey) { + ssh_key *pkey) +{ ssh_buffer cert; ssh_string tmp_s; const char *type_c; @@ -1512,9 +1572,6 @@ static int pki_import_cert_buffer(ssh_buffer buffer, SSH_STRING_FREE(tmp_s); switch (type) { - case SSH_KEYTYPE_DSS_CERT01: - rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_DSS, &key); - break; case SSH_KEYTYPE_RSA_CERT01: rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_RSA, &key); break; @@ -1545,7 +1602,7 @@ static int pki_import_cert_buffer(ssh_buffer buffer, key->type = type; key->type_c = type_c; - key->cert = (void*) cert; + key->cert = cert; *pkey = key; return SSH_OK; @@ -1557,14 +1614,14 @@ fail: } /** - * @brief Import a base64 formated public key from a memory c-string. + * @brief Import a base64 formatted public key from a memory c-string. * * @param[in] b64_key The base64 key to format. * * @param[in] type The type of the key to format. * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * @@ -1572,7 +1629,8 @@ fail: */ int ssh_pki_import_pubkey_base64(const char *b64_key, enum ssh_keytypes_e type, - ssh_key *pkey) { + ssh_key *pkey) +{ ssh_buffer buffer = NULL; ssh_string type_s = NULL; int rc; @@ -1612,14 +1670,15 @@ int ssh_pki_import_pubkey_base64(const char *b64_key, * 6.6 "Public Key Algorithms". * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * * @see ssh_key_free() */ int ssh_pki_import_pubkey_blob(const ssh_string key_blob, - ssh_key *pkey) { + ssh_key *pkey) +{ ssh_buffer buffer = NULL; ssh_string type_s = NULL; enum ssh_keytypes_e type; @@ -1631,26 +1690,26 @@ int ssh_pki_import_pubkey_blob(const ssh_string key_blob, buffer = ssh_buffer_new(); if (buffer == NULL) { - SSH_LOG(SSH_LOG_WARN, "Out of memory!"); + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); return SSH_ERROR; } rc = ssh_buffer_add_data(buffer, ssh_string_data(key_blob), ssh_string_len(key_blob)); if (rc < 0) { - SSH_LOG(SSH_LOG_WARN, "Out of memory!"); + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); goto fail; } type_s = ssh_buffer_get_ssh_string(buffer); if (type_s == NULL) { - SSH_LOG(SSH_LOG_WARN, "Out of memory!"); + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); goto fail; } type = ssh_key_type_from_name(ssh_string_get_char(type_s)); if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "Unknown key type found!"); + SSH_LOG(SSH_LOG_TRACE, "Unknown key type found!"); goto fail; } SSH_STRING_FREE(type_s); @@ -1671,6 +1730,7 @@ fail: return SSH_ERROR; } +#ifdef WITH_PKCS11_URI /** *@brief Detect if the pathname in cmp is a PKCS #11 URI. * @@ -1699,7 +1759,10 @@ bool ssh_pki_is_uri(const char *cmp) * * @param[in] priv_uri Private PKCS #11 URI. * - * @returns pointer to the public PKCS #11 URI + * @returns pointer to the public PKCS #11 URI. You need to free + * the memory using ssh_string_free_char(). + * + * @see ssh_string_free_char(). */ char *ssh_pki_export_pub_uri_from_priv_uri(const char *priv_uri) { @@ -1711,6 +1774,7 @@ char *ssh_pki_export_pub_uri_from_priv_uri(const char *priv_uri) return pub_uri_temp; } +#endif /* WITH_PKCS11_URI */ /** * @brief Import a public key from a file or a PKCS #11 device. @@ -1719,7 +1783,7 @@ char *ssh_pki_export_pub_uri_from_priv_uri(const char *priv_uri) * PKCS #11 URI corresponding to the public key. * * @param[out] pkey A pointer to store the allocated public key. You need to - * free the memory. + * free the memory using ssh_key_free(). * * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission * denied, SSH_ERROR otherwise. @@ -1730,12 +1794,14 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) { enum ssh_keytypes_e type; struct stat sb; - char *key_buf, *p; + char *key_buf = NULL, *p = NULL; size_t buflen, i; - const char *q; - FILE *file; + const char *q = NULL; + FILE *file = NULL; off_t size; int rc, cmp; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_key priv_key = NULL; if (pkey == NULL || filename == NULL || *filename == '\0') { return SSH_ERROR; @@ -1746,20 +1812,20 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) rc = pki_uri_import(filename, pkey, SSH_KEY_PUBLIC); return rc; } -#endif +#endif /* WITH_PKCS11_URI */ file = fopen(filename, "rb"); if (file == NULL) { - SSH_LOG(SSH_LOG_WARN, "Error opening %s: %s", - filename, strerror(errno)); + SSH_LOG(SSH_LOG_TRACE, "Error opening %s: %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_EOF; } rc = fstat(fileno(file), &sb); if (rc < 0) { fclose(file); - SSH_LOG(SSH_LOG_WARN, "Error gettint stat of %s: %s", - filename, strerror(errno)); + SSH_LOG(SSH_LOG_TRACE, "Error gettint stat of %s: %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); switch (errno) { case ENOENT: case EACCES: @@ -1776,7 +1842,7 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) key_buf = malloc(sb.st_size + 1); if (key_buf == NULL) { fclose(file); - SSH_LOG(SSH_LOG_WARN, "Out of memory!"); + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); return SSH_ERROR; } @@ -1785,8 +1851,8 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) if (size != sb.st_size) { SAFE_FREE(key_buf); - SSH_LOG(SSH_LOG_WARN, "Error reading %s: %s", - filename, strerror(errno)); + SSH_LOG(SSH_LOG_TRACE, "Error reading %s: %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } key_buf[size] = '\0'; @@ -1798,7 +1864,24 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) *pkey = ssh_pki_openssh_pubkey_import(key_buf); SAFE_FREE(key_buf); if (*pkey == NULL) { - SSH_LOG(SSH_LOG_WARN, "Failed to import public key from OpenSSH" + SSH_LOG(SSH_LOG_TRACE, "Failed to import public key from OpenSSH" + " private key file"); + return SSH_ERROR; + } + return SSH_OK; + } + + /* + * Try to parse key as PEM. Set empty passphrase, so user won't be prompted + * for passphrase. Don't try to decrypt encrypted private key. + */ + priv_key = pki_private_key_from_base64(key_buf, "", NULL, NULL); + if (priv_key) { + rc = ssh_pki_export_privkey_to_pubkey(priv_key, pkey); + ssh_key_free(priv_key); + SAFE_FREE(key_buf); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to import public key from PEM" " private key file"); return SSH_ERROR; } @@ -1820,6 +1903,10 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) return SSH_ERROR; } + if (i >= buflen) { + SAFE_FREE(key_buf); + return SSH_ERROR; + } q = &p[i + 1]; for (; i < buflen; i++) { if (isspace((int)p[i])) { @@ -1835,14 +1922,14 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) } /** - * @brief Import a base64 formated certificate from a memory c-string. + * @brief Import a base64 formatted certificate from a memory c-string. * * @param[in] b64_cert The base64 cert to format. * * @param[in] type The type of the cert to format. * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * @@ -1850,7 +1937,8 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) */ int ssh_pki_import_cert_base64(const char *b64_cert, enum ssh_keytypes_e type, - ssh_key *pkey) { + ssh_key *pkey) +{ return ssh_pki_import_pubkey_base64(b64_cert, type, pkey); } @@ -1863,14 +1951,15 @@ int ssh_pki_import_cert_base64(const char *b64_cert, * 6.6 "Public Key Algorithms". * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * * @see ssh_key_free() */ int ssh_pki_import_cert_blob(const ssh_string cert_blob, - ssh_key *pkey) { + ssh_key *pkey) +{ return ssh_pki_import_pubkey_blob(cert_blob, pkey); } @@ -1880,7 +1969,7 @@ int ssh_pki_import_cert_blob(const ssh_string cert_blob, * @param[in] filename The path to the certificate. * * @param[out] pkey A pointer to store the allocated certificate. You need to - * free the memory. + * free the memory using ssh_key_free(). * * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission * denied, SSH_ERROR otherwise. @@ -1889,26 +1978,39 @@ int ssh_pki_import_cert_blob(const ssh_string cert_blob, */ int ssh_pki_import_cert_file(const char *filename, ssh_key *pkey) { - return ssh_pki_import_pubkey_file(filename, pkey); + int rc; + + rc = ssh_pki_import_pubkey_file(filename, pkey); + if (rc == SSH_OK) { + /* check the key is a cert type. */ + if (!is_cert_type((*pkey)->type)) { + SSH_KEY_FREE(*pkey); + return SSH_ERROR; + } + } + + return rc; } /** - * @brief Generates a keypair. + * @brief Generates a key pair. * * @param[in] type Type of key to create * * @param[in] parameter Parameter to the creation of key: * rsa : length of the key in bits (e.g. 1024, 2048, 4096) - * dsa : length of the key in bits (e.g. 1024, 2048, 3072) * @param[out] pkey A pointer to store the allocated private key. You need - * to free the memory. + * to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * * @warning Generating a key pair may take some time. + * + * @see ssh_key_free() */ int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, - ssh_key *pkey){ + ssh_key *pkey) +{ int rc; ssh_key key = ssh_key_new(); @@ -1926,11 +2028,6 @@ int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, if(rc == SSH_ERROR) goto error; break; - case SSH_KEYTYPE_DSS: - rc = pki_key_generate_dss(key, parameter); - if(rc == SSH_ERROR) - goto error; - break; #ifdef HAVE_ECC case SSH_KEYTYPE_ECDSA: /* deprecated */ rc = pki_key_generate_ecdsa(key, parameter); @@ -1959,14 +2056,13 @@ int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, goto error; } break; -#endif +#endif /* HAVE_ECC */ case SSH_KEYTYPE_ED25519: rc = pki_key_generate_ed25519(key); if (rc == SSH_ERROR) { goto error; } break; - case SSH_KEYTYPE_DSS_CERT01: case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_ECDSA_P384_CERT01: @@ -1995,7 +2091,7 @@ error: * @param[in] privkey The private key to get the public key from. * * @param[out] pkey A pointer to store the newly allocated public key. You - * NEED to free the key. + * NEED to free the key using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * @@ -2033,11 +2129,11 @@ int ssh_pki_export_privkey_to_pubkey(const ssh_key privkey, * from. * * @param[out] pblob A pointer to store the newly allocated key blob. You - * NEED to free it. + * need to free it using ssh_string_free(). * * @return SSH_OK on success, SSH_ERROR otherwise. * - * @see SSH_STRING_FREE() + * @see ssh_string_free() */ int ssh_pki_export_pubkey_blob(const ssh_key key, ssh_string *pblob) @@ -2048,7 +2144,42 @@ int ssh_pki_export_pubkey_blob(const ssh_key key, return SSH_OK; } - blob = pki_publickey_to_blob(key); + blob = pki_key_to_blob(key, SSH_KEY_PUBLIC); + if (blob == NULL) { + return SSH_ERROR; + } + + *pblob = blob; + return SSH_OK; +} + +/** + * @internal + * + * @brief Create a key_blob from a private key. + * + * The "key_blob" is encoded as per draft-miller-ssh-agent-08 section 4.2 + * "Adding keys to the agent" for any of the supported key types. + * + * @param[in] key A private key to create the private ssh_string from. + * + * @param[out] pblob A pointer to store the newly allocated key blob. You + * need to free it using ssh_string_free(). + * + * @return SSH_OK on success, SSH_ERROR otherwise. + * + * @see ssh_string_free() + */ +int ssh_pki_export_privkey_blob(const ssh_key key, + ssh_string *pblob) +{ + ssh_string blob; + + if (key == NULL) { + return SSH_OK; + } + + blob = pki_key_to_blob(key, SSH_KEY_PRIVATE); if (blob == NULL) { return SSH_ERROR; } @@ -2063,11 +2194,11 @@ int ssh_pki_export_pubkey_blob(const ssh_key key, * @param[in] key The key to hash * * @param[out] b64_key A pointer to store the allocated base64 encoded key. You - * need to free the buffer. + * need to free the buffer using ssh_string_free_char() * * @return SSH_OK on success, SSH_ERROR on error. * - * @see SSH_STRING_FREE_CHAR() + * @see ssh_string_free_char() */ int ssh_pki_export_pubkey_base64(const ssh_key key, char **b64_key) @@ -2079,7 +2210,7 @@ int ssh_pki_export_pubkey_base64(const ssh_key key, return SSH_ERROR; } - key_blob = pki_publickey_to_blob(key); + key_blob = pki_key_to_blob(key, SSH_KEY_PUBLIC); if (key_blob == NULL) { return SSH_ERROR; } @@ -2095,6 +2226,18 @@ int ssh_pki_export_pubkey_base64(const ssh_key key, return SSH_OK; } +/** + * @brief Export public key to file + * + * Exports the public key in AuthorizedKeysFile acceptable format. + * For more information see `man sshd` + * + * @param key A key to export + * + * @param filename The name of the output file + * + * @returns SSH_OK on success, SSH_ERROR otherwise. + */ int ssh_pki_export_pubkey_file(const ssh_key key, const char *filename) { @@ -2164,7 +2307,7 @@ int ssh_pki_export_pubkey_file(const ssh_key key, **/ int ssh_pki_copy_cert_to_privkey(const ssh_key certkey, ssh_key privkey) { ssh_buffer cert_buffer; - int rc; + int rc, cmp; if (certkey == NULL || privkey == NULL) { return SSH_ERROR; @@ -2178,6 +2321,12 @@ int ssh_pki_copy_cert_to_privkey(const ssh_key certkey, ssh_key privkey) { return SSH_ERROR; } + /* make sure the public keys match */ + cmp = ssh_key_cmp(certkey, privkey, SSH_KEY_CMP_PUBLIC); + if (cmp != 0) { + return SSH_ERROR; + } + cert_buffer = ssh_buffer_new(); if (cert_buffer == NULL) { return SSH_ERROR; @@ -2348,22 +2497,11 @@ int pki_key_check_hash_compatible(ssh_key key, } switch(key->type) { - case SSH_KEYTYPE_DSS_CERT01: - case SSH_KEYTYPE_DSS: - if (hash_type == SSH_DIGEST_SHA1) { - if (ssh_fips_mode()) { - SSH_LOG(SSH_LOG_WARN, "SHA1 is not allowed in FIPS mode"); - return SSH_ERROR; - } else { - return SSH_OK; - } - } - break; case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_RSA: if (hash_type == SSH_DIGEST_SHA1) { if (ssh_fips_mode()) { - SSH_LOG(SSH_LOG_WARN, "SHA1 is not allowed in FIPS mode"); + SSH_LOG(SSH_LOG_TRACE, "SHA1 is not allowed in FIPS mode"); return SSH_ERROR; } else { return SSH_OK; @@ -2404,14 +2542,16 @@ int pki_key_check_hash_compatible(ssh_key key, return SSH_OK; } break; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_ECDSA: case SSH_KEYTYPE_UNKNOWN: - SSH_LOG(SSH_LOG_WARN, "Unknown key type %d", key->type); + SSH_LOG(SSH_LOG_TRACE, "Unknown key type %d", key->type); return SSH_ERROR; } - SSH_LOG(SSH_LOG_WARN, "Key type %d incompatible with hash type %d", + SSH_LOG(SSH_LOG_TRACE, "Key type %d incompatible with hash type %d", key->type, hash_type); return SSH_ERROR; @@ -2424,6 +2564,7 @@ int ssh_pki_signature_verify(ssh_session session, size_t input_len) { int rc; + bool allowed; enum ssh_keytypes_e key_type; if (session == NULL || sig == NULL || key == NULL || input == NULL) { @@ -2438,12 +2579,19 @@ int ssh_pki_signature_verify(ssh_session session, sig->type_c); if (key_type != sig->type) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Can not verify %s signature with %s key", sig->type_c, key->type_c); return SSH_ERROR; } + allowed = ssh_key_size_allowed(session, key); + if (!allowed) { + ssh_set_error(session, SSH_FATAL, "The '%s' key of size %d is not " + "allowed by RSA_MIN_SIZE", key->type_c, ssh_key_size(key)); + return SSH_ERROR; + } + /* Check if public key and hash type are compatible */ rc = pki_key_check_hash_compatible(key, sig->hash_type); if (rc != SSH_OK) { @@ -2462,7 +2610,7 @@ int ssh_pki_signature_verify(ssh_session session, ctx = sha256_init(); if (ctx == NULL) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Can not create SHA256CTX for application hash"); return SSH_ERROR; } @@ -2472,7 +2620,7 @@ int ssh_pki_signature_verify(ssh_session session, ctx = sha256_init(); if (ctx == NULL) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Can not create SHA256CTX for input hash"); return SSH_ERROR; } @@ -2562,11 +2710,11 @@ ssh_string ssh_pki_do_sign(ssh_session session, } /* Get the session ID */ - session_id = ssh_string_new(crypto->digest_len); + session_id = ssh_string_new(crypto->session_id_len); if (session_id == NULL) { return NULL; } - rc = ssh_string_fill(session_id, crypto->session_id, crypto->digest_len); + rc = ssh_string_fill(session_id, crypto->session_id, crypto->session_id_len); if (rc < 0) { goto end; } @@ -2609,7 +2757,6 @@ end: return sig_blob; } -#ifndef _WIN32 ssh_string ssh_pki_do_sign_agent(ssh_session session, struct ssh_buffer_struct *buf, const ssh_key pubkey) @@ -2626,11 +2773,11 @@ ssh_string ssh_pki_do_sign_agent(ssh_session session, } /* prepend session identifier */ - session_id = ssh_string_new(crypto->digest_len); + session_id = ssh_string_new(crypto->session_id_len); if (session_id == NULL) { return NULL; } - rc = ssh_string_fill(session_id, crypto->session_id, crypto->digest_len); + rc = ssh_string_fill(session_id, crypto->session_id, crypto->session_id_len); if (rc < 0) { SSH_STRING_FREE(session_id); return NULL; @@ -2663,7 +2810,6 @@ ssh_string ssh_pki_do_sign_agent(ssh_session session, return sig_blob; } -#endif /* _WIN32 */ #ifdef WITH_SERVER ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, @@ -2671,7 +2817,7 @@ ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, const enum ssh_digest_e digest) { struct ssh_crypto_struct *crypto = NULL; - + bool allowed; ssh_signature sig = NULL; ssh_string sig_blob = NULL; @@ -2683,11 +2829,17 @@ ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, return NULL; } + allowed = ssh_key_size_allowed(session, privkey); + if (!allowed) { + ssh_set_error(session, SSH_FATAL, "The hostkey size too small"); + return NULL; + } + crypto = session->next_crypto ? session->next_crypto : session->current_crypto; if (crypto->secret_hash == NULL){ - ssh_set_error(session,SSH_FATAL,"Missing secret_hash"); + ssh_set_error(session, SSH_FATAL, "Missing secret_hash"); return NULL; } @@ -2708,9 +2860,9 @@ ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, /* Generate the signature */ sig = pki_do_sign(privkey, - ssh_buffer_get(sign_input), - ssh_buffer_get_len(sign_input), - digest); + ssh_buffer_get(sign_input), + ssh_buffer_get_len(sign_input), + digest); if (sig == NULL) { goto end; } diff --git a/src/pki_container_openssh.c b/src/pki_container_openssh.c index ecde4cdd..82afdf8e 100644 --- a/src/pki_container_openssh.c +++ b/src/pki_container_openssh.c @@ -49,7 +49,7 @@ * code. * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * @@ -69,20 +69,20 @@ static int pki_openssh_import_privkey_blob(ssh_buffer key_blob_buffer, rc = ssh_buffer_unpack(key_blob_buffer, "s", &type_s); if (rc == SSH_ERROR){ - SSH_LOG(SSH_LOG_WARN, "Unpack error"); + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); return SSH_ERROR; } type = ssh_key_type_from_name(type_s); if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "Unknown key type '%s' found!", type_s); + SSH_LOG(SSH_LOG_TRACE, "Unknown key type '%s' found!", type_s); return SSH_ERROR; } SAFE_FREE(type_s); rc = pki_import_privkey_buffer(type, key_blob_buffer, &key); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Failed to read key in OpenSSH format"); + SSH_LOG(SSH_LOG_TRACE, "Failed to read key in OpenSSH format"); goto fail; } @@ -133,17 +133,17 @@ static int pki_private_key_decrypt(ssh_string blob, } if (ciphers[i].name == NULL){ - SSH_LOG(SSH_LOG_WARN, "Unsupported cipher %s", ciphername); + SSH_LOG(SSH_LOG_TRACE, "Unsupported cipher %s", ciphername); return SSH_ERROR; } cmp = strcmp(kdfname, "bcrypt"); if (cmp != 0) { - SSH_LOG(SSH_LOG_WARN, "Unsupported KDF %s", kdfname); + SSH_LOG(SSH_LOG_TRACE, "Unsupported KDF %s", kdfname); return SSH_ERROR; } if (ssh_string_len(blob) % cipher.blocksize != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Encrypted string not multiple of blocksize: %zu", ssh_string_len(blob)); return SSH_ERROR; @@ -167,12 +167,12 @@ static int pki_private_key_decrypt(ssh_string blob, /* We need material for key (keysize bits / 8) and IV (blocksize) */ key_material_len = cipher.keysize/8 + cipher.blocksize; if (key_material_len > sizeof(key_material)) { - SSH_LOG(SSH_LOG_WARN, "Key material too big"); + SSH_LOG(SSH_LOG_TRACE, "Key material too big"); return SSH_ERROR; } SSH_LOG(SSH_LOG_DEBUG, - "Decryption: %d key, %d IV, %d rounds, %zu bytes salt", + "Decryption: %d key, %d IV, %" PRIu32 " rounds, %zu bytes salt", cipher.keysize/8, cipher.blocksize, rounds, @@ -181,7 +181,7 @@ static int pki_private_key_decrypt(ssh_string blob, if (passphrase == NULL) { if (auth_fn == NULL) { SAFE_FREE(salt); - SSH_LOG(SSH_LOG_WARN, "No passphrase provided"); + SSH_LOG(SSH_LOG_TRACE, "No passphrase provided"); return SSH_ERROR; } rc = auth_fn("Passphrase", @@ -251,7 +251,7 @@ ssh_pki_openssh_import(const char *text_key, cmp = strncmp(ptr, OPENSSH_HEADER_BEGIN, strlen(OPENSSH_HEADER_BEGIN)); if (cmp != 0) { - SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (no header)"); + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (no header)"); goto out; } ptr += strlen(OPENSSH_HEADER_BEGIN); @@ -260,7 +260,7 @@ ssh_pki_openssh_import(const char *text_key, } end = strstr(ptr, OPENSSH_HEADER_END); if (end == NULL) { - SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (no footer)"); + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (no footer)"); goto out; } base64 = malloc(end - ptr + 1); @@ -277,7 +277,7 @@ ssh_pki_openssh_import(const char *text_key, buffer = base64_to_bin(base64); SAFE_FREE(base64); if (buffer == NULL) { - SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (base64 error)"); + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (base64 error)"); goto out; } rc = ssh_buffer_unpack(buffer, "PssSdSS", @@ -290,21 +290,21 @@ ssh_pki_openssh_import(const char *text_key, &pubkey0, &privkeys); if (rc == SSH_ERROR) { - SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (unpack error)"); + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (unpack error)"); goto out; } cmp = strncmp(magic, OPENSSH_AUTH_MAGIC, strlen(OPENSSH_AUTH_MAGIC)); if (cmp != 0) { - SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (bad magic)"); + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (bad magic)"); goto out; } - SSH_LOG(SSH_LOG_INFO, - "Opening OpenSSH private key: ciphername: %s, kdf: %s, nkeys: %d", + SSH_LOG(SSH_LOG_DEBUG, + "Opening OpenSSH private key: ciphername: %s, kdf: %s, nkeys: %" PRIu32, ciphername, kdfname, nkeys); if (nkeys != 1) { - SSH_LOG(SSH_LOG_WARN, "Opening OpenSSH private key: only 1 key supported (%d available)", nkeys); + SSH_LOG(SSH_LOG_TRACE, "Opening OpenSSH private key: only 1 key supported (%" PRIu32 " available)", nkeys); goto out; } @@ -314,7 +314,7 @@ ssh_pki_openssh_import(const char *text_key, if (!private) { rc = ssh_pki_import_pubkey_blob(pubkey0, &key); if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_WARN, "Failed to import public key blob"); + SSH_LOG(SSH_LOG_TRACE, "Failed to import public key blob"); } /* in either case we clean up here */ goto out; @@ -343,7 +343,7 @@ ssh_pki_openssh_import(const char *text_key, rc = ssh_buffer_unpack(privkey_buffer, "dd", &checkint1, &checkint2); if (rc == SSH_ERROR || checkint1 != checkint2) { - SSH_LOG(SSH_LOG_WARN, "OpenSSH private key unpack error (correct password?)"); + SSH_LOG(SSH_LOG_TRACE, "OpenSSH private key unpack error (correct password?)"); goto out; } rc = pki_openssh_import_privkey_blob(privkey_buffer, &key); @@ -358,7 +358,7 @@ ssh_pki_openssh_import(const char *text_key, if (padding != i) { ssh_key_free(key); key = NULL; - SSH_LOG(SSH_LOG_WARN, "Invalid padding"); + SSH_LOG(SSH_LOG_TRACE, "Invalid padding"); goto out; } } @@ -395,37 +395,6 @@ ssh_key ssh_pki_openssh_pubkey_import(const char *text_key) /** @internal - * @brief exports a private key to a string blob. - * @param[in] privkey private key to convert - * @param[out] buffer buffer to write the blob in. - * @returns SSH_OK on success - * @warning only supports ed25519 key type at the moment. - */ -static int pki_openssh_export_privkey_blob(const ssh_key privkey, - ssh_buffer buffer) -{ - int rc; - - if (privkey->type != SSH_KEYTYPE_ED25519) { - SSH_LOG(SSH_LOG_WARN, "Type %s not supported", privkey->type_c); - return SSH_ERROR; - } - if (privkey->ed25519_privkey == NULL || - privkey->ed25519_pubkey == NULL) { - return SSH_ERROR; - } - rc = ssh_buffer_pack(buffer, - "sdPdPP", - privkey->type_c, - (uint32_t)ED25519_KEY_LEN, - (size_t)ED25519_KEY_LEN, privkey->ed25519_pubkey, - (uint32_t)(2 * ED25519_KEY_LEN), - (size_t)ED25519_KEY_LEN, privkey->ed25519_privkey, - (size_t)ED25519_KEY_LEN, privkey->ed25519_pubkey); - return rc; -} - -/** @internal * @brief encrypts an ed25519 private key blob * */ @@ -462,29 +431,29 @@ static int pki_private_key_encrypt(ssh_buffer privkey_buffer, } if (ciphers[i].name == NULL){ - SSH_LOG(SSH_LOG_WARN, "Unsupported cipher %s", ciphername); + SSH_LOG(SSH_LOG_TRACE, "Unsupported cipher %s", ciphername); return SSH_ERROR; } cmp = strcmp(kdfname, "bcrypt"); if (cmp != 0){ - SSH_LOG(SSH_LOG_WARN, "Unsupported KDF %s", kdfname); + SSH_LOG(SSH_LOG_TRACE, "Unsupported KDF %s", kdfname); return SSH_ERROR; } /* We need material for key (keysize bits / 8) and IV (blocksize) */ key_material_len = cipher.keysize/8 + cipher.blocksize; if (key_material_len > sizeof(key_material)){ - SSH_LOG(SSH_LOG_WARN, "Key material too big"); + SSH_LOG(SSH_LOG_TRACE, "Key material too big"); return SSH_ERROR; } - SSH_LOG(SSH_LOG_WARN, "Encryption: %d key, %d IV, %d rounds, %zu bytes salt", + SSH_LOG(SSH_LOG_DEBUG, "Encryption: %d key, %d IV, %" PRIu32 " rounds, %zu bytes salt", cipher.keysize/8, cipher.blocksize, rounds, ssh_string_len(salt)); if (passphrase == NULL){ if (auth_fn == NULL){ - SSH_LOG(SSH_LOG_WARN, "No passphrase provided"); + SSH_LOG(SSH_LOG_TRACE, "No passphrase provided"); return SSH_ERROR; } rc = auth_fn("Passphrase", @@ -536,8 +505,8 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, ssh_auth_callback auth_fn, void *auth_data) { - ssh_buffer buffer; - ssh_string str = NULL; + ssh_buffer buffer = NULL; + ssh_string str = NULL, blob = NULL; ssh_string pubkey_s=NULL; ssh_buffer privkey_buffer = NULL; uint32_t rnd; @@ -554,17 +523,13 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, if (privkey == NULL) { return NULL; } - if (privkey->type != SSH_KEYTYPE_ED25519){ - SSH_LOG(SSH_LOG_WARN, "Unsupported key type %s", privkey->type_c); - return NULL; - } if (passphrase != NULL || auth_fn != NULL){ - SSH_LOG(SSH_LOG_INFO, "Enabling encryption for private key export"); + SSH_LOG(SSH_LOG_DEBUG, "Enabling encryption for private key export"); to_encrypt = 1; } buffer = ssh_buffer_new(); - pubkey_s = pki_publickey_to_blob(privkey); - if(buffer == NULL || pubkey_s == NULL){ + rc = ssh_pki_export_pubkey_blob(privkey, &pubkey_s); + if (buffer == NULL || rc != SSH_OK) { goto error; } @@ -578,22 +543,17 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, goto error; } - /* checkint1 & 2 */ - rc = ssh_buffer_pack(privkey_buffer, - "dd", - rnd, - rnd); - if (rc == SSH_ERROR){ - goto error; - } - - rc = pki_openssh_export_privkey_blob(privkey, privkey_buffer); - if (rc == SSH_ERROR){ + rc = ssh_pki_export_privkey_blob(privkey, &blob); + if (rc != SSH_OK) { goto error; } - /* comment */ - rc = ssh_buffer_pack(privkey_buffer, "s", "" /* comment */); + rc = ssh_buffer_pack(privkey_buffer, + "ddPs", + rnd, /* checkint 1 & 2 */ + rnd, + ssh_string_len(blob), ssh_string_data(blob), + "" /* comment */); if (rc == SSH_ERROR){ goto error; } @@ -630,7 +590,11 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, goto error; } - ssh_buffer_pack(kdf_buf, "Sd", salt, rounds); + rc = ssh_buffer_pack(kdf_buf, "Sd", salt, rounds); + if (rc != SSH_OK) { + SSH_BUFFER_FREE(kdf_buf); + goto error; + } kdf_options = ssh_string_new(ssh_buffer_get_len(kdf_buf)); if (kdf_options == NULL){ SSH_BUFFER_FREE(kdf_buf); @@ -706,6 +670,8 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, } error: + ssh_string_burn(blob); + ssh_string_free(blob); if (privkey_buffer != NULL) { void *bufptr = ssh_buffer_get(privkey_buffer); explicit_bzero(bufptr, ssh_buffer_get_len(privkey_buffer)); diff --git a/src/pki_crypto.c b/src/pki_crypto.c index 4c95286b..f4ce8bdf 100644 --- a/src/pki_crypto.c +++ b/src/pki_crypto.c @@ -29,14 +29,24 @@ #include "config.h" #include "libssh/priv.h" +#include "libcrypto-compat.h" #include <openssl/pem.h> -#include <openssl/dsa.h> -#include <openssl/err.h> -#include <openssl/engine.h> #include <openssl/evp.h> +#include <openssl/engine.h> +#include <openssl/err.h> +#if OPENSSL_VERSION_NUMBER < 0x30000000L +#include <openssl/dsa.h> #include <openssl/rsa.h> -#include "libcrypto-compat.h" +#else +#include <openssl/params.h> +#include <openssl/core_names.h> +#include <openssl/param_build.h> +#if defined(WITH_PKCS11_URI) && defined(WITH_PKCS11_PROVIDER) +#include <openssl/store.h> +#include <openssl/provider.h> +#endif +#endif /* OPENSSL_VERSION_NUMBER */ #ifdef HAVE_OPENSSL_EC_H #include <openssl/ec.h> @@ -81,12 +91,24 @@ static int pem_get_password(char *buf, int size, int rwflag, void *userdata) { return 0; } +void pki_key_clean(ssh_key key) +{ + if (key == NULL) + return; + EVP_PKEY_free(key->key); + key->key = NULL; +} + #ifdef HAVE_OPENSSL_ECC +#if OPENSSL_VERSION_NUMBER < 0x30000000L static int pki_key_ecdsa_to_nid(EC_KEY *k) { const EC_GROUP *g = EC_KEY_get0_group(k); int nid; + if (g == NULL) { + return -1; + } nid = EC_GROUP_get_curve_name(g); if (nid) { return nid; @@ -94,8 +116,30 @@ static int pki_key_ecdsa_to_nid(EC_KEY *k) return -1; } +#else +static int pki_key_ecdsa_to_nid(EVP_PKEY *k) +{ + char gname[25] = { 0 }; + int rc; + + rc = EVP_PKEY_get_utf8_string_param(k, + OSSL_PKEY_PARAM_GROUP_NAME, + gname, + 25, + NULL); + if (rc != 1) { + return -1; + } + + return pki_key_ecgroup_name_to_nid(gname); +} +#endif /* OPENSSL_VERSION_NUMBER */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(EC_KEY *k) +#else +static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(EVP_PKEY *k) +#endif /* OPENSSL_VERSION_NUMBER */ { int nid; @@ -158,114 +202,157 @@ int pki_key_ecdsa_nid_from_name(const char *name) return -1; } -static ssh_string make_ecpoint_string(const EC_GROUP *g, - const EC_POINT *p) +int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) { - ssh_string s; - size_t len; - - len = EC_POINT_point2oct(g, - p, - POINT_CONVERSION_UNCOMPRESSED, - NULL, - 0, - NULL); - if (len == 0) { - return NULL; - } + int rc = 0; + BIGNUM *bexp = NULL; - s = ssh_string_new(len); - if (s == NULL) { - return NULL; - } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_POINT *p = NULL; + const EC_GROUP *g = NULL; + EC_KEY *ecdsa = NULL; +#else + const char *group_name = OSSL_EC_curve_nid2name(nid); + OSSL_PARAM_BLD *param_bld = NULL; - len = EC_POINT_point2oct(g, - p, - POINT_CONVERSION_UNCOMPRESSED, - ssh_string_data(s), - ssh_string_len(s), - NULL); - if (len != ssh_string_len(s)) { - SSH_STRING_FREE(s); - return NULL; + if (group_name == NULL) { + return -1; } +#endif /* OPENSSL_VERSION_NUMBER */ - return s; -} - -int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) -{ - EC_POINT *p = NULL; - const EC_GROUP *g = NULL; - int ok; - BIGNUM *bexp = NULL; + bexp = ssh_make_string_bn(exp); + if (bexp == NULL) { + return -1; + } key->ecdsa_nid = nid; key->type_c = pki_key_ecdsa_nid_to_name(nid); - key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); - if (key->ecdsa == NULL) { - return -1; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (ecdsa == NULL) { + rc = -1; + goto cleanup; } - g = EC_KEY_get0_group(key->ecdsa); + g = EC_KEY_get0_group(ecdsa); p = EC_POINT_new(g); if (p == NULL) { - return -1; + rc = -1; + goto cleanup; } - ok = EC_POINT_oct2point(g, + rc = EC_POINT_oct2point(g, p, ssh_string_data(e), ssh_string_len(e), NULL); - if (!ok) { - EC_POINT_free(p); - return -1; + if (rc != 1) { + rc = -1; + goto cleanup; } /* EC_KEY_set_public_key duplicates p */ - ok = EC_KEY_set_public_key(key->ecdsa, p); - EC_POINT_free(p); - if (!ok) { - return -1; + rc = EC_KEY_set_public_key(ecdsa, p); + if (rc != 1) { + rc = -1; + goto cleanup; } - bexp = ssh_make_string_bn(exp); - if (bexp == NULL) { - EC_KEY_free(key->ecdsa); - return -1; - } /* EC_KEY_set_private_key duplicates exp */ - ok = EC_KEY_set_private_key(key->ecdsa, bexp); + rc = EC_KEY_set_private_key(ecdsa, bexp); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + rc = -1; + goto cleanup; + } + + /* ecdsa will be freed when the EVP_PKEY key->key is freed */ + rc = EVP_PKEY_assign_EC_KEY(key->key, ecdsa); + if (rc != 1) { + rc = -1; + goto cleanup; + } + /* ssh_key is now the owner of this memory */ + ecdsa = NULL; + + /* set rc to 0 if everything went well */ + rc = 0; + +cleanup: + EC_KEY_free(ecdsa); + EC_POINT_free(p); BN_free(bexp); - if (!ok) { - EC_KEY_free(key->ecdsa); - return -1; + return rc; +#else + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL){ + rc = -1; + goto cleanup; } - return 0; + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME, + group_name, strlen(group_name)); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY, + ssh_string_data(e), ssh_string_len(e)); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PRIV_KEY, bexp); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + rc = evp_build_pkey("EC", param_bld, &(key->key), EVP_PKEY_KEYPAIR); + +cleanup: + OSSL_PARAM_BLD_free(param_bld); + BN_free(bexp); + return rc; +#endif /* OPENSSL_VERSION_NUMBER */ } int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) { + int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L EC_POINT *p = NULL; const EC_GROUP *g = NULL; + EC_KEY *ecdsa = NULL; int ok; +#else + const char *group_name = OSSL_EC_curve_nid2name(nid); + OSSL_PARAM_BLD *param_bld; +#endif /* OPENSSL_VERSION_NUMBER */ key->ecdsa_nid = nid; key->type_c = pki_key_ecdsa_nid_to_name(nid); - key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); - if (key->ecdsa == NULL) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (ecdsa == NULL) { return -1; } - g = EC_KEY_get0_group(key->ecdsa); + g = EC_KEY_get0_group(ecdsa); p = EC_POINT_new(g); if (p == NULL) { + EC_KEY_free(ecdsa); return -1; } @@ -275,24 +362,60 @@ int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) ssh_string_len(e), NULL); if (!ok) { + EC_KEY_free(ecdsa); EC_POINT_free(p); return -1; } /* EC_KEY_set_public_key duplicates p */ - ok = EC_KEY_set_public_key(key->ecdsa, p); + ok = EC_KEY_set_public_key(ecdsa, p); EC_POINT_free(p); if (!ok) { + EC_KEY_free(ecdsa); + return -1; + } + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + EC_KEY_free(ecdsa); + return -1; + } + + rc = EVP_PKEY_assign_EC_KEY(key->key, ecdsa); + if (rc != 1) { + EC_KEY_free(ecdsa); return -1; } return 0; +#else + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) + goto err; + + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME, + group_name, strlen(group_name)); + if (rc != 1) + goto err; + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY, + ssh_string_data(e), ssh_string_len(e)); + if (rc != 1) + goto err; + + rc = evp_build_pkey("EC", param_bld, &(key->key), EVP_PKEY_PUBLIC_KEY); + OSSL_PARAM_BLD_free(param_bld); + + return rc; +err: + OSSL_PARAM_BLD_free(param_bld); + return -1; +#endif /* OPENSSL_VERSION_NUMBER */ } -#endif +#endif /* HAVE_OPENSSL_ECC */ ssh_key pki_key_dup(const ssh_key key, int demote) { - ssh_key new; + ssh_key new = NULL; int rc; new = ssh_key_new(); @@ -300,10 +423,6 @@ ssh_key pki_key_dup(const ssh_key key, int demote) return NULL; } -#ifdef WITH_PKCS11_URI - new->key = key->key; -#endif - new->type = key->type; new->type_c = key->type_c; if (demote) { @@ -313,75 +432,28 @@ ssh_key pki_key_dup(const ssh_key key, int demote) } switch (key->type) { - case SSH_KEYTYPE_DSS: { - const BIGNUM *p = NULL, *q = NULL, *g = NULL, - *pub_key = NULL, *priv_key = NULL; - BIGNUM *np, *nq, *ng, *npub_key, *npriv_key; - new->dsa = DSA_new(); - if (new->dsa == NULL) { - goto fail; - } - - /* - * p = public prime number - * q = public 160-bit subprime, q | p-1 - * g = public generator of subgroup - * pub_key = public key y = g^x - * priv_key = private key x - */ - DSA_get0_pqg(key->dsa, &p, &q, &g); - np = BN_dup(p); - nq = BN_dup(q); - ng = BN_dup(g); - if (np == NULL || nq == NULL || ng == NULL) { - BN_free(np); - BN_free(nq); - BN_free(ng); - goto fail; - } - - /* Memory management of np, nq and ng is transferred to DSA object */ - rc = DSA_set0_pqg(new->dsa, np, nq, ng); - if (rc == 0) { - BN_free(np); - BN_free(nq); - BN_free(ng); - goto fail; - } - - DSA_get0_key(key->dsa, &pub_key, &priv_key); - npub_key = BN_dup(pub_key); - if (npub_key == NULL) { - goto fail; - } - - /* Memory management of npubkey is transferred to DSA object */ - rc = DSA_set0_key(new->dsa, npub_key, NULL); - if (rc == 0) { - goto fail; - } - - if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { - npriv_key = BN_dup(priv_key); - if (npriv_key == NULL) { - goto fail; - } - - /* Memory management of npriv_key is transferred to DSA object */ - rc = DSA_set0_key(new->dsa, NULL, npriv_key); - if (rc == 0) { - goto fail; - } - } - - break; - } case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: { +#if OPENSSL_VERSION_NUMBER < 0x30000000L const BIGNUM *n = NULL, *e = NULL, *d = NULL; BIGNUM *nn, *ne, *nd; - new->rsa = RSA_new(); - if (new->rsa == NULL) { + RSA *new_rsa = NULL; + const RSA *key_rsa = EVP_PKEY_get0_RSA(key->key); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */ +#ifdef WITH_PKCS11_URI + /* Take the PKCS#11 keys as they are */ + if (key->flags & SSH_KEY_FLAG_PKCS11_URI && !demote) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + return new; + } +#endif /* WITH_PKCS11_URI */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + new_rsa = RSA_new(); + if (new_rsa == NULL) { goto fail; } @@ -395,18 +467,20 @@ ssh_key pki_key_dup(const ssh_key key, int demote) * dmq1 = d mod (q-1) * iqmp = q^-1 mod p */ - RSA_get0_key(key->rsa, &n, &e, &d); + RSA_get0_key(key_rsa, &n, &e, &d); nn = BN_dup(n); ne = BN_dup(e); if (nn == NULL || ne == NULL) { + RSA_free(new_rsa); BN_free(nn); BN_free(ne); goto fail; } /* Memory management of nn and ne is transferred to RSA object */ - rc = RSA_set0_key(new->rsa, nn, ne, NULL); + rc = RSA_set0_key(new_rsa, nn, ne, NULL); if (rc == 0) { + RSA_free(new_rsa); BN_free(nn); BN_free(ne); goto fail; @@ -419,43 +493,48 @@ ssh_key pki_key_dup(const ssh_key key, int demote) nd = BN_dup(d); if (nd == NULL) { + RSA_free(new_rsa); goto fail; } /* Memory management of nd is transferred to RSA object */ - rc = RSA_set0_key(new->rsa, NULL, NULL, nd); + rc = RSA_set0_key(new_rsa, NULL, NULL, nd); if (rc == 0) { + RSA_free(new_rsa); goto fail; } /* p, q, dmp1, dmq1 and iqmp may be NULL in private keys, but the * RSA operations are much faster when these values are available. */ - RSA_get0_factors(key->rsa, &p, &q); + RSA_get0_factors(key_rsa, &p, &q); if (p != NULL && q != NULL) { /* need to set both of them */ np = BN_dup(p); nq = BN_dup(q); if (np == NULL || nq == NULL) { + RSA_free(new_rsa); BN_free(np); BN_free(nq); goto fail; } /* Memory management of np and nq is transferred to RSA object */ - rc = RSA_set0_factors(new->rsa, np, nq); + rc = RSA_set0_factors(new_rsa, np, nq); if (rc == 0) { + RSA_free(new_rsa); BN_free(np); BN_free(nq); goto fail; } } - RSA_get0_crt_params(key->rsa, &dmp1, &dmq1, &iqmp); + RSA_get0_crt_params(key_rsa, &dmp1, &dmq1, &iqmp); if (dmp1 != NULL || dmq1 != NULL || iqmp != NULL) { ndmp1 = BN_dup(dmp1); ndmq1 = BN_dup(dmq1); niqmp = BN_dup(iqmp); if (ndmp1 == NULL || ndmq1 == NULL || niqmp == NULL) { + RSA_free(new_rsa); BN_free(ndmp1); BN_free(ndmq1); BN_free(niqmp); @@ -464,8 +543,9 @@ ssh_key pki_key_dup(const ssh_key key, int demote) /* Memory management of ndmp1, ndmq1 and niqmp is transferred * to RSA object */ - rc = RSA_set0_crt_params(new->rsa, ndmp1, ndmq1, niqmp); + rc = RSA_set0_crt_params(new_rsa, ndmp1, ndmq1, niqmp); if (rc == 0) { + RSA_free(new_rsa); BN_free(ndmp1); BN_free(ndmq1); BN_free(niqmp); @@ -474,6 +554,26 @@ ssh_key pki_key_dup(const ssh_key key, int demote) } } + new->key = EVP_PKEY_new(); + if (new->key == NULL) { + RSA_free(new_rsa); + goto fail; + } + + rc = EVP_PKEY_assign_RSA(new->key, new_rsa); + if (rc != 1) { + EVP_PKEY_free(new->key); + RSA_free(new_rsa); + goto fail; + } + + new_rsa = NULL; +#else + rc = evp_dup_rsa_pkey(key, new, demote); + if (rc != SSH_OK) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ break; } case SSH_KEYTYPE_ECDSA_P256: @@ -481,31 +581,73 @@ ssh_key pki_key_dup(const ssh_key key, int demote) case SSH_KEYTYPE_ECDSA_P521: #ifdef HAVE_OPENSSL_ECC new->ecdsa_nid = key->ecdsa_nid; - +#ifdef WITH_PKCS11_URI + /* Take the PKCS#11 keys as they are */ + if (key->flags & SSH_KEY_FLAG_PKCS11_URI && !demote) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + return new; + } +#endif /* WITH_PKCS11_URI */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L /* privkey -> pubkey */ if (demote && ssh_key_is_private(key)) { - const EC_POINT *p; + const EC_POINT *p = NULL; + EC_KEY *new_ecdsa = NULL, *old_ecdsa = NULL; int ok; - new->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); - if (new->ecdsa == NULL) { + new_ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (new_ecdsa == NULL) { goto fail; } - p = EC_KEY_get0_public_key(key->ecdsa); + old_ecdsa = EVP_PKEY_get0_EC_KEY(key->key); + if (old_ecdsa == NULL) { + EC_KEY_free(new_ecdsa); + goto fail; + } + + p = EC_KEY_get0_public_key(old_ecdsa); if (p == NULL) { + EC_KEY_free(new_ecdsa); + goto fail; + } + + ok = EC_KEY_set_public_key(new_ecdsa, p); + if (ok != 1) { + EC_KEY_free(new_ecdsa); goto fail; } - ok = EC_KEY_set_public_key(new->ecdsa, p); - if (!ok) { + new->key = EVP_PKEY_new(); + if (new->key == NULL) { + EC_KEY_free(new_ecdsa); + goto fail; + } + + ok = EVP_PKEY_assign_EC_KEY(new->key, new_ecdsa); + if (ok != 1) { + EC_KEY_free(new_ecdsa); goto fail; } } else { - new->ecdsa = EC_KEY_dup(key->ecdsa); + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + } +#else + rc = evp_dup_ecdsa_pkey(key, new, demote); + if (rc != SSH_OK) { + goto fail; } +#endif /* OPENSSL_VERSION_NUMBER */ break; -#endif +#endif /* HAVE_OPENSSL_ECC */ case SSH_KEYTYPE_ED25519: rc = pki_ed25519_key_dup(new, key); if (rc != SSH_OK) { @@ -525,164 +667,181 @@ fail: } int pki_key_generate_rsa(ssh_key key, int parameter){ - BIGNUM *e; int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + BIGNUM *e = NULL; + RSA *key_rsa = NULL; +#else + OSSL_PARAM params[3]; + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); + unsigned e = 65537; +#endif /* OPENSSL_VERSION_NUMBER */ + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + e = BN_new(); + key_rsa = RSA_new(); + if (key_rsa == NULL) { + return SSH_ERROR; + } - e = BN_new(); - key->rsa = RSA_new(); + BN_set_word(e, 65537); + rc = RSA_generate_key_ex(key_rsa, parameter, e, NULL); - BN_set_word(e, 65537); - rc = RSA_generate_key_ex(key->rsa, parameter, e, NULL); + BN_free(e); - BN_free(e); + if (rc <= 0 || key_rsa == NULL) { + return SSH_ERROR; + } - if (rc <= 0 || key->rsa == NULL) - return SSH_ERROR; - return SSH_OK; -} + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + RSA_free(key_rsa); + return SSH_ERROR; + } -int pki_key_generate_dss(ssh_key key, int parameter){ - int rc; - key->dsa = DSA_new(); - if (key->dsa == NULL) { + rc = EVP_PKEY_assign_RSA(key->key, key_rsa); + if (rc != 1) { + RSA_free(key_rsa); + EVP_PKEY_free(key->key); return SSH_ERROR; } - rc = DSA_generate_parameters_ex(key->dsa, - parameter, - NULL, /* seed */ - 0, /* seed_len */ - NULL, /* counter_ret */ - NULL, /* h_ret */ - NULL); /* cb */ + + key_rsa = NULL; +#else + key->key = NULL; + + rc = EVP_PKEY_keygen_init(pctx); if (rc != 1) { - DSA_free(key->dsa); - key->dsa = NULL; + EVP_PKEY_CTX_free(pctx); return SSH_ERROR; } - rc = DSA_generate_key(key->dsa); - if (rc != 1){ - DSA_free(key->dsa); - key->dsa=NULL; + + params[0] = OSSL_PARAM_construct_int("bits", ¶meter); + params[1] = OSSL_PARAM_construct_uint("e", &e); + params[2] = OSSL_PARAM_construct_end(); + rc = EVP_PKEY_CTX_set_params(pctx, params); + if (rc != 1) { + EVP_PKEY_CTX_free(pctx); return SSH_ERROR; } - return SSH_OK; + + rc = EVP_PKEY_generate(pctx, &(key->key)); + + EVP_PKEY_CTX_free(pctx); + + if (rc != 1 || key->key == NULL) + return SSH_ERROR; +#endif /* OPENSSL_VERSION_NUMBER */ + return SSH_OK; } #ifdef HAVE_OPENSSL_ECC -int pki_key_generate_ecdsa(ssh_key key, int parameter) { +int pki_key_generate_ecdsa(ssh_key key, int parameter) +{ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY *ecdsa = NULL; int ok; - +#else + const char *group_name = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ switch (parameter) { + case 256: + key->ecdsa_nid = NID_X9_62_prime256v1; + key->type = SSH_KEYTYPE_ECDSA_P256; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + group_name = NISTP256; +#endif /* OPENSSL_VERSION_NUMBER */ + break; case 384: key->ecdsa_nid = NID_secp384r1; key->type = SSH_KEYTYPE_ECDSA_P384; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + group_name = NISTP384; +#endif /* OPENSSL_VERSION_NUMBER */ break; case 521: key->ecdsa_nid = NID_secp521r1; key->type = SSH_KEYTYPE_ECDSA_P521; - break; - case 256: - key->ecdsa_nid = NID_X9_62_prime256v1; - key->type = SSH_KEYTYPE_ECDSA_P256; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + group_name = NISTP521; +#endif /* OPENSSL_VERSION_NUMBER */ break; default: - SSH_LOG(SSH_LOG_WARN, "Invalid parameter %d for ECDSA key " + SSH_LOG(SSH_LOG_TRACE, "Invalid parameter %d for ECDSA key " "generation", parameter); return SSH_ERROR; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (ecdsa == NULL) { + return SSH_ERROR; + } + ok = EC_KEY_generate_key(ecdsa); + if (!ok) { + EC_KEY_free(ecdsa); + return SSH_ERROR; + } + + EC_KEY_set_asn1_flag(ecdsa, OPENSSL_EC_NAMED_CURVE); - key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); - if (key->ecdsa == NULL) { + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + EC_KEY_free(ecdsa); return SSH_ERROR; } - ok = EC_KEY_generate_key(key->ecdsa); - if (!ok) { - EC_KEY_free(key->ecdsa); + ok = EVP_PKEY_assign_EC_KEY(key->key, ecdsa); + if (ok != 1) { return SSH_ERROR; } - EC_KEY_set_asn1_flag(key->ecdsa, OPENSSL_EC_NAMED_CURVE); +#else + key->key = EVP_EC_gen(group_name); + if (key->key == NULL) { + return SSH_ERROR; + } +#endif /* OPENSSL_VERSION_NUMBER */ return SSH_OK; } -#endif +#endif /* HAVE_OPENSSL_ECC */ +/* With OpenSSL 3.0 and higher the parameter 'what' + * is ignored and the comparison is done by OpenSSL + */ int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) { - switch (k1->type) { - case SSH_KEYTYPE_DSS: { - const BIGNUM *p1, *p2, *q1, *q2, *g1, *g2, - *pub_key1, *pub_key2, *priv_key1, *priv_key2; - if (DSA_size(k1->dsa) != DSA_size(k2->dsa)) { - return 1; - } - DSA_get0_pqg(k1->dsa, &p1, &q1, &g1); - DSA_get0_pqg(k2->dsa, &p2, &q2, &g2); - if (bignum_cmp(p1, p2) != 0) { - return 1; - } - if (bignum_cmp(q1, q2) != 0) { - return 1; - } - if (bignum_cmp(g1, g2) != 0) { - return 1; - } - DSA_get0_key(k1->dsa, &pub_key1, &priv_key1); - DSA_get0_key(k2->dsa, &pub_key2, &priv_key2); - if (bignum_cmp(pub_key1, pub_key2) != 0) { - return 1; - } + int rc; - if (what == SSH_KEY_CMP_PRIVATE) { - if (bignum_cmp(priv_key1, priv_key2) != 0) { - return 1; - } - } - break; - } - case SSH_KEYTYPE_RSA: - case SSH_KEYTYPE_RSA1: { - const BIGNUM *e1, *e2, *n1, *n2, *p1, *p2, *q1, *q2; - if (RSA_size(k1->rsa) != RSA_size(k2->rsa)) { - return 1; - } - RSA_get0_key(k1->rsa, &n1, &e1, NULL); - RSA_get0_key(k2->rsa, &n2, &e2, NULL); - if (bignum_cmp(e1, e2) != 0) { - return 1; - } - if (bignum_cmp(n1, n2) != 0) { - return 1; - } + (void)what; - if (what == SSH_KEY_CMP_PRIVATE) { - RSA_get0_factors(k1->rsa, &p1, &q1); - RSA_get0_factors(k2->rsa, &p2, &q2); - if (bignum_cmp(p1, p2) != 0) { - return 1; - } - - if (bignum_cmp(q1, q2) != 0) { - return 1; - } - } - break; - } + switch (ssh_key_type_plain(k1->type)) { case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: case SSH_KEYTYPE_SK_ECDSA: +#if OPENSSL_VERSION_NUMBER < 0x30000000L #ifdef HAVE_OPENSSL_ECC { - const EC_POINT *p1 = EC_KEY_get0_public_key(k1->ecdsa); - const EC_POINT *p2 = EC_KEY_get0_public_key(k2->ecdsa); - const EC_GROUP *g1 = EC_KEY_get0_group(k1->ecdsa); - const EC_GROUP *g2 = EC_KEY_get0_group(k2->ecdsa); + const EC_KEY *ec1 = EVP_PKEY_get0_EC_KEY(k1->key); + const EC_KEY *ec2 = EVP_PKEY_get0_EC_KEY(k2->key); + const EC_POINT *p1 = NULL; + const EC_POINT *p2 = NULL; + const EC_GROUP *g1 = NULL; + const EC_GROUP *g2 = NULL; + + if (ec1 == NULL || ec2 == NULL) { + return 1; + } - if (p1 == NULL || p2 == NULL) { + p1 = EC_KEY_get0_public_key(ec1); + p2 = EC_KEY_get0_public_key(ec2); + g1 = EC_KEY_get0_group(ec1); + g2 = EC_KEY_get0_group(ec2); + + if (p1 == NULL || p2 == NULL || g1 == NULL || g2 == NULL) { return 1; } @@ -695,23 +854,29 @@ int pki_key_compare(const ssh_key k1, } if (what == SSH_KEY_CMP_PRIVATE) { - if (bignum_cmp(EC_KEY_get0_private_key(k1->ecdsa), - EC_KEY_get0_private_key(k2->ecdsa))) { + if (bignum_cmp(EC_KEY_get0_private_key(ec1), + EC_KEY_get0_private_key(ec2))) { return 1; } } - break; } -#endif +#endif /* HAVE_OPENSSL_ECC */ +#endif /* OPENSSL_VERSION_NUMBER */ + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + rc = EVP_PKEY_eq(k1->key, k2->key); + if (rc != 1) { + return 1; + } + break; case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: - /* ed25519 keys handled globaly */ + /* ed25519 keys handled globally */ case SSH_KEYTYPE_UNKNOWN: default: return 1; } - return 0; } @@ -732,37 +897,22 @@ ssh_string pki_private_key_to_pem(const ssh_key key, } switch (key->type) { - case SSH_KEYTYPE_DSS: - pkey = EVP_PKEY_new(); - if (pkey == NULL) { - goto err; - } - - rc = EVP_PKEY_set1_DSA(pkey, key->dsa); - break; case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: - pkey = EVP_PKEY_new(); - if (pkey == NULL) { - goto err; - } - - rc = EVP_PKEY_set1_RSA(pkey, key->rsa); - break; -#ifdef HAVE_ECC case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: - pkey = EVP_PKEY_new(); - if (pkey == NULL) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { goto err; } + pkey = key->key; + + /* Mark the operation as successful as for the other key types */ + rc = 1; - rc = EVP_PKEY_set1_EC_KEY(pkey, key->ecdsa); break; -#endif case SSH_KEYTYPE_ED25519: -#ifdef HAVE_OPENSSL_ED25519 /* In OpenSSL, the input is the private key seed only, which means * the first half of the SSH private key (the second half is the * public key) */ @@ -779,11 +929,6 @@ ssh_string pki_private_key_to_pem(const ssh_key key, /* Mark the operation as successful as for the other key types */ rc = 1; break; -#else - SSH_LOG(SSH_LOG_WARN, "PEM output not supported for key type ssh-ed25519"); - goto err; -#endif - case SSH_KEYTYPE_DSS_CERT01: case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_ECDSA_P384_CERT01: @@ -791,11 +936,11 @@ ssh_string pki_private_key_to_pem(const ssh_key key, case SSH_KEYTYPE_ED25519_CERT01: case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_WARN, "Unknown or invalid private key type %d", key->type); + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key type %d", key->type); goto err; } if (rc != 1) { - SSH_LOG(SSH_LOG_WARN, "Failed to initialize EVP_PKEY structure"); + SSH_LOG(SSH_LOG_TRACE, "Failed to initialize EVP_PKEY structure"); goto err; } @@ -822,6 +967,9 @@ ssh_string pki_private_key_to_pem(const ssh_key key, pkey = NULL; if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to write private key: %s\n", + ERR_error_string(ERR_get_error(), NULL)); goto err; } @@ -854,20 +1002,13 @@ ssh_key pki_private_key_from_base64(const char *b64_key, void *auth_data) { BIO *mem = NULL; - DSA *dsa = NULL; - RSA *rsa = NULL; -#ifdef HAVE_OPENSSL_ED25519 +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY *ecdsa = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ uint8_t *ed25519 = NULL; -#else - ed25519_privkey *ed25519 = NULL; -#endif + uint8_t *ed25519_pubkey = NULL; ssh_key key = NULL; enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; -#ifdef HAVE_OPENSSL_ECC - EC_KEY *ecdsa = NULL; -#else - void *ecdsa = NULL; -#endif EVP_PKEY *pkey = NULL; mem = BIO_new_mem_buf((void*)b64_key, -1); @@ -888,53 +1029,41 @@ ssh_key pki_private_key_from_base64(const char *b64_key, BIO_free(mem); if (pkey == NULL) { - SSH_LOG(SSH_LOG_WARN, - "Parsing private key: %s", + SSH_LOG(SSH_LOG_TRACE, + "Error parsing private key: %s", ERR_error_string(ERR_get_error(), NULL)); return NULL; } switch (EVP_PKEY_base_id(pkey)) { - case EVP_PKEY_DSA: - dsa = EVP_PKEY_get1_DSA(pkey); - if (dsa == NULL) { - SSH_LOG(SSH_LOG_WARN, - "Parsing private key: %s", - ERR_error_string(ERR_get_error(),NULL)); - goto fail; - } - type = SSH_KEYTYPE_DSS; - break; case EVP_PKEY_RSA: - rsa = EVP_PKEY_get1_RSA(pkey); - if (rsa == NULL) { - SSH_LOG(SSH_LOG_WARN, - "Parsing private key: %s", - ERR_error_string(ERR_get_error(),NULL)); - goto fail; - } type = SSH_KEYTYPE_RSA; break; case EVP_PKEY_EC: #ifdef HAVE_OPENSSL_ECC - ecdsa = EVP_PKEY_get1_EC_KEY(pkey); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EVP_PKEY_get0_EC_KEY(pkey); if (ecdsa == NULL) { - SSH_LOG(SSH_LOG_WARN, - "Parsing private key: %s", + SSH_LOG(SSH_LOG_TRACE, + "Error parsing private key: %s", ERR_error_string(ERR_get_error(), NULL)); goto fail; } +#endif /* OPENSSL_VERSION_NUMBER */ /* pki_privatekey_type_from_string always returns P256 for ECDSA * keys, so we need to figure out the correct type here */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L type = pki_key_ecdsa_to_key_type(ecdsa); +#else + type = pki_key_ecdsa_to_key_type(pkey); +#endif /* OPENSSL_VERSION_NUMBER */ if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "Invalid private key."); + SSH_LOG(SSH_LOG_TRACE, "Invalid private key."); goto fail; } break; -#endif -#ifdef HAVE_OPENSSL_ED25519 +#endif /* HAVE_OPENSSL_ECC */ case EVP_PKEY_ED25519: { size_t key_len; @@ -955,7 +1084,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, ed25519 = malloc(key_len); if (ed25519 == NULL) { - SSH_LOG(SSH_LOG_WARN, "Out of memory"); + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); goto fail; } @@ -967,17 +1096,32 @@ ssh_key pki_private_key_from_base64(const char *b64_key, ERR_error_string(ERR_get_error(), NULL)); goto fail; } + + /* length matches the private key length */ + ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (ed25519_pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto fail; + } + + evp_rc = EVP_PKEY_get_raw_public_key(pkey, (uint8_t *)ed25519_pubkey, + &key_len); + if (evp_rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } type = SSH_KEYTYPE_ED25519; + } break; -#endif default: - EVP_PKEY_free(pkey); - SSH_LOG(SSH_LOG_WARN, "Unknown or invalid private key type %d", + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key type %d", EVP_PKEY_base_id(pkey)); + EVP_PKEY_free(pkey); return NULL; } - EVP_PKEY_free(pkey); key = ssh_key_new(); if (key == NULL) { @@ -987,164 +1131,207 @@ ssh_key pki_private_key_from_base64(const char *b64_key, key->type = type; key->type_c = ssh_key_type_to_char(type); key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; - key->dsa = dsa; - key->rsa = rsa; - key->ecdsa = ecdsa; + key->key = pkey; key->ed25519_privkey = ed25519; + key->ed25519_pubkey = ed25519_pubkey; #ifdef HAVE_OPENSSL_ECC if (is_ecdsa_key_type(key->type)) { - key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + key->ecdsa_nid = pki_key_ecdsa_to_nid(ecdsa); +#else + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->key); +#endif /* OPENSSL_VERSION_NUMBER */ } -#endif +#endif /* HAVE_OPENSSL_ECC */ return key; fail: EVP_PKEY_free(pkey); ssh_key_free(key); - DSA_free(dsa); - RSA_free(rsa); -#ifdef HAVE_OPENSSL_ECC - EC_KEY_free(ecdsa); -#endif -#ifdef HAVE_OPENSSL_ED25519 SAFE_FREE(ed25519); -#endif + SAFE_FREE(ed25519_pubkey); return NULL; } -int pki_privkey_build_dss(ssh_key key, +int pki_privkey_build_rsa(ssh_key key, + ssh_string n, + ssh_string e, + ssh_string d, + ssh_string iqmp, ssh_string p, - ssh_string q, - ssh_string g, - ssh_string pubkey, - ssh_string privkey) + ssh_string q) { int rc; - BIGNUM *bp, *bq, *bg, *bpub_key, *bpriv_key; - - key->dsa = DSA_new(); - if (key->dsa == NULL) { + BIGNUM *be = NULL, *bn = NULL, *bd = NULL; + BIGNUM *biqmp = NULL, *bp = NULL, *bq = NULL; + BIGNUM *aux = NULL, *d_consttime = NULL; + BIGNUM *bdmq1 = NULL, *bdmp1 = NULL; + BN_CTX *ctx = NULL; + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) { + return SSH_ERROR; + } +#else + RSA *key_rsa = RSA_new(); + if (key_rsa == NULL) { return SSH_ERROR; } +#endif /* OPENSSL_VERSION_NUMBER */ + bn = ssh_make_string_bn(n); + be = ssh_make_string_bn(e); + bd = ssh_make_string_bn(d); + biqmp = ssh_make_string_bn(iqmp); bp = ssh_make_string_bn(p); bq = ssh_make_string_bn(q); - bg = ssh_make_string_bn(g); - bpub_key = ssh_make_string_bn(pubkey); - bpriv_key = ssh_make_string_bn(privkey); - if (bp == NULL || bq == NULL || - bg == NULL || bpub_key == NULL) { + if (be == NULL || bn == NULL || bd == NULL || + /*biqmp == NULL ||*/ bp == NULL || bq == NULL) { + rc = SSH_ERROR; goto fail; } - /* Memory management of bp, qq and bg is transferred to DSA object */ - rc = DSA_set0_pqg(key->dsa, bp, bq, bg); - if (rc == 0) { + /* Calculate remaining CRT parameters for OpenSSL to be happy + * taken from OpenSSH */ + if ((ctx = BN_CTX_new()) == NULL) { + rc = SSH_ERROR; goto fail; } - - /* Memory management of bpub_key and bpriv_key is transferred to DSA object */ - rc = DSA_set0_key(key->dsa, bpub_key, bpriv_key); - if (rc == 0) { + if ((aux = BN_new()) == NULL || + (bdmq1 = BN_new()) == NULL || + (bdmp1 = BN_new()) == NULL) { + rc = SSH_ERROR; goto fail; } + if ((d_consttime = BN_dup(bd)) == NULL) { + rc = SSH_ERROR; + goto fail; + } + BN_set_flags(aux, BN_FLG_CONSTTIME); + BN_set_flags(d_consttime, BN_FLG_CONSTTIME); - return SSH_OK; -fail: - DSA_free(key->dsa); - return SSH_ERROR; -} - -int pki_pubkey_build_dss(ssh_key key, - ssh_string p, - ssh_string q, - ssh_string g, - ssh_string pubkey) { - int rc; - BIGNUM *bp = NULL, *bq = NULL, *bg = NULL, *bpub_key = NULL; - - key->dsa = DSA_new(); - if (key->dsa == NULL) { - return SSH_ERROR; + if ((BN_sub(aux, bq, BN_value_one()) == 0) || + (BN_mod(bdmq1, d_consttime, aux, ctx) == 0) || + (BN_sub(aux, bp, BN_value_one()) == 0) || + (BN_mod(bdmp1, d_consttime, aux, ctx) == 0)) { + rc = SSH_ERROR; + goto fail; } - bp = ssh_make_string_bn(p); - bq = ssh_make_string_bn(q); - bg = ssh_make_string_bn(g); - bpub_key = ssh_make_string_bn(pubkey); - if (bp == NULL || bq == NULL || - bg == NULL || bpub_key == NULL) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* Memory management of be, bn and bd is transferred to RSA object */ + rc = RSA_set0_key(key_rsa, bn, be, bd); + if (rc == 0) { goto fail; } - /* Memory management of bp, bq and bg is transferred to DSA object */ - rc = DSA_set0_pqg(key->dsa, bp, bq, bg); + /* Memory management of bp and bq is transferred to RSA object */ + rc = RSA_set0_factors(key_rsa, bp, bq); if (rc == 0) { goto fail; } - /* Memory management of npub_key is transferred to DSA object */ - rc = DSA_set0_key(key->dsa, bpub_key, NULL); + /* p, q, dmp1, dmq1 and iqmp may be NULL in private keys, but the RSA + * operations are much faster when these values are available. + * https://www.openssl.org/docs/man1.0.2/crypto/rsa.html + * And OpenSSL fails to export these keys to PEM if these are missing: + * https://github.com/openssl/openssl/issues/21826 + */ + rc = RSA_set0_crt_params(key_rsa, bdmp1, bdmq1, biqmp); if (rc == 0) { goto fail; } + bignum_safe_free(aux); + bignum_safe_free(d_consttime); + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + goto fail; + } + + rc = EVP_PKEY_assign_RSA(key->key, key_rsa); + if (rc != 1) { + goto fail; + } return SSH_OK; fail: - DSA_free(key->dsa); + RSA_free(key_rsa); + EVP_PKEY_free(key->key); return SSH_ERROR; -} +#else + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_N, bn); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_E, be); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_D, bd); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } -int pki_privkey_build_rsa(ssh_key key, - ssh_string n, - ssh_string e, - ssh_string d, - UNUSED_PARAM(ssh_string iqmp), - ssh_string p, - ssh_string q) -{ - int rc; - BIGNUM *be, *bn, *bd/*, *biqmp*/, *bp, *bq; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_FACTOR1, bp); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } - key->rsa = RSA_new(); - if (key->rsa == NULL) { - return SSH_ERROR; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_FACTOR2, bq); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; } - bn = ssh_make_string_bn(n); - be = ssh_make_string_bn(e); - bd = ssh_make_string_bn(d); - /*biqmp = ssh_make_string_bn(iqmp);*/ - bp = ssh_make_string_bn(p); - bq = ssh_make_string_bn(q); - if (be == NULL || bn == NULL || bd == NULL || - /*biqmp == NULL ||*/ bp == NULL || bq == NULL) { + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_EXPONENT1, bdmp1); + if (rc != 1) { + rc = SSH_ERROR; goto fail; } - /* Memory management of be, bn and bd is transferred to RSA object */ - rc = RSA_set0_key(key->rsa, bn, be, bd); - if (rc == 0) { + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_EXPONENT2, bdmq1); + if (rc != 1) { + rc = SSH_ERROR; goto fail; } - /* Memory management of bp and bq is transferred to RSA object */ - rc = RSA_set0_factors(key->rsa, bp, bq); - if (rc == 0) { + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, biqmp); + if (rc != 1) { + rc = SSH_ERROR; goto fail; } - /* p, q, dmp1, dmq1 and iqmp may be NULL in private keys, but the RSA - * operations are much faster when these values are available. - * https://www.openssl.org/docs/man1.0.2/crypto/rsa.html - */ - /* RSA_set0_crt_params(key->rsa, biqmp, NULL, NULL); - TODO calculate missing crt_params */ + rc = evp_build_pkey("RSA", param_bld, &(key->key), EVP_PKEY_KEYPAIR); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to import private key: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + rc = SSH_ERROR; + goto fail; + } - return SSH_OK; fail: - RSA_free(key->rsa); - return SSH_ERROR; + OSSL_PARAM_BLD_free(param_bld); + bignum_safe_free(bn); + bignum_safe_free(be); + bignum_safe_free(bd); + bignum_safe_free(bp); + bignum_safe_free(bq); + bignum_safe_free(biqmp); + + bignum_safe_free(aux); + bignum_safe_free(d_consttime); + bignum_safe_free(bdmp1); + bignum_safe_free(bdmq1); + BN_CTX_free(ctx); + return rc; +#endif /* OPENSSL_VERSION_NUMBER */ } int pki_pubkey_build_rsa(ssh_key key, @@ -1152,31 +1339,71 @@ int pki_pubkey_build_rsa(ssh_key key, ssh_string n) { int rc; BIGNUM *be = NULL, *bn = NULL; - - key->rsa = RSA_new(); - if (key->rsa == NULL) { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) { + return SSH_ERROR; + } +#else + RSA *key_rsa = RSA_new(); + if (key_rsa == NULL) { return SSH_ERROR; } +#endif /* OPENSSL_VERSION_NUMBER */ be = ssh_make_string_bn(e); bn = ssh_make_string_bn(n); if (be == NULL || bn == NULL) { + rc = SSH_ERROR; goto fail; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L /* Memory management of bn and be is transferred to RSA object */ - rc = RSA_set0_key(key->rsa, bn, be, NULL); + rc = RSA_set0_key(key_rsa, bn, be, NULL); if (rc == 0) { goto fail; } + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + goto fail; + } + + rc = EVP_PKEY_assign_RSA(key->key, key_rsa); + if (rc != 1) { + goto fail; + } + return SSH_OK; fail: - RSA_free(key->rsa); + EVP_PKEY_free(key->key); + RSA_free(key_rsa); return SSH_ERROR; +#else + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_N, bn); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_E, be); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = evp_build_pkey("RSA", param_bld, &(key->key), EVP_PKEY_PUBLIC_KEY); + +fail: + OSSL_PARAM_BLD_free(param_bld); + bignum_safe_free(bn); + bignum_safe_free(be); + + return rc; +#endif /* OPENSSL_VERSION_NUMBER */ } -ssh_string pki_publickey_to_blob(const ssh_key key) +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) { ssh_buffer buffer; ssh_string type_s; @@ -1186,7 +1413,15 @@ ssh_string pki_publickey_to_blob(const ssh_key key) ssh_string p = NULL; ssh_string g = NULL; ssh_string q = NULL; + ssh_string d = NULL; + ssh_string iqmp = NULL; int rc; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + BIGNUM *bp = NULL, *bq = NULL, *bg = NULL, *bpub_key = NULL, + *bn = NULL, *be = NULL, + *bd = NULL, *biqmp = NULL; + OSSL_PARAM *params = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ buffer = ssh_buffer_new(); if (buffer == NULL) { @@ -1216,62 +1451,37 @@ ssh_string pki_publickey_to_blob(const ssh_key key) } switch (key->type) { - case SSH_KEYTYPE_DSS: { - const BIGNUM *bp, *bq, *bg, *bpub_key; - DSA_get0_pqg(key->dsa, &bp, &bq, &bg); - p = ssh_make_bignum_string((BIGNUM *)bp); - if (p == NULL) { - goto fail; - } - - q = ssh_make_bignum_string((BIGNUM *)bq); - if (q == NULL) { - goto fail; - } - - g = ssh_make_bignum_string((BIGNUM *)bg); - if (g == NULL) { - goto fail; - } - - DSA_get0_key(key->dsa, &bpub_key, NULL); - n = ssh_make_bignum_string((BIGNUM *)bpub_key); - if (n == NULL) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const BIGNUM *be = NULL, *bn = NULL; + const RSA *key_rsa = EVP_PKEY_get0_RSA(key->key); + RSA_get0_key(key_rsa, &bn, &be, NULL); +#else + const OSSL_PARAM *out_param = NULL; + rc = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, ¶ms); + if (rc != 1) { goto fail; } - - if (ssh_buffer_add_ssh_string(buffer, p) < 0) { + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_E); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param E has been found"); goto fail; } - if (ssh_buffer_add_ssh_string(buffer, q) < 0) { + rc = OSSL_PARAM_get_BN(out_param, &be); + if (rc != 1) { goto fail; } - if (ssh_buffer_add_ssh_string(buffer, g) < 0) { + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_N); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param N has been found"); goto fail; } - if (ssh_buffer_add_ssh_string(buffer, n) < 0) { + rc = OSSL_PARAM_get_BN(out_param, &bn); + if (rc != 1) { goto fail; } - - ssh_string_burn(p); - SSH_STRING_FREE(p); - p = NULL; - ssh_string_burn(g); - SSH_STRING_FREE(g); - g = NULL; - ssh_string_burn(q); - SSH_STRING_FREE(q); - q = NULL; - ssh_string_burn(n); - SSH_STRING_FREE(n); - n = NULL; - - break; - } - case SSH_KEYTYPE_RSA: - case SSH_KEYTYPE_RSA1: { - const BIGNUM *be, *bn; - RSA_get0_key(key->rsa, &bn, &be, NULL); +#endif /* OPENSSL_VERSION_NUMBER */ e = ssh_make_bignum_string((BIGNUM *)be); if (e == NULL) { goto fail; @@ -1282,31 +1492,165 @@ ssh_string pki_publickey_to_blob(const ssh_key key) goto fail; } - if (ssh_buffer_add_ssh_string(buffer, e) < 0) { - goto fail; - } - if (ssh_buffer_add_ssh_string(buffer, n) < 0) { - goto fail; - } + if (type == SSH_KEY_PUBLIC) { + /* The N and E parts are swapped in the public key export ! */ + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + } else if (type == SSH_KEY_PRIVATE) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const BIGNUM *bd, *biqmp, *bp, *bq; + RSA_get0_key(key_rsa, NULL, NULL, &bd); + RSA_get0_factors(key_rsa, &bp, &bq); + RSA_get0_crt_params(key_rsa, NULL, NULL, &biqmp); +#else + rc = EVP_PKEY_todata(key->key, EVP_PKEY_KEYPAIR, ¶ms); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_D); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param D has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bd); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_FACTOR1); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param P has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bp); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_FACTOR2); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param Q has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bq); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_COEFFICIENT1); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param IQMP has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &biqmp); + if (rc != 1) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + d = ssh_make_bignum_string((BIGNUM *)bd); + if (d == NULL) { + goto fail; + } + + iqmp = ssh_make_bignum_string((BIGNUM *)biqmp); + if (iqmp == NULL) { + goto fail; + } + + p = ssh_make_bignum_string((BIGNUM *)bp); + if (p == NULL) { + goto fail; + } + + q = ssh_make_bignum_string((BIGNUM *)bq); + if (q == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, iqmp); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, q); + if (rc < 0) { + goto fail; + } + ssh_string_burn(d); + SSH_STRING_FREE(d); + d = NULL; + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); + iqmp = NULL; + ssh_string_burn(p); + SSH_STRING_FREE(p); + p = NULL; + ssh_string_burn(q); + SSH_STRING_FREE(q); + q = NULL; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bd); + bignum_safe_free(biqmp); + bignum_safe_free(bp); + bignum_safe_free(bq); +#endif /* OPENSSL_VERSION_NUMBER */ + } ssh_string_burn(e); SSH_STRING_FREE(e); e = NULL; ssh_string_burn(n); SSH_STRING_FREE(n); n = NULL; - +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bn); + bignum_safe_free(be); + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ break; } case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: - rc = pki_ed25519_public_key_to_blob(buffer, key); - if (rc == SSH_ERROR){ - goto fail; - } - if (key->type == SSH_KEYTYPE_SK_ED25519 && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; + if (type == SSH_KEY_PUBLIC) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR){ + goto fail; + } + /* public key can contain certificate sk information */ + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto fail; + } + } + } else { + rc = pki_ed25519_private_key_to_blob(buffer, key); + if (rc == SSH_ERROR){ + goto fail; + } } break; case SSH_KEYTYPE_ECDSA_P256: @@ -1314,50 +1658,150 @@ ssh_string pki_publickey_to_blob(const ssh_key key) case SSH_KEYTYPE_ECDSA_P521: case SSH_KEYTYPE_SK_ECDSA: #ifdef HAVE_OPENSSL_ECC - type_s = ssh_string_from_char(pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); - if (type_s == NULL) { - SSH_BUFFER_FREE(buffer); - return NULL; - } + { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EC_GROUP *group = NULL; + EC_POINT *point = NULL; + const void *pubkey; + size_t pubkey_len; + OSSL_PARAM *locate_param = NULL; +#else + const EC_GROUP *group = NULL; + const EC_POINT *point = NULL; + const BIGNUM *exp = NULL; + EC_KEY *ec = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + + type_s = ssh_string_from_char(pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); + if (type_s == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } - rc = ssh_buffer_add_ssh_string(buffer, type_s); - SSH_STRING_FREE(type_s); - if (rc < 0) { - SSH_BUFFER_FREE(buffer); - return NULL; - } + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ec = EVP_PKEY_get0_EC_KEY(key->key); + if (ec == NULL) { + goto fail; + } #ifdef WITH_PKCS11_URI - if (ssh_key_is_private(key) && !EC_KEY_get0_public_key(key->ecdsa)) { - SSH_LOG(SSH_LOG_INFO, "It is mandatory to have separate public" - " ECDSA key objects in the PKCS #11 device. Unlike RSA," - " ECDSA public keys cannot be derived from their private keys."); - goto fail; - } -#endif - e = make_ecpoint_string(EC_KEY_get0_group(key->ecdsa), - EC_KEY_get0_public_key(key->ecdsa)); - if (e == NULL) { - SSH_BUFFER_FREE(buffer); - return NULL; - } + if (ssh_key_is_private(key) && !EC_KEY_get0_public_key(ec)) { + SSH_LOG(SSH_LOG_TRACE, "It is mandatory to have separate" + " public ECDSA key objects in the PKCS #11 device." + " Unlike RSA, ECDSA public keys cannot be derived" + " from their private keys."); + goto fail; + } +#endif /* WITH_PKCS11_URI */ + group = EC_KEY_get0_group(ec); + point = EC_KEY_get0_public_key(ec); + if (group == NULL || point == NULL) { + goto fail; + } + e = pki_key_make_ecpoint_string(group, point); +#else + rc = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, ¶ms); + if (rc < 0) { + goto fail; + } - rc = ssh_buffer_add_ssh_string(buffer, e); - if (rc < 0) { - goto fail; - } + locate_param = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PUB_KEY); +#ifdef WITH_PKCS11_URI + if (ssh_key_is_private(key) && !locate_param) { + SSH_LOG(SSH_LOG_TRACE, "It is mandatory to have separate" + " public ECDSA key objects in the PKCS #11 device." + " Unlike RSA, ECDSA public keys cannot be derived" + " from their private keys."); + goto fail; + } +#endif /* WITH_PKCS11_URI */ - ssh_string_burn(e); - SSH_STRING_FREE(e); - e = NULL; + rc = OSSL_PARAM_get_octet_string_ptr(locate_param, &pubkey, &pubkey_len); + if (rc != 1) { + goto fail; + } + /* Convert the data to low-level representation */ + group = EC_GROUP_new_by_curve_name_ex(NULL, NULL, key->ecdsa_nid); + point = EC_POINT_new(group); + rc = EC_POINT_oct2point(group, point, pubkey, pubkey_len, NULL); + if (group == NULL || point == NULL || rc != 1) { + EC_GROUP_free(group); + EC_POINT_free(point); + goto fail; + } - if (key->type == SSH_KEYTYPE_SK_ECDSA && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; - } + e = pki_key_make_ecpoint_string(group, point); + EC_GROUP_free(group); + EC_POINT_free(point); +#endif /* OPENSSL_VERSION_NUMBER */ + if (e == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } - break; -#endif + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(e); + SSH_STRING_FREE(e); + e = NULL; + if (type == SSH_KEY_PRIVATE) { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + rc = EVP_PKEY_todata(key->key, EVP_PKEY_KEYPAIR, ¶ms); + if (rc < 0) { + goto fail; + } + + locate_param = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PRIV_KEY); + rc = OSSL_PARAM_get_BN(locate_param, &bd); + if (rc != 1) { + goto fail; + } + d = ssh_make_bignum_string((BIGNUM *)bd); + if (d == NULL) { + goto fail; + } + if (ssh_buffer_add_ssh_string(buffer, d) < 0) { + goto fail; + } +#else + exp = EC_KEY_get0_private_key(ec); + if (exp == NULL) { + goto fail; + } + d = ssh_make_bignum_string((BIGNUM *)exp); + if (d == NULL) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + ssh_string_burn(d); + SSH_STRING_FREE(d); + d = NULL; + } else if (key->type == SSH_KEYTYPE_SK_ECDSA) { + /* public key can contain certificate sk information */ + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto fail; + } + } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ + break; + } +#endif /* HAVE_OPENSSL_ECC */ case SSH_KEYTYPE_UNKNOWN: default: goto fail; @@ -1390,96 +1834,25 @@ fail: SSH_STRING_FREE(q); ssh_string_burn(n); SSH_STRING_FREE(n); + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bp); + bignum_safe_free(bq); + bignum_safe_free(bg); + bignum_safe_free(bpub_key); + bignum_safe_free(bn); + bignum_safe_free(be); + bignum_safe_free(bd); + bignum_safe_free(biqmp); + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ return NULL; } -static ssh_string pki_dsa_signature_to_blob(const ssh_signature sig) -{ - char buffer[40] = { 0 }; - ssh_string sig_blob = NULL; - const BIGNUM *pr = NULL, *ps = NULL; - - ssh_string r = NULL; - int r_len, r_offset_in, r_offset_out; - - ssh_string s = NULL; - int s_len, s_offset_in, s_offset_out; - - const unsigned char *raw_sig_data = NULL; - size_t raw_sig_len; - int rc; - - 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; - } - - rc = ssh_string_fill(sig_blob, buffer, 40); - if (rc < 0) { - SSH_STRING_FREE(sig_blob); - return NULL; - } - - 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; @@ -1572,9 +1945,6 @@ ssh_string pki_signature_to_blob(const ssh_signature sig) ssh_string sig_blob = NULL; switch(sig->type) { - case SSH_KEYTYPE_DSS: - sig_blob = pki_dsa_signature_to_blob(sig); - break; case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: sig_blob = ssh_string_copy(sig->raw_sig); @@ -1588,10 +1958,10 @@ ssh_string pki_signature_to_blob(const ssh_signature sig) #ifdef HAVE_OPENSSL_ECC sig_blob = pki_ecdsa_signature_to_blob(sig); break; -#endif +#endif /* HAVE_OPENSSL_ECC */ default: case SSH_KEYTYPE_UNKNOWN: - SSH_LOG(SSH_LOG_WARN, "Unknown signature key type: %s", sig->type_c); + SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %s", sig->type_c); return NULL; } @@ -1610,14 +1980,25 @@ static int pki_signature_from_rsa_blob(const ssh_key pubkey, size_t rsalen = 0; size_t len = ssh_string_len(sig_blob); - if (pubkey->rsa == NULL) { - SSH_LOG(SSH_LOG_WARN, "Pubkey RSA field NULL"); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const RSA *rsa = EVP_PKEY_get0_RSA(pubkey->key); + + if (rsa == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA field NULL"); goto errout; } - rsalen = RSA_size(pubkey->rsa); + rsalen = RSA_size(rsa); +#else + if (EVP_PKEY_get_base_id(pubkey->key) != EVP_PKEY_RSA) { + SSH_LOG(SSH_LOG_TRACE, "Key has no RSA pubkey"); + goto errout; + } + + rsalen = EVP_PKEY_size(pubkey->key); +#endif /* OPENSSL_VERSION_NUMBER */ if (len > rsalen) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Signature is too big: %lu > %lu", (unsigned long)len, (unsigned long)rsalen); @@ -1625,9 +2006,9 @@ static int pki_signature_from_rsa_blob(const ssh_key pubkey, } #ifdef DEBUG_CRYPTO - SSH_LOG(SSH_LOG_WARN, "RSA signature len: %lu", (unsigned long)len); + SSH_LOG(SSH_LOG_DEBUG, "RSA signature len: %lu", (unsigned long)len); ssh_log_hexdump("RSA signature", ssh_string_data(sig_blob), len); -#endif +#endif /* DEBUG_CRYPTO */ if (len == rsalen) { sig->raw_sig = ssh_string_copy(sig_blob); @@ -1667,133 +2048,6 @@ errout: return SSH_ERROR; } -static int pki_signature_from_dsa_blob(UNUSED_PARAM(const ssh_key pubkey), - const ssh_string sig_blob, - ssh_signature sig) -{ - DSA_SIG *dsa_sig = NULL; - BIGNUM *pr = NULL, *ps = NULL; - - ssh_string r; - ssh_string s; - - size_t len; - - int raw_sig_len = 0; - unsigned char *raw_sig_data = NULL; - unsigned char *temp_raw_sig = NULL; - - int rc; - - len = ssh_string_len(sig_blob); - - /* 40 is the dual signature blob len. */ - if (len != 40) { - SSH_LOG(SSH_LOG_WARN, - "Signature has wrong size: %lu", - (unsigned long)len); - goto error; - } - -#ifdef DEBUG_CRYPTO - ssh_log_hexdump("r", ssh_string_data(sig_blob), 20); - ssh_log_hexdump("s", (unsigned char *)ssh_string_data(sig_blob) + 20, 20); -#endif - - r = ssh_string_new(20); - if (r == NULL) { - goto error; - } - rc = ssh_string_fill(r, ssh_string_data(sig_blob), 20); - if (rc < 0) { - SSH_STRING_FREE(r); - goto error; - } - - 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; - } - rc = ssh_string_fill(s, (char *)ssh_string_data(sig_blob) + 20, 20); - if (rc < 0) { - SSH_STRING_FREE(s); - goto error; - } - - ps = ssh_make_string_bn(s); - ssh_string_burn(s); - SSH_STRING_FREE(s); - if (ps == NULL) { - goto error; - } - - dsa_sig = DSA_SIG_new(); - if (dsa_sig == NULL) { - goto error; - } - - /* Memory management of pr and ps is transferred to DSA signature - * object */ - rc = DSA_SIG_set0(dsa_sig, pr, ps); - if (rc == 0) { - goto error; - } - ps = NULL; - pr = NULL; - - /* Get the expected size of the buffer */ - rc = i2d_DSA_SIG(dsa_sig, NULL); - if (rc <= 0) { - goto error; - } - raw_sig_len = rc; - - raw_sig_data = (unsigned char *)calloc(1, raw_sig_len); - if (raw_sig_data == NULL) { - goto error; - } - temp_raw_sig = raw_sig_data; - - /* It is necessary to use a temporary pointer as i2d_* "advances" the - * pointer */ - raw_sig_len = i2d_DSA_SIG(dsa_sig, &temp_raw_sig); - if (raw_sig_len <= 0) { - goto error; - } - - sig->raw_sig = ssh_string_new(raw_sig_len); - if (sig->raw_sig == NULL) { - explicit_bzero(raw_sig_data, raw_sig_len); - goto error; - } - - rc = ssh_string_fill(sig->raw_sig, raw_sig_data, raw_sig_len); - if (rc < 0) { - explicit_bzero(raw_sig_data, raw_sig_len); - goto error; - } - - explicit_bzero(raw_sig_data, raw_sig_len); - SAFE_FREE(raw_sig_data); - DSA_SIG_free(dsa_sig); - - return SSH_OK; - -error: - bignum_safe_free(ps); - bignum_safe_free(pr); - SAFE_FREE(raw_sig_data); - DSA_SIG_free(dsa_sig); - return SSH_ERROR; -} - static int pki_signature_from_ecdsa_blob(UNUSED_PARAM(const ssh_key pubkey), const ssh_string sig_blob, ssh_signature sig) @@ -1852,7 +2106,7 @@ static int pki_signature_from_ecdsa_blob(UNUSED_PARAM(const ssh_key pubkey), if (rlen != 0) { ssh_string_burn(s); SSH_STRING_FREE(s); - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Signature has remaining bytes in inner " "sigblob: %lu", (unsigned long)rlen); @@ -1941,7 +2195,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, int rc; if (ssh_key_type_plain(pubkey->type) != type) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Incompatible public key provided (%d) expecting (%d)", type, pubkey->type); @@ -1958,12 +2212,6 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, sig->hash_type = hash_type; switch(type) { - case SSH_KEYTYPE_DSS: - rc = pki_signature_from_dsa_blob(pubkey, sig_blob, sig); - if (rc != SSH_OK) { - goto error; - } - break; case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: rc = pki_signature_from_rsa_blob(pubkey, sig_blob, sig); @@ -1995,7 +2243,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, #endif default: case SSH_KEYTYPE_UNKNOWN: - SSH_LOG(SSH_LOG_WARN, "Unknown signature type"); + SSH_LOG(SSH_LOG_TRACE, "Unknown signature type"); goto error; } @@ -2038,44 +2286,12 @@ static const EVP_MD *pki_digest_to_md(enum ssh_digest_e hash_type) static EVP_PKEY *pki_key_to_pkey(ssh_key key) { EVP_PKEY *pkey = NULL; + int rc = 0; -#ifdef WITH_PKCS11_URI - if (key->flags & SSH_KEY_FLAG_PKCS11_URI) { - pkey = key->key; - return pkey; - } -#endif - - switch(key->type) { - case SSH_KEYTYPE_DSS: - case SSH_KEYTYPE_DSS_CERT01: - if (key->dsa == NULL) { - SSH_LOG(SSH_LOG_TRACE, "NULL key->dsa"); - goto error; - } - pkey = EVP_PKEY_new(); - if (pkey == NULL) { - SSH_LOG(SSH_LOG_TRACE, "Out of memory"); - return NULL; - } - - EVP_PKEY_set1_DSA(pkey, key->dsa); - break; + switch (key->type) { case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_RSA_CERT01: - if (key->rsa == NULL) { - SSH_LOG(SSH_LOG_TRACE, "NULL key->rsa"); - goto error; - } - pkey = EVP_PKEY_new(); - if (pkey == NULL) { - SSH_LOG(SSH_LOG_TRACE, "Out of memory"); - return NULL; - } - - EVP_PKEY_set1_RSA(pkey, key->rsa); - break; case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: @@ -2084,25 +2300,21 @@ static EVP_PKEY *pki_key_to_pkey(ssh_key key) case SSH_KEYTYPE_ECDSA_P521_CERT01: case SSH_KEYTYPE_SK_ECDSA: case SSH_KEYTYPE_SK_ECDSA_CERT01: -# if defined(HAVE_OPENSSL_ECC) - if (key->ecdsa == NULL) { - SSH_LOG(SSH_LOG_TRACE, "NULL key->ecdsa"); + if (key->key == NULL) { + SSH_LOG(SSH_LOG_TRACE, "NULL key->key"); goto error; } - pkey = EVP_PKEY_new(); - if (pkey == NULL) { - SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to reference EVP_PKEY"); return NULL; } - - EVP_PKEY_set1_EC_KEY(pkey, key->ecdsa); + pkey = key->key; break; -# endif case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_ED25519_CERT01: case SSH_KEYTYPE_SK_ED25519: case SSH_KEYTYPE_SK_ED25519_CERT01: -# if defined(HAVE_OPENSSL_ED25519) if (ssh_key_is_private(key)) { if (key->ed25519_privkey == NULL) { SSH_LOG(SSH_LOG_TRACE, "NULL key->ed25519_privkey"); @@ -2130,7 +2342,6 @@ static EVP_PKEY *pki_key_to_pkey(ssh_key key) return NULL; } break; -#endif case SSH_KEYTYPE_UNKNOWN: default: SSH_LOG(SSH_LOG_TRACE, "Unknown private key algorithm for type: %d", @@ -2148,7 +2359,7 @@ error: /** * @internal * - * @brief Sign the given input data. The digest of to be signed is calculated + * @brief Sign the given input data. The digest to be signed is calculated * internally as necessary. * * @param[in] privkey The private key to be used for signing. @@ -2186,14 +2397,6 @@ ssh_signature pki_sign_data(const ssh_key privkey, return NULL; } -#ifndef HAVE_OPENSSL_ED25519 - if (privkey->type == SSH_KEYTYPE_ED25519 || - privkey->type == SSH_KEYTYPE_ED25519_CERT01) - { - return pki_do_sign_hash(privkey, input, input_len, hash_type); - } -#endif - /* Set hash algorithm to be used */ md = pki_digest_to_md(hash_type); if (md == NULL) { @@ -2217,7 +2420,7 @@ ssh_signature pki_sign_data(const ssh_key privkey, } /* Create the context */ - ctx = EVP_MD_CTX_create(); + ctx = EVP_MD_CTX_new(); if (ctx == NULL) { SSH_LOG(SSH_LOG_TRACE, "Out of memory"); goto out; @@ -2232,7 +2435,6 @@ ssh_signature pki_sign_data(const ssh_key privkey, goto out; } -#ifdef HAVE_OPENSSL_EVP_DIGESTSIGN rc = EVP_DigestSign(ctx, raw_sig_data, &raw_sig_len, input, input_len); if (rc != 1) { SSH_LOG(SSH_LOG_TRACE, @@ -2240,23 +2442,6 @@ ssh_signature pki_sign_data(const ssh_key privkey, ERR_error_string(ERR_get_error(), NULL)); goto out; } -#else - rc = EVP_DigestSignUpdate(ctx, input, input_len); - if (rc != 1) { - SSH_LOG(SSH_LOG_TRACE, - "EVP_DigestSignUpdate() failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - goto out; - } - - rc = EVP_DigestSignFinal(ctx, raw_sig_data, &raw_sig_len); - if (rc != 1) { - SSH_LOG(SSH_LOG_TRACE, - "EVP_DigestSignFinal() failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - goto out; - } -#endif #ifdef DEBUG_CRYPTO ssh_log_hexdump("Generated signature", raw_sig_data, raw_sig_len); @@ -2294,9 +2479,7 @@ out: explicit_bzero(raw_sig_data, raw_sig_len); } SAFE_FREE(raw_sig_data); - if (pkey != NULL) { - EVP_PKEY_free(pkey); - } + EVP_PKEY_free(pkey); return sig; } @@ -2325,15 +2508,15 @@ int pki_verify_data_signature(ssh_signature signature, unsigned char *raw_sig_data = NULL; unsigned int raw_sig_len; + /* Function return code + * Do not change this variable throughout the function until the signature + * is successfully verified! + */ int rc = SSH_ERROR; - int evp_rc; + int ok; if (pubkey == NULL || ssh_key_is_private(pubkey) || input == NULL || - signature == NULL || (signature->raw_sig == NULL -#ifndef HAVE_OPENSSL_ED25519 - && signature->ed25519_sig == NULL -#endif - )) + signature == NULL || signature->raw_sig == NULL) { SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " "pki_verify_data_signature()"); @@ -2341,21 +2524,11 @@ int pki_verify_data_signature(ssh_signature signature, } /* Check if public key and hash type are compatible */ - rc = pki_key_check_hash_compatible(pubkey, signature->hash_type); - if (rc != SSH_OK) { + ok = pki_key_check_hash_compatible(pubkey, signature->hash_type); + if (ok != SSH_OK) { return SSH_ERROR; } -#ifndef HAVE_OPENSSL_ED25519 - if (pubkey->type == SSH_KEYTYPE_ED25519 || - pubkey->type == SSH_KEYTYPE_ED25519_CERT01 || - pubkey->type == SSH_KEYTYPE_SK_ED25519 || - pubkey->type == SSH_KEYTYPE_SK_ED25519_CERT01) - { - return pki_ed25519_verify(pubkey, signature, input, input_len); - } -#endif - /* Get the signature to be verified */ raw_sig_data = ssh_string_data(signature->raw_sig); raw_sig_len = ssh_string_len(signature->raw_sig); @@ -2378,7 +2551,7 @@ int pki_verify_data_signature(ssh_signature signature, } /* Create the context */ - ctx = EVP_MD_CTX_create(); + ctx = EVP_MD_CTX_new(); if (ctx == NULL) { SSH_LOG(SSH_LOG_TRACE, "Failed to create EVP_MD_CTX: %s", @@ -2387,48 +2560,69 @@ int pki_verify_data_signature(ssh_signature signature, } /* Verify the signature */ - evp_rc = EVP_DigestVerifyInit(ctx, NULL, md, NULL, pkey); - if (evp_rc != 1){ + ok = EVP_DigestVerifyInit(ctx, NULL, md, NULL, pkey); + if (ok != 1){ SSH_LOG(SSH_LOG_TRACE, "EVP_DigestVerifyInit() failed: %s", ERR_error_string(ERR_get_error(), NULL)); goto out; } -#ifdef HAVE_OPENSSL_EVP_DIGESTVERIFY - evp_rc = EVP_DigestVerify(ctx, raw_sig_data, raw_sig_len, input, input_len); -#else - evp_rc = EVP_DigestVerifyUpdate(ctx, input, input_len); - if (evp_rc != 1) { + ok = EVP_DigestVerify(ctx, raw_sig_data, raw_sig_len, input, input_len); + if (ok != 1) { SSH_LOG(SSH_LOG_TRACE, - "EVP_DigestVerifyUpdate() failed: %s", + "Signature invalid: %s", ERR_error_string(ERR_get_error(), NULL)); goto out; } - evp_rc = EVP_DigestVerifyFinal(ctx, raw_sig_data, raw_sig_len); -#endif - if (evp_rc == 1) { - SSH_LOG(SSH_LOG_TRACE, "Signature valid"); - rc = SSH_OK; - } else { - SSH_LOG(SSH_LOG_TRACE, - "Signature invalid: %s", - ERR_error_string(ERR_get_error(), NULL)); - rc = SSH_ERROR; - } + SSH_LOG(SSH_LOG_TRACE, "Signature valid"); + rc = SSH_OK; out: - if (ctx != NULL) { - EVP_MD_CTX_free(ctx); - } - if (pkey != NULL) { + EVP_MD_CTX_free(ctx); + EVP_PKEY_free(pkey); + return rc; +} + +int ssh_key_size(ssh_key key) +{ + int bits = 0; + EVP_PKEY *pkey = NULL; + + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + pkey = pki_key_to_pkey(key); + if (pkey == NULL) { + return SSH_ERROR; + } + bits = EVP_PKEY_bits(pkey); EVP_PKEY_free(pkey); + return bits; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys have fixed size */ + return 255; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_UNKNOWN: + default: + return SSH_ERROR; } - return rc; } -#ifdef HAVE_OPENSSL_ED25519 int pki_key_generate_ed25519(ssh_key key) { int evp_rc; @@ -2513,50 +2707,21 @@ error: return SSH_ERROR; } -#else -ssh_signature pki_do_sign_hash(const ssh_key privkey, - const unsigned char *hash, - size_t hlen, - enum ssh_digest_e hash_type) -{ - ssh_signature sig = NULL; - int rc; - - sig = ssh_signature_new(); - if (sig == NULL) { - return NULL; - } - - sig->type = privkey->type; - sig->type_c = ssh_key_signature_to_char(privkey->type, hash_type); - sig->hash_type = hash_type; - switch(privkey->type) { - case SSH_KEYTYPE_ED25519: - rc = pki_ed25519_sign(privkey, sig, hash, hlen); - if (rc != SSH_OK) { - ssh_signature_free(sig); - return NULL; - } - break; - default: - ssh_signature_free(sig); - return NULL; - } - - return sig; -} -#endif /* HAVE_OPENSSL_ED25519 */ +#ifdef WITH_PKCS11_URI +#ifdef WITH_PKCS11_PROVIDER +static bool pkcs11_provider_failed = false; +#endif /** * @internal * - * @brief Populate the public/private ssh_key from the engine with + * @brief Populate the public/private ssh_key from the engine/provider with * PKCS#11 URIs as the look up. * * @param[in] uri_name The PKCS#11 URI * @param[in] nkey The ssh-key context for - * the key loaded from the engine. + * the key loaded from the engine/provider. * @param[in] key_type The type of the key used. Public/Private. * * @return SSH_OK if ssh-key is valid; SSH_ERROR otherwise. @@ -2565,64 +2730,112 @@ int pki_uri_import(const char *uri_name, ssh_key *nkey, enum ssh_key_e key_type) { - ENGINE *engine = NULL; EVP_PKEY *pkey = NULL; - RSA *rsa = NULL; ssh_key key = NULL; enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; -#ifdef HAVE_OPENSSL_ECC +#if OPENSSL_VERSION_NUMBER < 0x30000000L && HAVE_OPENSSL_ECC EC_KEY *ecdsa = NULL; -#else - void *ecdsa = NULL; #endif - int ok; - - ENGINE_load_builtin_engines(); +#ifndef WITH_PKCS11_PROVIDER + ENGINE *engine = NULL; - engine = ENGINE_by_id("pkcs11"); + /* Do the init only once */ + engine = pki_get_engine(); if (engine == NULL) { - SSH_LOG(SSH_LOG_WARN, - "Could not load the engine: %s", - ERR_error_string(ERR_get_error(),NULL)); - return SSH_ERROR; - } - SSH_LOG(SSH_LOG_INFO, "Engine loaded successfully"); - - ok = ENGINE_init(engine); - if (!ok) { - SSH_LOG(SSH_LOG_WARN, - "Could not initialize the engine: %s", - ERR_error_string(ERR_get_error(),NULL)); - ENGINE_free(engine); - return SSH_ERROR; + SSH_LOG(SSH_LOG_TRACE, "Failed to initialize engine"); + goto fail; } - SSH_LOG(SSH_LOG_INFO, "Engine init success"); - switch (key_type) { case SSH_KEY_PRIVATE: pkey = ENGINE_load_private_key(engine, uri_name, NULL, NULL); if (pkey == NULL) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Could not load key: %s", - ERR_error_string(ERR_get_error(),NULL)); + ERR_error_string(ERR_get_error(), NULL)); goto fail; } break; case SSH_KEY_PUBLIC: pkey = ENGINE_load_public_key(engine, uri_name, NULL, NULL); if (pkey == NULL) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Could not load key: %s", - ERR_error_string(ERR_get_error(),NULL)); + ERR_error_string(ERR_get_error(), NULL)); goto fail; } break; default: - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Invalid key type: %d", key_type); goto fail; } +#else /* WITH_PKCS11_PROVIDER */ + OSSL_STORE_CTX *store = NULL; + OSSL_STORE_INFO *info = NULL; + int rv, expect_type = OSSL_STORE_INFO_PKEY; + + /* The provider can be either configured in openssl.cnf or dynamically + * loaded, assuming it does not need any special configuration */ + if (OSSL_PROVIDER_available(NULL, "pkcs11") == 0 && + !pkcs11_provider_failed) { + OSSL_PROVIDER *pkcs11_provider = NULL; + + pkcs11_provider = OSSL_PROVIDER_try_load(NULL, "pkcs11", 1); + if (pkcs11_provider == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize provider: %s", + ERR_error_string(ERR_get_error(), NULL)); + /* Do not attempt to load it again */ + pkcs11_provider_failed = true; + goto fail; + } + } + + store = OSSL_STORE_open(uri_name, NULL, NULL, NULL, NULL); + if (store == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to open OpenSSL store: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + if (key_type == SSH_KEY_PUBLIC) { + expect_type = OSSL_STORE_INFO_PUBKEY; + } + rv = OSSL_STORE_expect(store, expect_type); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to set the store preference. Ignoring the error: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + for (info = OSSL_STORE_load(store); + info != NULL; + info = OSSL_STORE_load(store)) { + int ossl_type = OSSL_STORE_INFO_get_type(info); + + if (ossl_type == OSSL_STORE_INFO_PUBKEY && key_type == SSH_KEY_PUBLIC) { + pkey = OSSL_STORE_INFO_get1_PUBKEY(info); + break; + } else if (ossl_type == OSSL_STORE_INFO_PKEY && + key_type == SSH_KEY_PRIVATE) { + pkey = OSSL_STORE_INFO_get1_PKEY(info); + break; + } else { + SSH_LOG(SSH_LOG_TRACE, + "Ignoring object not matching our type: %d", + ossl_type); + } + } + OSSL_STORE_close(store); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "No key found in the pkcs11 store: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + +#endif /* WITH_PKCS11_PROVIDER */ key = ssh_key_new(); if (key == NULL) { @@ -2631,20 +2844,14 @@ int pki_uri_import(const char *uri_name, switch (EVP_PKEY_base_id(pkey)) { case EVP_PKEY_RSA: - rsa = EVP_PKEY_get1_RSA(pkey); - if (rsa == NULL) { - SSH_LOG(SSH_LOG_WARN, - "Parsing pub key: %s", - ERR_error_string(ERR_get_error(),NULL)); - goto fail; - } type = SSH_KEYTYPE_RSA; break; case EVP_PKEY_EC: #ifdef HAVE_OPENSSL_ECC - ecdsa = EVP_PKEY_get1_EC_KEY(pkey); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EVP_PKEY_get0_EC_KEY(pkey); if (ecdsa == NULL) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Parsing pub key: %s", ERR_error_string(ERR_get_error(), NULL)); goto fail; @@ -2653,15 +2860,18 @@ int pki_uri_import(const char *uri_name, /* pki_privatekey_type_from_string always returns P256 for ECDSA * keys, so we need to figure out the correct type here */ type = pki_key_ecdsa_to_key_type(ecdsa); +#else + type = pki_key_ecdsa_to_key_type(pkey); +#endif /* OPENSSL_VERSION_NUMBER */ if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "Invalid pub key."); + SSH_LOG(SSH_LOG_TRACE, "Invalid pub key."); goto fail; } break; #endif default: - SSH_LOG(SSH_LOG_WARN, "Unknown or invalid public key type %d", + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid public key type %d", EVP_PKEY_base_id(pkey)); goto fail; } @@ -2669,36 +2879,30 @@ int pki_uri_import(const char *uri_name, key->key = pkey; key->type = type; key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PUBLIC | SSH_KEY_FLAG_PKCS11_URI; if (key_type == SSH_KEY_PRIVATE) { - key->flags = SSH_KEY_FLAG_PUBLIC | SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PKCS11_URI; - } else { - key->flags = SSH_KEY_FLAG_PUBLIC | SSH_KEY_FLAG_PKCS11_URI; + key->flags |= SSH_KEY_FLAG_PRIVATE; } - key->rsa = rsa; - key->ecdsa = ecdsa; #ifdef HAVE_OPENSSL_ECC if (is_ecdsa_key_type(key->type)) { - key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + key->ecdsa_nid = pki_key_ecdsa_to_nid(ecdsa); +#else + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->key); +#endif /* OPENSSL_VERSION_NUMBER */ } #endif *nkey = key; - ENGINE_finish(engine); - ENGINE_free(engine); return SSH_OK; fail: - ENGINE_finish(engine); - ENGINE_free(engine); EVP_PKEY_free(pkey); ssh_key_free(key); - RSA_free(rsa); -#ifdef HAVE_OPENSSL_ECC - EC_KEY_free(ecdsa); -#endif return SSH_ERROR; } +#endif /* WITH_PKCS11_URI */ #endif /* _PKI_CRYPTO_H */ diff --git a/src/pki_ed25519.c b/src/pki_ed25519.c index fdf94b4e..6a5a4a8a 100644 --- a/src/pki_ed25519.c +++ b/src/pki_ed25519.c @@ -1,5 +1,5 @@ /* - * pki_ed25519 .c - PKI infrastructure using ed25519 + * pki_ed25519.c - PKI infrastructure using ed25519 * * This file is part of the SSH Library * diff --git a/src/pki_ed25519_common.c b/src/pki_ed25519_common.c index 7aa05269..03859f7c 100644 --- a/src/pki_ed25519_common.c +++ b/src/pki_ed25519_common.c @@ -34,11 +34,11 @@ int pki_privkey_build_ed25519(ssh_key key, if (ssh_string_len(pubkey) != ED25519_KEY_LEN || ssh_string_len(privkey) != (2 * ED25519_KEY_LEN)) { - SSH_LOG(SSH_LOG_WARN, "Invalid ed25519 key len"); + SSH_LOG(SSH_LOG_TRACE, "Invalid ed25519 key len"); return SSH_ERROR; } -#ifdef HAVE_OPENSSL_ED25519 +#ifdef HAVE_LIBCRYPTO /* In OpenSSL implementation, the private key is the original private seed, * without the public key. */ key->ed25519_privkey = malloc(ED25519_KEY_LEN); @@ -56,7 +56,7 @@ int pki_privkey_build_ed25519(ssh_key key, goto error; } -#ifdef HAVE_OPENSSL_ED25519 +#ifdef HAVE_LIBCRYPTO memcpy(key->ed25519_privkey, ssh_string_data(privkey), ED25519_KEY_LEN); #else @@ -99,7 +99,7 @@ int pki_ed25519_key_cmp(const ssh_key k1, if (k1->ed25519_privkey == NULL || k2->ed25519_privkey == NULL) { return 1; } -#ifdef HAVE_OPENSSL_ED25519 +#ifdef HAVE_LIBCRYPTO /* In OpenSSL implementation, the private key is the original private * seed, without the public key. */ cmp = memcmp(k1->ed25519_privkey, k2->ed25519_privkey, ED25519_KEY_LEN); @@ -121,6 +121,10 @@ int pki_ed25519_key_cmp(const ssh_key k1, if (cmp != 0) { return 1; } + break; + case SSH_KEY_CMP_CERTIFICATE: + /* handled globally */ + return 1; } return 0; @@ -137,39 +141,39 @@ int pki_ed25519_key_cmp(const ssh_key k1, * * @return SSH_ERROR on error, SSH_OK on success */ -int pki_ed25519_key_dup(ssh_key new, const ssh_key key) +int pki_ed25519_key_dup(ssh_key new_key, const ssh_key key) { if (key->ed25519_privkey == NULL && key->ed25519_pubkey == NULL) { return SSH_ERROR; } if (key->ed25519_privkey != NULL) { -#ifdef HAVE_OPENSSL_ED25519 +#ifdef HAVE_LIBCRYPTO /* In OpenSSL implementation, the private key is the original private * seed, without the public key. */ - new->ed25519_privkey = malloc(ED25519_KEY_LEN); + new_key->ed25519_privkey = malloc(ED25519_KEY_LEN); #else /* In the internal implementation, the private key is the concatenation * of the private seed with the public key. */ - new->ed25519_privkey = malloc(2 * ED25519_KEY_LEN); + new_key->ed25519_privkey = malloc(2 * ED25519_KEY_LEN); #endif - if (new->ed25519_privkey == NULL) { + if (new_key->ed25519_privkey == NULL) { return SSH_ERROR; } -#ifdef HAVE_OPENSSL_ED25519 - memcpy(new->ed25519_privkey, key->ed25519_privkey, ED25519_KEY_LEN); +#ifdef HAVE_LIBCRYPTO + memcpy(new_key->ed25519_privkey, key->ed25519_privkey, ED25519_KEY_LEN); #else - memcpy(new->ed25519_privkey, key->ed25519_privkey, 2 * ED25519_KEY_LEN); + memcpy(new_key->ed25519_privkey, key->ed25519_privkey, 2 * ED25519_KEY_LEN); #endif } if (key->ed25519_pubkey != NULL) { - new->ed25519_pubkey = malloc(ED25519_KEY_LEN); - if (new->ed25519_pubkey == NULL) { - SAFE_FREE(new->ed25519_privkey); + new_key->ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (new_key->ed25519_pubkey == NULL) { + SAFE_FREE(new_key->ed25519_privkey); return SSH_ERROR; } - memcpy(new->ed25519_pubkey, key->ed25519_pubkey, ED25519_KEY_LEN); + memcpy(new_key->ed25519_pubkey, key->ed25519_pubkey, ED25519_KEY_LEN); } return SSH_OK; @@ -202,6 +206,34 @@ int pki_ed25519_public_key_to_blob(ssh_buffer buffer, ssh_key key) return rc; } +/** @internal + * @brief exports a ed25519 private key to a string blob. + * @param[in] privkey private key to convert + * @param[out] buffer buffer to write the blob in. + * @returns SSH_OK on success + */ +int pki_ed25519_private_key_to_blob(ssh_buffer buffer, const ssh_key privkey) +{ + int rc; + + if (privkey->type != SSH_KEYTYPE_ED25519) { + SSH_LOG(SSH_LOG_TRACE, "Type %s not supported", privkey->type_c); + return SSH_ERROR; + } + if (privkey->ed25519_privkey == NULL || + privkey->ed25519_pubkey == NULL) { + return SSH_ERROR; + } + rc = ssh_buffer_pack(buffer, + "dPdPP", + (uint32_t)ED25519_KEY_LEN, + (size_t)ED25519_KEY_LEN, privkey->ed25519_pubkey, + (uint32_t)(2 * ED25519_KEY_LEN), + (size_t)ED25519_KEY_LEN, privkey->ed25519_privkey, + (size_t)ED25519_KEY_LEN, privkey->ed25519_pubkey); + return rc; +} + /** * @internal * @@ -216,7 +248,7 @@ ssh_string pki_ed25519_signature_to_blob(ssh_signature sig) ssh_string sig_blob; int rc; -#ifdef HAVE_OPENSSL_ED25519 +#ifdef HAVE_LIBCRYPTO /* When using the OpenSSL implementation, the signature is stored in raw_sig * which is shared by all algorithms.*/ if (sig->raw_sig == NULL) { @@ -235,7 +267,7 @@ ssh_string pki_ed25519_signature_to_blob(ssh_signature sig) return NULL; } -#ifdef HAVE_OPENSSL_ED25519 +#ifdef HAVE_LIBCRYPTO rc = ssh_string_fill(sig_blob, ssh_string_data(sig->raw_sig), ssh_string_len(sig->raw_sig)); #else @@ -266,11 +298,11 @@ int pki_signature_from_ed25519_blob(ssh_signature sig, ssh_string sig_blob) len = ssh_string_len(sig_blob); if (len != ED25519_SIG_LEN){ - SSH_LOG(SSH_LOG_WARN, "Invalid ssh-ed25519 signature len: %zu", len); + SSH_LOG(SSH_LOG_TRACE, "Invalid ssh-ed25519 signature len: %zu", len); return SSH_ERROR; } -#ifdef HAVE_OPENSSL_ED25519 +#ifdef HAVE_LIBCRYPTO sig->raw_sig = ssh_string_copy(sig_blob); #else sig->ed25519_sig = malloc(ED25519_SIG_LEN); @@ -282,4 +314,3 @@ int pki_signature_from_ed25519_blob(ssh_signature sig, ssh_string sig_blob) return SSH_OK; } - diff --git a/src/pki_gcrypt.c b/src/pki_gcrypt.c index 7f8b140e..65bb77e6 100644 --- a/src/pki_gcrypt.c +++ b/src/pki_gcrypt.c @@ -45,8 +45,6 @@ #define MAXLINESIZE 80 #define RSA_HEADER_BEGIN "-----BEGIN RSA PRIVATE KEY-----" #define RSA_HEADER_END "-----END RSA PRIVATE KEY-----" -#define DSA_HEADER_BEGIN "-----BEGIN DSA PRIVATE KEY-----" -#define DSA_HEADER_END "-----END DSA PRIVATE KEY-----" #define ECDSA_HEADER_BEGIN "-----BEGIN EC PRIVATE KEY-----" #define ECDSA_HEADER_END "-----END EC PRIVATE KEY-----" @@ -283,6 +281,20 @@ static int passphrase_to_key(char *data, unsigned int datalen, return 0; } +void pki_key_clean(ssh_key key) +{ + if (key == NULL) + return; + + if (key->rsa) + gcry_sexp_release(key->rsa); + if (key->ecdsa) + gcry_sexp_release(key->ecdsa); + + key->rsa = NULL; + key->ecdsa = NULL; +} + static int privatekey_decrypt(int algo, int mode, unsigned int key_len, unsigned char *iv, unsigned int iv_len, ssh_buffer data, ssh_auth_callback cb, @@ -419,10 +431,6 @@ static ssh_buffer privatekey_string_to_buffer(const char *pkey, int type, } switch(type) { - case SSH_KEYTYPE_DSS: - header_begin = DSA_HEADER_BEGIN; - header_end = DSA_HEADER_END; - break; case SSH_KEYTYPE_RSA: header_begin = RSA_HEADER_BEGIN; header_end = RSA_HEADER_END; @@ -626,79 +634,6 @@ error: return rc; } -static int b64decode_dsa_privatekey(const char *pkey, gcry_sexp_t *r, ssh_auth_callback cb, - void *userdata, const char *desc) { - const unsigned char *data; - ssh_buffer buffer = NULL; - ssh_string p = NULL; - ssh_string q = NULL; - ssh_string g = NULL; - ssh_string y = NULL; - ssh_string x = NULL; - ssh_string v = NULL; - int rc = 1; - - buffer = privatekey_string_to_buffer(pkey, SSH_KEYTYPE_DSS, cb, userdata, desc); - if (buffer == NULL) { - return 0; - } - - if (!asn1_check_sequence(buffer)) { - SSH_BUFFER_FREE(buffer); - return 0; - } - - v = asn1_get_int(buffer); - if (v == NULL) { - SSH_BUFFER_FREE(buffer); - return 0; - } - - data = ssh_string_data(v); - if (ssh_string_len(v) != 1 || data[0] != 0) { - SSH_STRING_FREE(v); - SSH_BUFFER_FREE(buffer); - return 0; - } - - p = asn1_get_int(buffer); - q = asn1_get_int(buffer); - g = asn1_get_int(buffer); - y = asn1_get_int(buffer); - x = asn1_get_int(buffer); - SSH_BUFFER_FREE(buffer); - - if (p == NULL || q == NULL || g == NULL || y == NULL || x == NULL) { - rc = 0; - goto error; - } - - if (gcry_sexp_build(r, NULL, - "(private-key(dsa(p %b)(q %b)(g %b)(y %b)(x %b)))", - ssh_string_len(p), ssh_string_data(p), - ssh_string_len(q), ssh_string_data(q), - ssh_string_len(g), ssh_string_data(g), - ssh_string_len(y), ssh_string_data(y), - ssh_string_len(x), ssh_string_data(x))) { - rc = 0; - } - -error: - ssh_string_burn(p); - SSH_STRING_FREE(p); - ssh_string_burn(q); - SSH_STRING_FREE(q); - ssh_string_burn(g); - SSH_STRING_FREE(g); - ssh_string_burn(y); - SSH_STRING_FREE(y); - ssh_string_burn(x); - SSH_STRING_FREE(x); - SSH_STRING_FREE(v); - - return rc; -} - #ifdef HAVE_GCRYPT_ECC static int pki_key_ecdsa_to_nid(gcry_sexp_t k) { @@ -938,7 +873,7 @@ ssh_string pki_private_key_to_pem(const ssh_key key, (void) auth_fn; (void) auth_data; - SSH_LOG(SSH_LOG_WARN, "PEM export not supported by gcrypt backend!"); + SSH_LOG(SSH_LOG_TRACE, "PEM export not supported by gcrypt backend!"); return NULL; } @@ -948,7 +883,6 @@ ssh_key pki_private_key_from_base64(const char *b64_key, ssh_auth_callback auth_fn, void *auth_data) { - gcry_sexp_t dsa = NULL; gcry_sexp_t rsa = NULL; gcry_sexp_t ecdsa = NULL; ssh_key key = NULL; @@ -957,30 +891,11 @@ ssh_key pki_private_key_from_base64(const char *b64_key, type = pki_privatekey_type_from_string(b64_key); if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "Unknown or invalid private key."); + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key."); return NULL; } switch (type) { - case SSH_KEYTYPE_DSS: - if (passphrase == NULL) { - if (auth_fn) { - valid = b64decode_dsa_privatekey(b64_key, &dsa, auth_fn, - auth_data, "Passphrase for private key:"); - } else { - valid = b64decode_dsa_privatekey(b64_key, &dsa, NULL, NULL, - NULL); - } - } else { - valid = b64decode_dsa_privatekey(b64_key, &dsa, NULL, (void *) - passphrase, NULL); - } - - if (!valid) { - SSH_LOG(SSH_LOG_WARN, "Parsing private key"); - goto fail; - } - break; case SSH_KEYTYPE_RSA: if (passphrase == NULL) { if (auth_fn) { @@ -996,7 +911,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, } if (!valid) { - SSH_LOG(SSH_LOG_WARN, "Parsing private key"); + SSH_LOG(SSH_LOG_TRACE, "Error parsing private key"); goto fail; } break; @@ -1027,7 +942,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, } if (!valid) { - SSH_LOG(SSH_LOG_WARN, "Parsing private key"); + SSH_LOG(SSH_LOG_TRACE, "Error parsing private key"); goto fail; } @@ -1035,7 +950,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, * keys, so we need to figure out the correct type here */ type = pki_key_ecdsa_to_key_type(ecdsa); if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "Invalid private key."); + SSH_LOG(SSH_LOG_TRACE, "Invalid private key."); goto fail; } break; @@ -1045,7 +960,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_WARN, "Unknown or invalid private key type %d", type); + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key type %d", type); return NULL; } @@ -1057,7 +972,6 @@ ssh_key pki_private_key_from_base64(const char *b64_key, key->type = type; key->type_c = ssh_key_type_to_char(type); key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; - key->dsa = dsa; key->rsa = rsa; key->ecdsa = ecdsa; #ifdef HAVE_GCRYPT_ECC @@ -1069,52 +983,12 @@ ssh_key pki_private_key_from_base64(const char *b64_key, return key; fail: ssh_key_free(key); - gcry_sexp_release(dsa); gcry_sexp_release(rsa); gcry_sexp_release(ecdsa); return NULL; } -int pki_privkey_build_dss(ssh_key key, - ssh_string p, - ssh_string q, - ssh_string g, - ssh_string pubkey, - ssh_string privkey) -{ - gcry_sexp_build(&key->dsa, NULL, - "(private-key(dsa(p %b)(q %b)(g %b)(y %b)(x %b)))", - ssh_string_len(p), ssh_string_data(p), - ssh_string_len(q), ssh_string_data(q), - ssh_string_len(g), ssh_string_data(g), - ssh_string_len(pubkey), ssh_string_data(pubkey), - ssh_string_len(privkey), ssh_string_data(privkey)); - if (key->dsa == NULL) { - return SSH_ERROR; - } - - return SSH_OK; -} - -int pki_pubkey_build_dss(ssh_key key, - ssh_string p, - ssh_string q, - ssh_string g, - ssh_string pubkey) { - gcry_sexp_build(&key->dsa, NULL, - "(public-key(dsa(p %b)(q %b)(g %b)(y %b)))", - ssh_string_len(p), ssh_string_data(p), - ssh_string_len(q), ssh_string_data(q), - ssh_string_len(g), ssh_string_data(g), - ssh_string_len(pubkey), ssh_string_data(pubkey)); - if (key->dsa == NULL) { - return SSH_ERROR; - } - - return SSH_OK; -} - int pki_privkey_build_rsa(ssh_key key, ssh_string n, ssh_string e, @@ -1226,32 +1100,6 @@ ssh_key pki_key_dup(const ssh_key key, int demote) } switch(key->type) { - case SSH_KEYTYPE_DSS: - err = gcry_sexp_extract_param(key->dsa, - NULL, - "pqgyx?", - &p, - &q, - &g, - &y, - &x, - NULL); - if (err != 0) { - break; - } - - if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { - err = gcry_sexp_build(&new->dsa, - NULL, - "(private-key(dsa(p %m)(q %m)(g %m)(y %m)(x %m)))", - p, q, g, y, x); - } else { - err = gcry_sexp_build(&new->dsa, - NULL, - "(public-key(dsa(p %m)(q %m)(g %m)(y %m)))", - p, q, g, y); - } - break; case SSH_KEYTYPE_RSA: err = gcry_sexp_extract_param(key->rsa, NULL, @@ -1353,9 +1201,9 @@ ssh_key pki_key_dup(const ssh_key key, int demote) } static int pki_key_generate(ssh_key key, int parameter, const char *type_s, int type){ - gcry_sexp_t parms; + gcry_sexp_t params; int rc; - rc = gcry_sexp_build(&parms, + rc = gcry_sexp_build(¶ms, NULL, "(genkey(%s(nbits %d)(transient-key)))", type_s, @@ -1364,20 +1212,17 @@ static int pki_key_generate(ssh_key key, int parameter, const char *type_s, int return SSH_ERROR; switch (type) { case SSH_KEYTYPE_RSA: - rc = gcry_pk_genkey(&key->rsa, parms); - break; - case SSH_KEYTYPE_DSS: - rc = gcry_pk_genkey(&key->dsa, parms); + rc = gcry_pk_genkey(&key->rsa, params); break; case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: - rc = gcry_pk_genkey(&key->ecdsa, parms); + rc = gcry_pk_genkey(&key->ecdsa, params); break; default: assert (! "reached"); } - gcry_sexp_release(parms); + gcry_sexp_release(params); if (rc != 0) return SSH_ERROR; return SSH_OK; @@ -1386,9 +1231,6 @@ static int pki_key_generate(ssh_key key, int parameter, const char *type_s, int int pki_key_generate_rsa(ssh_key key, int parameter){ return pki_key_generate(key, parameter, "rsa", SSH_KEYTYPE_RSA); } -int pki_key_generate_dss(ssh_key key, int parameter){ - return pki_key_generate(key, parameter, "dsa", SSH_KEYTYPE_DSS); -} #ifdef HAVE_GCRYPT_ECC int pki_key_generate_ecdsa(ssh_key key, int parameter) { @@ -1455,30 +1297,8 @@ int pki_key_compare(const ssh_key k1, enum ssh_keycmp_e what) { switch (k1->type) { - case SSH_KEYTYPE_DSS: - if (_bignum_cmp(k1->dsa, k2->dsa, "p") != 0) { - return 1; - } - - if (_bignum_cmp(k1->dsa, k2->dsa, "q") != 0) { - return 1; - } - - if (_bignum_cmp(k1->dsa, k2->dsa, "g") != 0) { - return 1; - } - - if (_bignum_cmp(k1->dsa, k2->dsa, "y") != 0) { - return 1; - } - - if (what == SSH_KEY_CMP_PRIVATE) { - if (_bignum_cmp(k1->dsa, k2->dsa, "x") != 0) { - return 1; - } - } - break; case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: if (_bignum_cmp(k1->rsa, k2->rsa, "e") != 0) { return 1; } @@ -1506,13 +1326,19 @@ int pki_key_compare(const ssh_key k1, } break; case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: case SSH_KEYTYPE_SK_ED25519: - /* ed25519 keys handled globaly */ - return 0; + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys handled globally */ + return 0; case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: #ifdef HAVE_GCRYPT_ECC if (k1->ecdsa_nid != k2->ecdsa_nid) { return 1; @@ -1529,15 +1355,9 @@ int pki_key_compare(const ssh_key k1, } break; #endif - case SSH_KEYTYPE_DSS_CERT01: - case SSH_KEYTYPE_RSA_CERT01: - case SSH_KEYTYPE_ECDSA: - case SSH_KEYTYPE_ECDSA_P256_CERT01: - case SSH_KEYTYPE_ECDSA_P384_CERT01: - case SSH_KEYTYPE_ECDSA_P521_CERT01: - case SSH_KEYTYPE_SK_ECDSA_CERT01: - case SSH_KEYTYPE_ED25519_CERT01: - case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_ECDSA: /* deprecated */ case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_UNKNOWN: return 1; @@ -1546,16 +1366,18 @@ int pki_key_compare(const ssh_key k1, return 0; } -ssh_string pki_publickey_to_blob(const ssh_key key) +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) { ssh_buffer buffer; ssh_string type_s; ssh_string str = NULL; ssh_string e = NULL; ssh_string n = NULL; + ssh_string d = NULL; ssh_string p = NULL; ssh_string g = NULL; ssh_string q = NULL; + ssh_string u = NULL; int rc; buffer = ssh_buffer_new(); @@ -1586,66 +1408,6 @@ ssh_string pki_publickey_to_blob(const ssh_key key) } switch (key->type) { - case SSH_KEYTYPE_DSS: - p = ssh_sexp_extract_mpi(key->dsa, - "p", - GCRYMPI_FMT_USG, - GCRYMPI_FMT_STD); - if (p == NULL) { - goto fail; - } - - q = ssh_sexp_extract_mpi(key->dsa, - "q", - GCRYMPI_FMT_USG, - GCRYMPI_FMT_STD); - if (q == NULL) { - goto fail; - } - - g = ssh_sexp_extract_mpi(key->dsa, - "g", - GCRYMPI_FMT_USG, - GCRYMPI_FMT_STD); - if (g == NULL) { - goto fail; - } - - n = ssh_sexp_extract_mpi(key->dsa, - "y", - GCRYMPI_FMT_USG, - GCRYMPI_FMT_STD); - if (n == NULL) { - goto fail; - } - - rc = ssh_buffer_add_ssh_string(buffer, p); - if (rc < 0) { - goto fail; - } - rc = ssh_buffer_add_ssh_string(buffer, q); - if (rc < 0) { - goto fail; - } - rc = ssh_buffer_add_ssh_string(buffer, g); - if (rc < 0) { - goto fail; - } - rc = ssh_buffer_add_ssh_string(buffer, n); - if (rc < 0) { - goto fail; - } - - ssh_string_burn(p); - SSH_STRING_FREE(p); - ssh_string_burn(g); - SSH_STRING_FREE(g); - ssh_string_burn(q); - SSH_STRING_FREE(q); - ssh_string_burn(n); - SSH_STRING_FREE(n); - - break; case SSH_KEYTYPE_RSA: e = ssh_sexp_extract_mpi(key->rsa, "e", @@ -1663,30 +1425,108 @@ ssh_string pki_publickey_to_blob(const ssh_key key) goto fail; } - rc = ssh_buffer_add_ssh_string(buffer, e); - if (rc < 0) { - goto fail; - } - rc = ssh_buffer_add_ssh_string(buffer, n); - if (rc < 0) { - goto fail; - } + if (type == SSH_KEY_PUBLIC) { + /* The N and E parts are swapped in the public key export ! */ + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + } else if (type == SSH_KEY_PRIVATE) { + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + d = ssh_sexp_extract_mpi(key->rsa, + "d", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (d == NULL) { + goto fail; + } + + p = ssh_sexp_extract_mpi(key->rsa, + "p", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (p == NULL) { + goto fail; + } + q = ssh_sexp_extract_mpi(key->rsa, + "q", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (q == NULL) { + goto fail; + } + + u = ssh_sexp_extract_mpi(key->rsa, + "u", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (u == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, u); + if (rc < 0) { + goto fail; + } + /* Swap the P and Q as the iqmp in gcrypt is ipmq ... */ + rc = ssh_buffer_add_ssh_string(buffer, q); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { + goto fail; + } + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); + ssh_string_burn(u); + SSH_STRING_FREE(u); + } ssh_string_burn(e); SSH_STRING_FREE(e); ssh_string_burn(n); SSH_STRING_FREE(n); - break; case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: - rc = pki_ed25519_public_key_to_blob(buffer, key); - if (rc != SSH_OK){ - goto fail; - } - if (key->type == SSH_KEYTYPE_SK_ED25519 && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; + if (type == SSH_KEY_PUBLIC) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + /* public key can contain certificate sk information */ + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto fail; + } + } + } else { + rc = pki_ed25519_private_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } } break; case SSH_KEYTYPE_ECDSA_P256: @@ -1697,22 +1537,19 @@ ssh_string pki_publickey_to_blob(const ssh_key key) type_s = ssh_string_from_char( pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); if (type_s == NULL) { - SSH_BUFFER_FREE(buffer); - return NULL; + goto fail; } rc = ssh_buffer_add_ssh_string(buffer, type_s); SSH_STRING_FREE(type_s); if (rc < 0) { - SSH_BUFFER_FREE(buffer); - return NULL; + goto fail; } e = ssh_sexp_extract_mpi(key->ecdsa, "q", GCRYMPI_FMT_STD, GCRYMPI_FMT_STD); if (e == NULL) { - SSH_BUFFER_FREE(buffer); - return NULL; + goto fail; } rc = ssh_buffer_add_ssh_string(buffer, e); @@ -1724,9 +1561,27 @@ ssh_string pki_publickey_to_blob(const ssh_key key) SSH_STRING_FREE(e); e = NULL; - if (key->type == SSH_KEYTYPE_SK_ECDSA && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; + if (type == SSH_KEY_PRIVATE) { + d = ssh_sexp_extract_mpi(key->ecdsa, "d", GCRYMPI_FMT_STD, + GCRYMPI_FMT_STD); + if (d == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(d); + SSH_STRING_FREE(d); + d = NULL; + } else if (key->type == SSH_KEYTYPE_SK_ECDSA) { + /* public key can contain certificate sk information */ + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto fail; + } } break; @@ -1770,13 +1625,7 @@ fail: ssh_string pki_signature_to_blob(const ssh_signature sig) { - char buffer[40] = { 0 }; - - const char *r = NULL; - size_t r_len, r_offset_in, r_offset_out; - - const char *s = NULL; - size_t s_len, s_offset_in, s_offset_out; + const char *s = NULL; /* used in RSA */ gcry_sexp_t sexp; size_t size = 0; @@ -1784,57 +1633,6 @@ ssh_string pki_signature_to_blob(const ssh_signature sig) int rc; switch(sig->type) { - case SSH_KEYTYPE_DSS: - sexp = gcry_sexp_find_token(sig->dsa_sig, "r", 0); - if (sexp == NULL) { - return NULL; - } - r = gcry_sexp_nth_data(sexp, 1, &size); - /* libgcrypt put 0 when first bit is set */ - if (*r == 0) { - size--; - r++; - } - - r_len = size; - r_offset_in = (r_len > 20) ? (r_len - 20) : 0; - r_offset_out = (r_len < 20) ? (20 - r_len) : 0; - memcpy(buffer + r_offset_out, - r + r_offset_in, - r_len - r_offset_in); - - gcry_sexp_release(sexp); - - sexp = gcry_sexp_find_token(sig->dsa_sig, "s", 0); - if (sexp == NULL) { - return NULL; - } - s = gcry_sexp_nth_data(sexp,1,&size); - if (*s == 0) { - size--; - s++; - } - - s_len = size; - s_offset_in = (s_len > 20) ? (s_len - 20) : 0; - s_offset_out = (s_len < 20) ? (20 - s_len) : 0; - memcpy(buffer + 20 + s_offset_out, - s + s_offset_in, - s_len - s_offset_in); - - gcry_sexp_release(sexp); - - sig_blob = ssh_string_new(40); - if (sig_blob == NULL) { - return NULL; - } - - rc = ssh_string_fill(sig_blob, buffer, 40); - if (rc < 0) { - SSH_STRING_FREE(sig_blob); - return NULL; - } - break; case SSH_KEYTYPE_RSA: sexp = gcry_sexp_find_token(sig->rsa_sig, "s", 0); if (sexp == NULL) { @@ -1921,7 +1719,7 @@ ssh_string pki_signature_to_blob(const ssh_signature sig) case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_WARN, "Unknown signature key type: %d", sig->type); + SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %d", sig->type); return NULL; break; } @@ -1941,7 +1739,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, int rc; if (ssh_key_type_plain(pubkey->type) != type) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Incompatible public key provided (%d) expecting (%d)", type, pubkey->type); @@ -1960,40 +1758,11 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, len = ssh_string_len(sig_blob); switch(type) { - case SSH_KEYTYPE_DSS: - /* 40 is the dual signature blob len. */ - if (len != 40) { - SSH_LOG(SSH_LOG_WARN, - "Signature has wrong size: %lu", - (unsigned long)len); - ssh_signature_free(sig); - return NULL; - } - -#ifdef DEBUG_CRYPTO - SSH_LOG(SSH_LOG_DEBUG, - "DSA signature len: %lu", - (unsigned long)len); - ssh_log_hexdump("DSA signature", ssh_string_data(sig_blob), len); -#endif - - err = gcry_sexp_build(&sig->dsa_sig, - NULL, - "(sig-val(dsa(r %b)(s %b)))", - 20, - ssh_string_data(sig_blob), - 20, - (unsigned char *)ssh_string_data(sig_blob) + 20); - if (err) { - ssh_signature_free(sig); - return NULL; - } - break; case SSH_KEYTYPE_RSA: rsalen = (gcry_pk_get_nbits(pubkey->rsa) + 7) / 8; if (len > rsalen) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Signature is too big: %lu > %lu", (unsigned long)len, (unsigned long)rsalen); @@ -2074,7 +1843,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, } if (rlen != 0) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Signature has remaining bytes in inner " "sigblob: %lu", (unsigned long)rlen); @@ -2112,7 +1881,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_WARN, "Unknown signature type"); + SSH_LOG(SSH_LOG_TRACE, "Unknown signature type"); return NULL; } @@ -2124,7 +1893,6 @@ ssh_signature pki_do_sign_hash(const ssh_key privkey, size_t hlen, enum ssh_digest_e hash_type) { - unsigned char ghash[hlen + 1]; const char *hash_c = NULL; ssh_signature sig; gcry_sexp_t sexp; @@ -2138,28 +1906,6 @@ ssh_signature pki_do_sign_hash(const ssh_key privkey, sig->type_c = ssh_key_signature_to_char(privkey->type, hash_type); sig->hash_type = hash_type; switch (privkey->type) { - case SSH_KEYTYPE_DSS: - /* That is to mark the number as positive */ - if(hash[0] >= 0x80) { - memcpy(ghash + 1, hash, hlen); - ghash[0] = 0; - hash = ghash; - hlen += 1; - } - - err = gcry_sexp_build(&sexp, NULL, "%b", hlen, hash); - if (err) { - ssh_signature_free(sig); - return NULL; - } - - err = gcry_pk_sign(&sig->dsa_sig, sexp, privkey->dsa); - gcry_sexp_release(sexp); - if (err) { - ssh_signature_free(sig); - return NULL; - } - break; case SSH_KEYTYPE_RSA: switch (hash_type) { case SSH_DIGEST_SHA1: @@ -2173,7 +1919,7 @@ ssh_signature pki_do_sign_hash(const ssh_key privkey, break; case SSH_DIGEST_AUTO: default: - SSH_LOG(SSH_LOG_WARN, "Incompatible key algorithm"); + SSH_LOG(SSH_LOG_TRACE, "Incompatible key algorithm"); return NULL; } err = gcry_sexp_build(&sexp, @@ -2392,32 +2138,6 @@ int pki_verify_data_signature(ssh_signature signature, } switch(pubkey->type) { - case SSH_KEYTYPE_DSS: - case SSH_KEYTYPE_DSS_CERT01: - /* That is to mark the number as positive */ - if(hash[0] >= 0x80) { - hash = ghash; - hlen += 1; - } - - err = gcry_sexp_build(&sexp, NULL, "%b", hlen, hash); - if (err) { - SSH_LOG(SSH_LOG_TRACE, - "DSA hash error: %s", gcry_strerror(err)); - return SSH_ERROR; - } - err = gcry_pk_verify(signature->dsa_sig, sexp, pubkey->dsa); - gcry_sexp_release(sexp); - if (err) { - SSH_LOG(SSH_LOG_TRACE, "Invalid DSA signature"); - if (gcry_err_code(err) != GPG_ERR_BAD_SIGNATURE) { - SSH_LOG(SSH_LOG_TRACE, - "DSA verify error: %s", - gcry_strerror(err)); - } - return SSH_ERROR; - } - break; case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA_CERT01: err = gcry_sexp_build(&sexp, @@ -2495,13 +2215,45 @@ int pki_verify_data_signature(ssh_signature signature, return SSH_OK; } +int ssh_key_size(ssh_key key) +{ + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA1: + return gcry_pk_get_nbits(key->rsa); + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + return gcry_pk_get_nbits(key->ecdsa); + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys have fixed size */ + return 255; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_UNKNOWN: + default: + return SSH_ERROR; + } +} + +#ifdef WITH_PKCS11_URI int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type) { (void) uri_name; (void) key; (void) key_type; - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "gcrypt does not support PKCS #11"); return SSH_ERROR; } +#endif /* WITH_PKCS11_URI */ #endif /* HAVE_LIBGCRYPT */ diff --git a/src/pki_mbedcrypto.c b/src/pki_mbedcrypto.c index 720fe1de..d3fda0ae 100644 --- a/src/pki_mbedcrypto.c +++ b/src/pki_mbedcrypto.c @@ -26,6 +26,7 @@ #ifdef HAVE_LIBMBEDCRYPTO #include <mbedtls/pk.h> #include <mbedtls/error.h> +#include "mbedcrypto-compat.h" #include "libssh/priv.h" #include "libssh/pki.h" @@ -37,6 +38,22 @@ #define MAX_PASSPHRASE_SIZE 1024 #define MAX_KEY_SIZE 32 +void pki_key_clean(ssh_key key) +{ + if (key == NULL) + return; + + if (key->rsa != NULL) { + mbedtls_pk_free(key->rsa); + SAFE_FREE(key->rsa); + } + + if (key->ecdsa != NULL) { + mbedtls_ecdsa_free(key->ecdsa); + SAFE_FREE(key->ecdsa); + } +} + ssh_string pki_private_key_to_pem(const ssh_key key, const char *passphrase, ssh_auth_callback auth_fn, void *auth_data) { @@ -50,7 +67,7 @@ static int pki_key_ecdsa_to_nid(mbedtls_ecdsa_context *ecdsa) { mbedtls_ecp_group_id id; - id = ecdsa->grp.id; + id = ecdsa->MBEDTLS_PRIVATE(grp.id); if (id == MBEDTLS_ECP_DP_SECP256R1) { return NID_mbedtls_nistp256; } else if (id == MBEDTLS_ECP_DP_SECP384R1) { @@ -84,118 +101,110 @@ ssh_key pki_private_key_from_base64(const char *b64_key, const char *passphrase, ssh_auth_callback auth_fn, void *auth_data) { ssh_key key = NULL; - mbedtls_pk_context *rsa = NULL; - mbedtls_pk_context *ecdsa = NULL; - ed25519_privkey *ed25519 = NULL; - enum ssh_keytypes_e type; + mbedtls_pk_context *pk = NULL; + mbedtls_pk_type_t mbed_type; int valid; /* mbedtls pk_parse_key expects strlen to count the 0 byte */ size_t b64len = strlen(b64_key) + 1; unsigned char tmp[MAX_PASSPHRASE_SIZE] = {0}; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_ctr_drbg_context *ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); +#endif - type = pki_privatekey_type_from_string(b64_key); - if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "Unknown or invalid private key."); - return NULL; + pk = malloc(sizeof(mbedtls_pk_context)); + if (pk == NULL) { + goto fail; } - - switch (type) { - case SSH_KEYTYPE_RSA: - rsa = malloc(sizeof(mbedtls_pk_context)); - if (rsa == NULL) { - return NULL; - } - - mbedtls_pk_init(rsa); - - if (passphrase == NULL) { - if (auth_fn) { - valid = auth_fn("Passphrase for private key:", (char *) tmp, - MAX_PASSPHRASE_SIZE, 0, 0, auth_data); - if (valid < 0) { - goto fail; - } - /* TODO fix signedness and strlen */ - valid = mbedtls_pk_parse_key(rsa, - (const unsigned char *) b64_key, - b64len, tmp, - strnlen((const char *) tmp, MAX_PASSPHRASE_SIZE)); - } else { - valid = mbedtls_pk_parse_key(rsa, - (const unsigned char *) b64_key, - b64len, NULL, - 0); - } - } else { - valid = mbedtls_pk_parse_key(rsa, - (const unsigned char *) b64_key, b64len, - (const unsigned char *) passphrase, - strnlen(passphrase, MAX_PASSPHRASE_SIZE)); - } - - if (valid != 0) { - char error_buf[100]; - mbedtls_strerror(valid, error_buf, 100); - SSH_LOG(SSH_LOG_WARN,"Parsing private key %s", error_buf); + mbedtls_pk_init(pk); + + if (passphrase == NULL) { + if (auth_fn) { + valid = auth_fn("Passphrase for private key:", + (char *)tmp, + MAX_PASSPHRASE_SIZE, + 0, + 0, + auth_data); + if (valid < 0) { goto fail; } - break; - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: - ecdsa = malloc(sizeof(mbedtls_pk_context)); - if (ecdsa == NULL) { - return NULL; - } - - mbedtls_pk_init(ecdsa); - - if (passphrase == NULL) { - if (auth_fn) { - valid = auth_fn("Passphrase for private key:", (char *) tmp, - MAX_PASSPHRASE_SIZE, 0, 0, auth_data); - if (valid < 0) { - goto fail; - } - valid = mbedtls_pk_parse_key(ecdsa, - (const unsigned char *) b64_key, - b64len, tmp, - strnlen((const char *) tmp, MAX_PASSPHRASE_SIZE)); - } else { - valid = mbedtls_pk_parse_key(ecdsa, - (const unsigned char *) b64_key, - b64len, NULL, - 0); - } - } else { - valid = mbedtls_pk_parse_key(ecdsa, - (const unsigned char *) b64_key, b64len, - (const unsigned char *) passphrase, - strnlen(passphrase, MAX_PASSPHRASE_SIZE)); - } - - if (valid != 0) { - char error_buf[100]; - mbedtls_strerror(valid, error_buf, 100); - SSH_LOG(SSH_LOG_WARN,"Parsing private key %s", error_buf); - goto fail; - } - break; - case SSH_KEYTYPE_ED25519: - /* Cannot open ed25519 keys with libmbedcrypto */ - default: - SSH_LOG(SSH_LOG_WARN, "Unknown or invalid private key type %d", - type); - return NULL; +#if MBEDTLS_VERSION_MAJOR > 2 + valid = mbedtls_pk_parse_key( + pk, + (const unsigned char *)b64_key, + b64len, + tmp, + strnlen((const char *)tmp, MAX_PASSPHRASE_SIZE), + mbedtls_ctr_drbg_random, + ctr_drbg); +#else + valid = mbedtls_pk_parse_key( + pk, + (const unsigned char *)b64_key, + b64len, + tmp, + strnlen((const char *)tmp, MAX_PASSPHRASE_SIZE)); +#endif + } else { +#if MBEDTLS_VERSION_MAJOR > 2 + valid = mbedtls_pk_parse_key(pk, + (const unsigned char *)b64_key, + b64len, + NULL, + 0, + mbedtls_ctr_drbg_random, + ctr_drbg); +#else + valid = mbedtls_pk_parse_key(pk, + (const unsigned char *)b64_key, + b64len, + NULL, + 0); +#endif + } + } else { +#if MBEDTLS_VERSION_MAJOR > 2 + valid = mbedtls_pk_parse_key(pk, + (const unsigned char *)b64_key, + b64len, + (const unsigned char *)passphrase, + strnlen(passphrase, MAX_PASSPHRASE_SIZE), + mbedtls_ctr_drbg_random, + ctr_drbg); +#else + valid = mbedtls_pk_parse_key(pk, + (const unsigned char *)b64_key, + b64len, + (const unsigned char *)passphrase, + strnlen(passphrase, MAX_PASSPHRASE_SIZE)); +#endif + } + if (valid != 0) { + char error_buf[100]; + mbedtls_strerror(valid, error_buf, 100); + SSH_LOG(SSH_LOG_WARN, "Parsing private key %s", error_buf); + goto fail; } + mbed_type = mbedtls_pk_get_type(pk); + key = ssh_key_new(); if (key == NULL) { goto fail; } - if (ecdsa != NULL) { - mbedtls_ecp_keypair *keypair = mbedtls_pk_ec(*ecdsa); + switch (mbed_type) { + case MBEDTLS_PK_RSA: + case MBEDTLS_PK_RSA_ALT: + key->rsa = pk; + pk = NULL; + key->type = SSH_KEYTYPE_RSA; + break; + case MBEDTLS_PK_ECKEY: + case MBEDTLS_PK_ECDSA: { + /* type will be set later */ + mbedtls_ecp_keypair *keypair = mbedtls_pk_ec(*pk); + pk = NULL; key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); if (key->ecdsa == NULL) { @@ -204,40 +213,36 @@ ssh_key pki_private_key_from_base64(const char *b64_key, const char *passphrase, mbedtls_ecdsa_init(key->ecdsa); mbedtls_ecdsa_from_keypair(key->ecdsa, keypair); - mbedtls_pk_free(ecdsa); - SAFE_FREE(ecdsa); + mbedtls_pk_free(pk); + SAFE_FREE(pk); key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa); /* pki_privatekey_type_from_string always returns P256 for ECDSA - * keys, so we need to figure out the correct type here */ - type = pki_key_ecdsa_to_key_type(key->ecdsa); - if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "Invalid private key."); + * keys, so we need to figure out the correct type here */ + key->type = pki_key_ecdsa_to_key_type(key->ecdsa); + if (key->type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid private key."); goto fail; } - } else { - key->ecdsa = NULL; + break; + } + default: + SSH_LOG(SSH_LOG_WARN, + "Unknown or invalid private key type %d", + mbed_type); + return NULL; } - key->type = type; - key->type_c = ssh_key_type_to_char(type); + key->type_c = ssh_key_type_to_char(key->type); key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; - key->rsa = rsa; - key->ed25519_privkey = ed25519; - rsa = NULL; - ecdsa = NULL; return key; fail: ssh_key_free(key); - if (rsa != NULL) { - mbedtls_pk_free(rsa); - SAFE_FREE(rsa); - } - if (ecdsa != NULL) { - mbedtls_pk_free(ecdsa); - SAFE_FREE(ecdsa); + if (pk != NULL) { + mbedtls_pk_free(pk); + SAFE_FREE(pk); } return NULL; } @@ -276,19 +281,19 @@ int pki_privkey_build_rsa(ssh_key key, ssh_string_data(d), ssh_string_len(d), ssh_string_data(e), ssh_string_len(e)); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, "Failed to import private RSA key"); + SSH_LOG(SSH_LOG_TRACE, "Failed to import private RSA key"); goto fail; } rc = mbedtls_rsa_complete(rsa); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, "Failed to complete private RSA key"); + SSH_LOG(SSH_LOG_TRACE, "Failed to complete private RSA key"); goto fail; } rc = mbedtls_rsa_check_privkey(rsa); if (rc != 0) { - SSH_LOG(SSH_LOG_WARN, "Inconsistent private RSA key"); + SSH_LOG(SSH_LOG_TRACE, "Inconsistent private RSA key"); goto fail; } @@ -304,6 +309,10 @@ int pki_pubkey_build_rsa(ssh_key key, ssh_string e, ssh_string n) { mbedtls_rsa_context *rsa = NULL; const mbedtls_pk_info_t *pk_info = NULL; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi N; + mbedtls_mpi E; +#endif int rc; key->rsa = malloc(sizeof(mbedtls_pk_context)); @@ -320,26 +329,59 @@ int pki_pubkey_build_rsa(ssh_key key, ssh_string e, ssh_string n) goto fail; } +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&E); +#endif + rsa = mbedtls_pk_rsa(*key->rsa); +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_mpi_read_binary(&N, ssh_string_data(n), + ssh_string_len(n)); +#else rc = mbedtls_mpi_read_binary(&rsa->N, ssh_string_data(n), ssh_string_len(n)); +#endif if (rc != 0) { goto fail; } +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_mpi_read_binary(&E, ssh_string_data(e), + ssh_string_len(e)); +#else rc = mbedtls_mpi_read_binary(&rsa->E, ssh_string_data(e), ssh_string_len(e)); +#endif if (rc != 0) { goto fail; } - rsa->len = (mbedtls_mpi_bitlen(&rsa->N) + 7) >> 3; +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_import(rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } - return SSH_OK; + rc = mbedtls_rsa_complete(rsa); + if (rc != 0) { + goto fail; + } +#else + rsa->len = (mbedtls_mpi_bitlen(&rsa->N) + 7) >> 3; +#endif + rc = SSH_OK; + goto exit; fail: + rc = SSH_ERROR; mbedtls_pk_free(key->rsa); SAFE_FREE(key->rsa); - return SSH_ERROR; +exit: +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&E); +#endif + return rc; } ssh_key pki_key_dup(const ssh_key key, int demote) @@ -347,7 +389,13 @@ ssh_key pki_key_dup(const ssh_key key, int demote) ssh_key new = NULL; int rc; const mbedtls_pk_info_t *pk_info = NULL; - +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi N; + mbedtls_mpi E; + mbedtls_mpi D; + mbedtls_mpi P; + mbedtls_mpi Q; +#endif new = ssh_key_new(); if (new == NULL) { @@ -362,6 +410,13 @@ ssh_key pki_key_dup(const ssh_key key, int demote) new->flags = key->flags; } +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&E); + mbedtls_mpi_init(&D); + mbedtls_mpi_init(&P); + mbedtls_mpi_init(&Q); +#endif switch(key->type) { case SSH_KEYTYPE_RSA: { @@ -376,11 +431,26 @@ ssh_key pki_key_dup(const ssh_key key, int demote) pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); mbedtls_pk_setup(new->rsa, pk_info); - if (mbedtls_pk_can_do(key->rsa, MBEDTLS_PK_RSA) && - mbedtls_pk_can_do(new->rsa, MBEDTLS_PK_RSA)) { - rsa = mbedtls_pk_rsa(*key->rsa); - new_rsa = mbedtls_pk_rsa(*new->rsa); + if (!mbedtls_pk_can_do(key->rsa, MBEDTLS_PK_RSA) || + !mbedtls_pk_can_do(new->rsa, MBEDTLS_PK_RSA)) + { + goto fail; + } + + rsa = mbedtls_pk_rsa(*key->rsa); + new_rsa = mbedtls_pk_rsa(*new->rsa); + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, &N, &P, &Q, &D, &E); + if (rc != 0) { + goto fail; + } + rc = mbedtls_rsa_import(new_rsa, &N, &P, &Q, &D, &E); + if (rc != 0) { + goto fail; + } +#else rc = mbedtls_mpi_copy(&new_rsa->N, &rsa->N); if (rc != 0) { goto fail; @@ -390,42 +460,70 @@ ssh_key pki_key_dup(const ssh_key key, int demote) if (rc != 0) { goto fail; } + new_rsa->len = (mbedtls_mpi_bitlen(&new_rsa->N) + 7) >> 3; - if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { - rc = mbedtls_mpi_copy(&new_rsa->D, &rsa->D); - if (rc != 0) { - goto fail; - } + rc = mbedtls_mpi_copy(&new_rsa->D, &rsa->D); + if (rc != 0) { + goto fail; + } - rc = mbedtls_mpi_copy(&new_rsa->P, &rsa->P); - if (rc != 0) { - goto fail; - } + rc = mbedtls_mpi_copy(&new_rsa->P, &rsa->P); + if (rc != 0) { + goto fail; + } - rc = mbedtls_mpi_copy(&new_rsa->Q, &rsa->Q); - if (rc != 0) { - goto fail; - } + rc = mbedtls_mpi_copy(&new_rsa->Q, &rsa->Q); + if (rc != 0) { + goto fail; + } - rc = mbedtls_mpi_copy(&new_rsa->DP, &rsa->DP); - if (rc != 0) { - goto fail; - } + rc = mbedtls_mpi_copy(&new_rsa->DP, &rsa->DP); + if (rc != 0) { + goto fail; + } - rc = mbedtls_mpi_copy(&new_rsa->DQ, &rsa->DQ); - if (rc != 0) { - goto fail; - } + rc = mbedtls_mpi_copy(&new_rsa->DQ, &rsa->DQ); + if (rc != 0) { + goto fail; + } - rc = mbedtls_mpi_copy(&new_rsa->QP, &rsa->QP); - if (rc != 0) { - goto fail; - } + rc = mbedtls_mpi_copy(&new_rsa->QP, &rsa->QP); + if (rc != 0) { + goto fail; } +#endif } else { +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } + rc = mbedtls_rsa_import(new_rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } +#else + rc = mbedtls_mpi_copy(&new_rsa->N, &rsa->N); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->E, &rsa->E); + if (rc != 0) { + goto fail; + } + + new_rsa->len = (mbedtls_mpi_bitlen(&new_rsa->N) + 7) >> 3; +#endif + } + +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_complete(new_rsa); + if (rc != 0) { goto fail; } +#endif break; } @@ -443,12 +541,14 @@ ssh_key pki_key_dup(const ssh_key key, int demote) mbedtls_ecdsa_init(new->ecdsa); if (demote && ssh_key_is_private(key)) { - rc = mbedtls_ecp_copy(&new->ecdsa->Q, &key->ecdsa->Q); + rc = mbedtls_ecp_copy(&new->ecdsa->MBEDTLS_PRIVATE(Q), + &key->ecdsa->MBEDTLS_PRIVATE(Q)); if (rc != 0) { goto fail; } - rc = mbedtls_ecp_group_copy(&new->ecdsa->grp, &key->ecdsa->grp); + rc = mbedtls_ecp_group_copy(&new->ecdsa->MBEDTLS_PRIVATE(grp), + &key->ecdsa->MBEDTLS_PRIVATE(grp)); if (rc != 0) { goto fail; } @@ -467,10 +567,19 @@ ssh_key pki_key_dup(const ssh_key key, int demote) goto fail; } - return new; + goto cleanup; + fail: - ssh_key_free(new); - return NULL; + SSH_KEY_FREE(new); +cleanup: +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&E); + mbedtls_mpi_free(&D); + mbedtls_mpi_free(&P); + mbedtls_mpi_free(&Q); +#endif + return new; } int pki_key_generate_rsa(ssh_key key, int parameter) @@ -508,36 +617,140 @@ int pki_key_generate_rsa(ssh_key key, int parameter) int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) { - switch (k1->type) { + int rc = 0; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi N1; + mbedtls_mpi N2; + mbedtls_mpi P1; + mbedtls_mpi P2; + mbedtls_mpi Q1; + mbedtls_mpi Q2; + mbedtls_mpi E1; + mbedtls_mpi E2; + + mbedtls_mpi_init(&N1); + mbedtls_mpi_init(&N2); + mbedtls_mpi_init(&P1); + mbedtls_mpi_init(&P2); + mbedtls_mpi_init(&Q1); + mbedtls_mpi_init(&Q2); + mbedtls_mpi_init(&E1); + mbedtls_mpi_init(&E2); +#endif + + switch (ssh_key_type_plain(k1->type)) { case SSH_KEYTYPE_RSA: { mbedtls_rsa_context *rsa1, *rsa2; - if (mbedtls_pk_can_do(k1->rsa, MBEDTLS_PK_RSA) && - mbedtls_pk_can_do(k2->rsa, MBEDTLS_PK_RSA)) { - if (mbedtls_pk_get_type(k1->rsa) != mbedtls_pk_get_type(k2->rsa) || - mbedtls_pk_get_bitlen(k1->rsa) != - mbedtls_pk_get_bitlen(k2->rsa)) { - return 1; + if (!mbedtls_pk_can_do(k1->rsa, MBEDTLS_PK_RSA) || + !mbedtls_pk_can_do(k2->rsa, MBEDTLS_PK_RSA)) + { + break; + } + + if (mbedtls_pk_get_type(k1->rsa) != mbedtls_pk_get_type(k2->rsa) || + mbedtls_pk_get_bitlen(k1->rsa) != + mbedtls_pk_get_bitlen(k2->rsa)) + { + rc = 1; + goto cleanup; + } + + if (what == SSH_KEY_CMP_PUBLIC) { +#if MBEDTLS_VERSION_MAJOR > 2 + rsa1 = mbedtls_pk_rsa(*k1->rsa); + rc = mbedtls_rsa_export(rsa1, &N1, NULL, NULL, NULL, &E1); + if (rc != 0) { + rc = 1; + goto cleanup; + } + + rsa2 = mbedtls_pk_rsa(*k2->rsa); + rc = mbedtls_rsa_export(rsa2, &N2, NULL, NULL, NULL, &E2); + if (rc != 0) { + rc = 1; + goto cleanup; } + if (mbedtls_mpi_cmp_mpi(&N1, &N2) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&E1, &E2) != 0) { + rc = 1; + goto cleanup; + } +#else rsa1 = mbedtls_pk_rsa(*k1->rsa); rsa2 = mbedtls_pk_rsa(*k2->rsa); if (mbedtls_mpi_cmp_mpi(&rsa1->N, &rsa2->N) != 0) { - return 1; + rc = 1; + goto cleanup; } if (mbedtls_mpi_cmp_mpi(&rsa1->E, &rsa2->E) != 0) { - return 1; + rc = 1; + goto cleanup; + } +#endif + } else if (what == SSH_KEY_CMP_PRIVATE) { +#if MBEDTLS_VERSION_MAJOR > 2 + rsa1 = mbedtls_pk_rsa(*k1->rsa); + rc = mbedtls_rsa_export(rsa1, &N1, &P1, &Q1, NULL, &E1); + if (rc != 0) { + rc = 1; + goto cleanup; } - if (what == SSH_KEY_CMP_PRIVATE) { - if (mbedtls_mpi_cmp_mpi(&rsa1->P, &rsa2->P) != 0) { - return 1; - } + rsa2 = mbedtls_pk_rsa(*k2->rsa); + rc = mbedtls_rsa_export(rsa2, &N2, &P2, &Q2, NULL, &E2); + if (rc != 0) { + rc = 1; + goto cleanup; + } - if (mbedtls_mpi_cmp_mpi(&rsa1->Q, &rsa2->Q) != 0) { - return 1; - } + if (mbedtls_mpi_cmp_mpi(&N1, &N2) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&E1, &E2) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&P1, &P2) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&Q1, &Q2) != 0) { + rc = 1; + goto cleanup; + } +#else + rsa1 = mbedtls_pk_rsa(*k1->rsa); + rsa2 = mbedtls_pk_rsa(*k2->rsa); + if (mbedtls_mpi_cmp_mpi(&rsa1->N, &rsa2->N) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->E, &rsa2->E) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->P, &rsa2->P) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->Q, &rsa2->Q) != 0) { + rc = 1; + goto cleanup; } +#endif } break; } @@ -548,25 +761,39 @@ int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) mbedtls_ecp_keypair *ecdsa1 = k1->ecdsa; mbedtls_ecp_keypair *ecdsa2 = k2->ecdsa; - if (ecdsa1->grp.id != ecdsa2->grp.id) { - return 1; + if (ecdsa1->MBEDTLS_PRIVATE(grp).id != + ecdsa2->MBEDTLS_PRIVATE(grp).id) { + rc = 1; + goto cleanup; } - if (mbedtls_mpi_cmp_mpi(&ecdsa1->Q.X, &ecdsa2->Q.X)) { - return 1; + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X), + &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X))) + { + rc = 1; + goto cleanup; } - if (mbedtls_mpi_cmp_mpi(&ecdsa1->Q.Y, &ecdsa2->Q.Y)) { - return 1; + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y), + &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y))) + { + rc = 1; + goto cleanup; } - if (mbedtls_mpi_cmp_mpi(&ecdsa1->Q.Z, &ecdsa2->Q.Z)) { - return 1; + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Z), + &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Z))) + { + rc = 1; + goto cleanup; } if (what == SSH_KEY_CMP_PRIVATE) { - if (mbedtls_mpi_cmp_mpi(&ecdsa1->d, &ecdsa2->d)) { - return 1; + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(d), + &ecdsa2->MBEDTLS_PRIVATE(d))) + { + rc = 1; + goto cleanup; } } @@ -575,12 +802,25 @@ int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: /* ed25519 keys handled globally */ - return 0; + rc = 0; + break; default: - return 1; - } - - return 0; + rc = 1; + break; + } + +cleanup: +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N1); + mbedtls_mpi_free(&N2); + mbedtls_mpi_free(&P1); + mbedtls_mpi_free(&P2); + mbedtls_mpi_free(&Q1); + mbedtls_mpi_free(&Q2); + mbedtls_mpi_free(&E1); + mbedtls_mpi_free(&E2); +#endif + return rc; } ssh_string make_ecpoint_string(const mbedtls_ecp_group *g, const @@ -638,15 +878,28 @@ static const char* pki_key_ecdsa_nid_to_char(int nid) return "unknown"; } -ssh_string pki_publickey_to_blob(const ssh_key key) +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) { ssh_buffer buffer = NULL; ssh_string type_s = NULL; ssh_string e = NULL; ssh_string n = NULL; ssh_string str = NULL; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi E; + mbedtls_mpi N; + mbedtls_mpi D; + mbedtls_mpi IQMP; + mbedtls_mpi P; + mbedtls_mpi Q; +#endif int rc; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_init(&E); + mbedtls_mpi_init(&N); +#endif + buffer = ssh_buffer_new(); if (buffer == NULL) { return NULL; @@ -685,31 +938,151 @@ ssh_string pki_publickey_to_blob(const ssh_key key) rsa = mbedtls_pk_rsa(*key->rsa); - e = ssh_make_bignum_string(&rsa->E); +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } + + e = ssh_make_bignum_string(&E); if (e == NULL) { goto fail; } - n = ssh_make_bignum_string(&rsa->N); + n = ssh_make_bignum_string(&N); if (n == NULL) { goto fail; } - - if (ssh_buffer_add_ssh_string(buffer, e) < 0) { +#else + e = ssh_make_bignum_string(&rsa->E); + if (e == NULL) { goto fail; } - if (ssh_buffer_add_ssh_string(buffer, n) < 0) { + n = ssh_make_bignum_string(&rsa->N); + if (n == NULL) { goto fail; } +#endif + if (type == SSH_KEY_PUBLIC) { + /* The N and E parts are swapped in the public key export ! */ + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + } else if (type == SSH_KEY_PRIVATE) { + ssh_string p = NULL; + ssh_string q = NULL; + ssh_string d = NULL; + ssh_string iqmp = NULL; + + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, NULL, &P, &Q, &D, NULL); + if (rc != 0) { + goto fail; + } + + p = ssh_make_bignum_string(&P); + if (p == NULL) { + goto fail; + } + + q = ssh_make_bignum_string(&Q); + if (q == NULL) { + goto fail; + } + + d = ssh_make_bignum_string(&D); + if (d == NULL) { + goto fail; + } + rc = mbedtls_rsa_export_crt(rsa, NULL, NULL, &IQMP) + if (rc != 0) { + goto fail; + } + + iqmp = ssh_make_bignum_string(&IQMP); + if (iqmp == NULL) { + goto fail; + } + +#else + p = ssh_make_bignum_string(&rsa->P); + if (p == NULL) { + goto fail; + } + + q = ssh_make_bignum_string(&rsa->Q); + if (q == NULL) { + goto fail; + } + + d = ssh_make_bignum_string(&rsa->D); + if (d == NULL) { + goto fail; + } + + iqmp = ssh_make_bignum_string(&rsa->QP); + if (iqmp == NULL) { + goto fail; + } +#endif + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, iqmp); + if (rc < 0) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, q); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(d); + SSH_STRING_FREE(d); + d = NULL; + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); + iqmp = NULL; + ssh_string_burn(p); + SSH_STRING_FREE(p); + p = NULL; + ssh_string_burn(q); + SSH_STRING_FREE(q); + q = NULL; + } ssh_string_burn(e); SSH_STRING_FREE(e); e = NULL; ssh_string_burn(n); SSH_STRING_FREE(n); n = NULL; - break; } case SSH_KEYTYPE_ECDSA_P256: @@ -730,7 +1103,8 @@ ssh_string pki_publickey_to_blob(const ssh_key key) return NULL; } - e = make_ecpoint_string(&key->ecdsa->grp, &key->ecdsa->Q); + e = make_ecpoint_string(&key->ecdsa->MBEDTLS_PRIVATE(grp), + &key->ecdsa->MBEDTLS_PRIVATE(Q)); if (e == NULL) { SSH_BUFFER_FREE(buffer); @@ -746,21 +1120,51 @@ ssh_string pki_publickey_to_blob(const ssh_key key) SSH_STRING_FREE(e); e = NULL; - if (key->type == SSH_KEYTYPE_SK_ECDSA && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; - } + if (type == SSH_KEY_PRIVATE) { + ssh_string d = NULL; + d = ssh_make_bignum_string(&key->ecdsa->MBEDTLS_PRIVATE(d)); + + if (d == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(d); + SSH_STRING_FREE(d); + d = NULL; + } else if (key->type == SSH_KEYTYPE_SK_ECDSA) { + /* public key can contain certificate sk information */ + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto fail; + } + } break; case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: - rc = pki_ed25519_public_key_to_blob(buffer, key); - if (rc != SSH_OK) { - goto fail; - } - if (key->type == SSH_KEYTYPE_SK_ED25519 && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; + if (type == SSH_KEY_PUBLIC) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + /* public key can contain certificate sk information */ + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto fail; + } + } + } else { + rc = pki_ed25519_private_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } } break; default: @@ -779,6 +1183,10 @@ makestring: } SSH_BUFFER_FREE(buffer); +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&E); +#endif return str; fail: SSH_BUFFER_FREE(buffer); @@ -788,6 +1196,10 @@ fail: SSH_STRING_FREE(e); ssh_string_burn(n); SSH_STRING_FREE(n); +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&E); +#endif return NULL; } @@ -858,7 +1270,7 @@ ssh_string pki_signature_to_blob(const ssh_signature sig) sig_blob = pki_ed25519_signature_to_blob(sig); break; default: - SSH_LOG(SSH_LOG_WARN, "Unknown signature key type: %s", + SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %s", sig->type_c); return NULL; } @@ -878,20 +1290,20 @@ static ssh_signature pki_signature_from_rsa_blob(const ssh_key pubkey, const size_t len = ssh_string_len(sig_blob); if (pubkey->rsa == NULL) { - SSH_LOG(SSH_LOG_WARN, "Pubkey RSA field NULL"); + SSH_LOG(SSH_LOG_TRACE, "Pubkey RSA field NULL"); goto errout; } rsalen = mbedtls_pk_get_bitlen(pubkey->rsa) / 8; if (len > rsalen) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Signature is too big: %lu > %lu", (unsigned long) len, (unsigned long) rsalen); goto errout; } #ifdef DEBUG_CRYPTO - SSH_LOG(SSH_LOG_WARN, "RSA signature len: %lu", (unsigned long)len); + SSH_LOG(SSH_LOG_TRACE, "RSA signature len: %lu", (unsigned long)len); ssh_log_hexdump("RSA signature", ssh_string_data(sig_blob), len); #endif @@ -932,7 +1344,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, int rc; if (ssh_key_type_plain(pubkey->type) != type) { - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_TRACE, "Incompatible public key provided (%d) expecting (%d)", type, pubkey->type); @@ -1017,7 +1429,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, } if (rlen != 0) { - SSH_LOG(SSH_LOG_WARN, "Signature has remaining bytes in inner " + SSH_LOG(SSH_LOG_TRACE, "Signature has remaining bytes in inner " "sigblob: %lu", (unsigned long)rlen); ssh_signature_free(sig); @@ -1035,7 +1447,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, } break; default: - SSH_LOG(SSH_LOG_WARN, "Unknown signature type"); + SSH_LOG(SSH_LOG_TRACE, "Unknown signature type"); return NULL; } @@ -1051,6 +1463,7 @@ static ssh_string rsa_do_sign_hash(const unsigned char *digest, mbedtls_md_type_t md = 0; unsigned char *sig = NULL; size_t slen; + size_t sig_size; int ok; switch (hash_type) { @@ -1065,11 +1478,12 @@ static ssh_string rsa_do_sign_hash(const unsigned char *digest, break; case SSH_DIGEST_AUTO: default: - SSH_LOG(SSH_LOG_WARN, "Incompatible key algorithm"); + SSH_LOG(SSH_LOG_TRACE, "Incompatible key algorithm"); return NULL; } - sig = malloc(mbedtls_pk_get_bitlen(privkey) / 8); + sig_size = mbedtls_pk_get_bitlen(privkey) / 8; + sig = malloc(sig_size); if (sig == NULL) { return NULL; } @@ -1079,6 +1493,9 @@ static ssh_string rsa_do_sign_hash(const unsigned char *digest, digest, dlen, sig, +#if MBEDTLS_VERSION_MAJOR > 2 + sig_size, +#endif &slen, mbedtls_ctr_drbg_random, ssh_get_mbedtls_ctr_drbg_context()); @@ -1145,10 +1562,10 @@ ssh_signature pki_do_sign_hash(const ssh_key privkey, return NULL; } - rc = mbedtls_ecdsa_sign(&privkey->ecdsa->grp, + rc = mbedtls_ecdsa_sign(&privkey->ecdsa->MBEDTLS_PRIVATE(grp), sig->ecdsa_sig.r, sig->ecdsa_sig.s, - &privkey->ecdsa->d, + &privkey->ecdsa->MBEDTLS_PRIVATE(d), hash, hlen, mbedtls_ctr_drbg_random, @@ -1351,8 +1768,9 @@ int pki_verify_data_signature(ssh_signature signature, case SSH_KEYTYPE_ECDSA_P521_CERT01: case SSH_KEYTYPE_SK_ECDSA: case SSH_KEYTYPE_SK_ECDSA_CERT01: - rc = mbedtls_ecdsa_verify(&pubkey->ecdsa->grp, hash, hlen, - &pubkey->ecdsa->Q, signature->ecdsa_sig.r, + rc = mbedtls_ecdsa_verify(&pubkey->ecdsa->MBEDTLS_PRIVATE(grp), hash, + hlen, &pubkey->ecdsa->MBEDTLS_PRIVATE(Q), + signature->ecdsa_sig.r, signature->ecdsa_sig.s); if (rc != 0) { char error_buf[100]; @@ -1455,18 +1873,19 @@ int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) goto fail; } - rc = mbedtls_ecp_copy(&keypair.Q, &Q); + rc = mbedtls_ecp_copy(&keypair.MBEDTLS_PRIVATE(Q), &Q); if (rc != 0) { goto fail; } - rc = mbedtls_ecp_group_copy(&keypair.grp, &group); + rc = mbedtls_ecp_group_copy(&keypair.MBEDTLS_PRIVATE(grp), &group); if (rc != 0) { goto fail; } - rc = mbedtls_mpi_read_binary(&keypair.d, ssh_string_data(exp), - ssh_string_len(exp)); + rc = mbedtls_mpi_read_binary(&keypair.MBEDTLS_PRIVATE(d), + ssh_string_data(exp), + ssh_string_len(exp)); if (rc != 0) { goto fail; } @@ -1522,17 +1941,17 @@ int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) goto fail; } - rc = mbedtls_ecp_copy(&keypair.Q, &Q); + rc = mbedtls_ecp_copy(&keypair.MBEDTLS_PRIVATE(Q), &Q); if (rc != 0) { goto fail; } - rc = mbedtls_ecp_group_copy(&keypair.grp, &group); + rc = mbedtls_ecp_group_copy(&keypair.MBEDTLS_PRIVATE(grp), &group); if (rc != 0) { goto fail; } - mbedtls_mpi_init(&keypair.d); + mbedtls_mpi_init(&keypair.MBEDTLS_PRIVATE(d)); rc = mbedtls_ecdsa_from_keypair(key->ecdsa, &keypair); if (rc != 0) { @@ -1592,36 +2011,39 @@ int pki_key_generate_ecdsa(ssh_key key, int parameter) return SSH_OK; } -int pki_privkey_build_dss(ssh_key key, ssh_string p, ssh_string q, ssh_string g, - ssh_string pubkey, ssh_string privkey) -{ - (void) key; - (void) p; - (void) q; - (void) g; - (void) pubkey; - (void) privkey; - return SSH_ERROR; -} - -int pki_pubkey_build_dss(ssh_key key, ssh_string p, ssh_string q, ssh_string g, - ssh_string pubkey) +int ssh_key_size(ssh_key key) { - (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; + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA1: + return mbedtls_pk_get_bitlen(key->rsa); + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + return 256; + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + return 384; + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + return 521; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys have fixed size */ + return 255; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_UNKNOWN: + default: + return SSH_ERROR; + } } +#ifdef WITH_PKCS11_URI int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type) { (void) uri_name; @@ -1631,4 +2053,5 @@ int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type) "mbedcrypto does not support PKCS #11"); return SSH_ERROR; } +#endif /* WITH_PKCS11_URI */ #endif /* HAVE_LIBMBEDCRYPTO */ @@ -44,14 +44,14 @@ #endif /** - * @defgroup libssh_poll The SSH poll functions. + * @defgroup libssh_poll The SSH poll functions * @ingroup libssh * * Add a generic way to handle sockets asynchronously. * * It's based on poll objects, each of which store a socket, its events and a * callback, which gets called whenever an event is set. The poll objects are - * attached to a poll context, which should be allocated on per thread basis. + * attached to a poll context, which should be allocated on a per thread basis. * * Polling the poll context will poll all the attached poll objects and call * their callbacks (handlers) if any of the socket events are set. This should @@ -68,7 +68,7 @@ struct ssh_poll_handle_struct { size_t idx; } x; short events; - int lock; + uint32_t lock_cnt; ssh_poll_callback cb; void *cb_data; }; @@ -84,15 +84,18 @@ struct ssh_poll_ctx_struct { #ifdef HAVE_POLL #include <poll.h> -void ssh_poll_init(void) { +void ssh_poll_init(void) +{ return; } -void ssh_poll_cleanup(void) { +void ssh_poll_cleanup(void) +{ return; } -int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) { +int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) +{ return poll((struct pollfd *) fds, nfds, timeout); } @@ -210,8 +213,8 @@ static short bsd_socket_compute_revents(int fd, short events) * poll implementation. * * Keep in mind that select is terribly inefficient. The interface is simply not - * meant to be used with maximum descriptor value greater, say, 32 or so. With - * a value as high as 1024 on Linux you'll pay dearly in every single call. + * meant to be used with maximum descriptor value greater than, say, 32 or so. + * With a value as high as 1024 on Linux you'll pay dearly in every single call. * poll() will be orders of magnitude faster. */ static int bsd_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) @@ -246,19 +249,17 @@ static int bsd_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) } #endif - if (fds[i].events & (POLLIN | POLLRDNORM)) { - FD_SET (fds[i].fd, &readfds); - } + // we use the readfds to get POLLHUP and POLLERR, which are provided even when not requested + FD_SET (fds[i].fd, &readfds); + if (fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND)) { FD_SET (fds[i].fd, &writefds); } if (fds[i].events & (POLLPRI | POLLRDBAND)) { FD_SET (fds[i].fd, &exceptfds); } - if (fds[i].fd > max_fd && - (fds[i].events & (POLLIN | POLLOUT | POLLPRI | - POLLRDNORM | POLLRDBAND | - POLLWRNORM | POLLWRBAND))) { + + if (fds[i].fd > max_fd) { max_fd = fds[i].fd; rc = 0; } @@ -286,7 +287,7 @@ static int bsd_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) if (rc < 0) { return -1; } - /* A timeout occured */ + /* A timeout occurred */ if (rc == 0) { return 0; } @@ -335,21 +336,24 @@ int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) { /** * @brief Allocate a new poll object, which could be used within a poll context. * - * @param fd Socket that will be polled. - * @param events Poll events that will be monitored for the socket. i.e. - * POLLIN, POLLPRI, POLLOUT - * @param cb Function to be called if any of the events are set. - * The prototype of cb is: - * int (*ssh_poll_callback)(ssh_poll_handle p, socket_t fd, - * int revents, void *userdata); - * @param userdata Userdata to be passed to the callback function. NULL if - * not needed. + * @param[in] fd Socket that will be polled. + * @param[in] events Poll events that will be monitored for the socket. + * i.e. POLLIN, POLLPRI, POLLOUT + * @param[in] cb Function to be called if any of the events are set. + * The prototype of cb is: + * int (*ssh_poll_callback)(ssh_poll_handle p, + * socket_t fd, + * int revents, + * void *userdata); + * @param[in] userdata Userdata to be passed to the callback function. + * NULL if not needed. * - * @return A new poll object, NULL on error + * @return A new poll object, NULL on error */ -ssh_poll_handle ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, - void *userdata) { +ssh_poll_handle +ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, void *userdata) +{ ssh_poll_handle p; p = malloc(sizeof(struct ssh_poll_handle_struct)); @@ -373,12 +377,13 @@ ssh_poll_handle ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, * @param p Pointer to an already allocated poll object. */ -void ssh_poll_free(ssh_poll_handle p) { - if(p->ctx != NULL){ - ssh_poll_ctx_remove(p->ctx,p); - p->ctx=NULL; - } - SAFE_FREE(p); +void ssh_poll_free(ssh_poll_handle p) +{ + if (p->ctx != NULL) { + ssh_poll_ctx_remove(p->ctx, p); + p->ctx = NULL; + } + SAFE_FREE(p); } /** @@ -388,8 +393,9 @@ void ssh_poll_free(ssh_poll_handle p) { * * @return Poll context or NULL if the poll object isn't attached. */ -ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p) { - return p->ctx; +ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p) +{ + return p->ctx; } /** @@ -399,22 +405,31 @@ ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p) { * * @return Poll events. */ -short ssh_poll_get_events(ssh_poll_handle p) { - return p->events; +short ssh_poll_get_events(ssh_poll_handle p) +{ + return p->events; } /** * @brief Set the events of a poll object. The events will also be propagated - * to an associated poll context. + * to an associated poll context unless the fd is locked. In that case, + * only the POLLOUT can be set. * * @param p Pointer to an already allocated poll object. * @param events Poll events. */ -void ssh_poll_set_events(ssh_poll_handle p, short events) { - p->events = events; - if (p->ctx != NULL && !p->lock) { - p->ctx->pollfds[p->x.idx].events = events; - } +void ssh_poll_set_events(ssh_poll_handle p, short events) +{ + p->events = events; + if (p->ctx != NULL) { + if (p->lock_cnt == 0) { + p->ctx->pollfds[p->x.idx].events = events; + } else if (!(p->ctx->pollfds[p->x.idx].events & POLLOUT)) { + /* if locked, allow only setting POLLOUT to prevent recursive + * callbacks */ + p->ctx->pollfds[p->x.idx].events = events & POLLOUT; + } + } } /** @@ -424,12 +439,13 @@ void ssh_poll_set_events(ssh_poll_handle p, short events) { * @param p Pointer to an already allocated poll object. * @param fd New file descriptor. */ -void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd) { - if (p->ctx != NULL) { - p->ctx->pollfds[p->x.idx].fd = fd; - } else { - p->x.fd = fd; - } +void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd) +{ + if (p->ctx != NULL) { + p->ctx->pollfds[p->x.idx].fd = fd; + } else { + p->x.fd = fd; + } } /** @@ -439,8 +455,9 @@ void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd) { * @param p Pointer to an already allocated poll object. * @param events Poll events. */ -void ssh_poll_add_events(ssh_poll_handle p, short events) { - ssh_poll_set_events(p, ssh_poll_get_events(p) | events); +void ssh_poll_add_events(ssh_poll_handle p, short events) +{ + ssh_poll_set_events(p, ssh_poll_get_events(p) | events); } /** @@ -450,8 +467,9 @@ void ssh_poll_add_events(ssh_poll_handle p, short events) { * @param p Pointer to an already allocated poll object. * @param events Poll events. */ -void ssh_poll_remove_events(ssh_poll_handle p, short events) { - ssh_poll_set_events(p, ssh_poll_get_events(p) & ~events); +void ssh_poll_remove_events(ssh_poll_handle p, short events) +{ + ssh_poll_set_events(p, ssh_poll_get_events(p) & ~events); } /** @@ -462,12 +480,13 @@ void ssh_poll_remove_events(ssh_poll_handle p, short events) { * @return Raw socket. */ -socket_t ssh_poll_get_fd(ssh_poll_handle p) { - if (p->ctx != NULL) { - return p->ctx->pollfds[p->x.idx].fd; - } +socket_t ssh_poll_get_fd(ssh_poll_handle p) +{ + if (p->ctx != NULL) { + return p->ctx->pollfds[p->x.idx].fd; + } - return p->x.fd; + return p->x.fd; } /** * @brief Set the callback of a poll object. @@ -477,11 +496,12 @@ socket_t ssh_poll_get_fd(ssh_poll_handle p) { * @param userdata Userdata to be passed to the callback function. NULL if * not needed. */ -void ssh_poll_set_callback(ssh_poll_handle p, ssh_poll_callback cb, void *userdata) { - if (cb != NULL) { - p->cb = cb; - p->cb_data = userdata; - } +void ssh_poll_set_callback(ssh_poll_handle p, ssh_poll_callback cb, void *userdata) +{ + if (cb != NULL) { + p->cb = cb; + p->cb_data = userdata; + } } /** @@ -495,7 +515,8 @@ void ssh_poll_set_callback(ssh_poll_handle p, ssh_poll_callback cb, void *userda * for the next 5. Set it to 0 if you want to use the * library's default value. */ -ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size) { +ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size) +{ ssh_poll_ctx ctx; ctx = malloc(sizeof(struct ssh_poll_ctx_struct)); @@ -518,25 +539,27 @@ ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size) { * * @param ctx Pointer to an already allocated poll context. */ -void ssh_poll_ctx_free(ssh_poll_ctx ctx) { - if (ctx->polls_allocated > 0) { - while (ctx->polls_used > 0){ - ssh_poll_handle p = ctx->pollptrs[0]; - /* - * The free function calls ssh_poll_ctx_remove() and decrements - * ctx->polls_used - */ - ssh_poll_free(p); - } +void ssh_poll_ctx_free(ssh_poll_ctx ctx) +{ + if (ctx->polls_allocated > 0) { + while (ctx->polls_used > 0){ + ssh_poll_handle p = ctx->pollptrs[0]; + /* + * The free function calls ssh_poll_ctx_remove() and decrements + * ctx->polls_used + */ + ssh_poll_free(p); + } - SAFE_FREE(ctx->pollptrs); - SAFE_FREE(ctx->pollfds); - } + SAFE_FREE(ctx->pollptrs); + SAFE_FREE(ctx->pollfds); + } - SAFE_FREE(ctx); + SAFE_FREE(ctx); } -static int ssh_poll_ctx_resize(ssh_poll_ctx ctx, size_t new_size) { +static int ssh_poll_ctx_resize(ssh_poll_ctx ctx, size_t new_size) +{ ssh_poll_handle *pollptrs; ssh_pollfd_t *pollfds; @@ -570,7 +593,8 @@ static int ssh_poll_ctx_resize(ssh_poll_ctx ctx, size_t new_size) { * * @return 0 on success, < 0 on error */ -int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p) { +int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p) +{ socket_t fd; if (p->ctx != NULL) { @@ -604,7 +628,7 @@ int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p) { */ int ssh_poll_ctx_add_socket (ssh_poll_ctx ctx, ssh_socket s) { - ssh_poll_handle p; + ssh_poll_handle p = NULL; int ret; p = ssh_socket_get_poll_handle(s); @@ -622,7 +646,8 @@ int ssh_poll_ctx_add_socket (ssh_poll_ctx ctx, ssh_socket s) * @param ctx Pointer to an already allocated poll context. * @param p Pointer to an already allocated poll object. */ -void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p) { +void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p) +{ size_t i; i = p->x.idx; @@ -648,7 +673,7 @@ void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p) { * @brief Poll all the sockets associated through a poll object with a * poll context. If any of the events are set after the poll, the * call back function of the socket will be called. - * This function should be called once within the programs main loop. + * This function should be called once within the program's main loop. * * @param ctx Pointer to an already allocated poll context. * @param timeout An upper limit on the time for which ssh_poll_ctx() will @@ -657,7 +682,7 @@ void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p) { * the poll() function. * @returns SSH_OK No error. * SSH_ERROR Error happened during the poll. - * SSH_AGAIN Timeout occured + * SSH_AGAIN Timeout occurred */ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) @@ -673,6 +698,15 @@ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) return SSH_ERROR; } + /* Allow only POLLOUT events on locked sockets as that means we are called + * recursively and we only want process the POLLOUT events here to flush + * output buffer */ + for (i = 0; i < ctx->polls_used; i++) { + /* The lock allows only POLLOUT events: drop the rest */ + if (ctx->pollptrs[i]->lock_cnt > 0) { + ctx->pollfds[i].events &= POLLOUT; + } + } ssh_timestamp_init(&ts); do { int tm = ssh_timeout_update(&ts, timeout); @@ -688,17 +722,24 @@ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) used = ctx->polls_used; for (i = 0; i < used && rc > 0; ) { - if (!ctx->pollfds[i].revents || ctx->pollptrs[i]->lock) { + revents = ctx->pollfds[i].revents; + /* Do not pass any other events except for POLLOUT to callback when + * called recursively more than 2 times. On s390x the poll will be + * spammed with POLLHUP events causing infinite recursion when the user + * callback issues some write/flush/poll calls. */ + if (ctx->pollptrs[i]->lock_cnt > 2) { + revents &= POLLOUT; + } + if (revents == 0) { i++; } else { int ret; p = ctx->pollptrs[i]; fd = ctx->pollfds[i].fd; - revents = ctx->pollfds[i].revents; /* avoid having any event caught during callback */ ctx->pollfds[i].events = 0; - p->lock = 1; + p->lock_cnt++; if (p->cb && (ret = p->cb(p, fd, revents, p->cb_data)) < 0) { if (ret == -2) { return -1; @@ -709,7 +750,7 @@ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) } else { ctx->pollfds[i].revents = 0; ctx->pollfds[i].events = p->events; - p->lock = 0; + p->lock_cnt--; i++; } @@ -727,12 +768,13 @@ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) * @param session SSH session * @returns the default ssh_poll_ctx */ -ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session){ - if(session->default_poll_ctx != NULL) - return session->default_poll_ctx; - /* 2 is enough for the default one */ - session->default_poll_ctx = ssh_poll_ctx_new(2); - return session->default_poll_ctx; +ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session) +{ + if(session->default_poll_ctx != NULL) + return session->default_poll_ctx; + /* 2 is enough for the default one */ + session->default_poll_ctx = ssh_poll_ctx_new(2); + return session->default_poll_ctx; } /* public event API */ @@ -754,10 +796,11 @@ struct ssh_event_struct { * ssh_session objects and socket fd which are going to be polled at the * same time as the event context. You would need a single event context * per thread. - * + * * @return The ssh_event object on success, NULL on failure. */ -ssh_event ssh_event_new(void) { +ssh_event ssh_event_new(void) +{ ssh_event event; event = malloc(sizeof(struct ssh_event_struct)); @@ -784,12 +827,14 @@ ssh_event ssh_event_new(void) { return event; } -static int ssh_event_fd_wrapper_callback(ssh_poll_handle p, socket_t fd, int revents, - void *userdata) { +static int +ssh_event_fd_wrapper_callback(ssh_poll_handle p, socket_t fd, int revents, + void *userdata) +{ struct ssh_event_fd_wrapper *pw = (struct ssh_event_fd_wrapper *)userdata; (void)p; - if(pw->cb != NULL) { + if (pw->cb != NULL) { return pw->cb(fd, revents, pw->userdata); } return 0; @@ -812,11 +857,13 @@ static int ssh_event_fd_wrapper_callback(ssh_poll_handle p, socket_t fd, int rev * @returns SSH_OK on success * SSH_ERROR on failure */ -int ssh_event_add_fd(ssh_event event, socket_t fd, short events, - ssh_event_callback cb, void *userdata) { +int +ssh_event_add_fd(ssh_event event, socket_t fd, short events, + ssh_event_callback cb, void *userdata) +{ ssh_poll_handle p; struct ssh_event_fd_wrapper *pw; - + if(event == NULL || event->ctx == NULL || cb == NULL || fd == SSH_INVALID_SOCKET) { return SSH_ERROR; @@ -872,7 +919,7 @@ void ssh_event_remove_poll(ssh_event event, ssh_poll_handle p) } /** - * @brief remove the poll handle from session and assign them to a event, + * @brief remove the poll handle from session and assign them to an event, * when used in blocking mode. * * @param event The ssh_event object @@ -881,7 +928,8 @@ void ssh_event_remove_poll(ssh_event event, ssh_poll_handle p) * @returns SSH_OK on success * SSH_ERROR on failure */ -int ssh_event_add_session(ssh_event event, ssh_session session) { +int ssh_event_add_session(ssh_event event, ssh_session session) +{ ssh_poll_handle p; #ifdef WITH_SERVER struct ssh_iterator *iterator; @@ -933,16 +981,19 @@ int ssh_event_add_session(ssh_event event, ssh_session session) { * * @return SSH_ERROR in case of error */ -int ssh_event_add_connector(ssh_event event, ssh_connector connector){ +int ssh_event_add_connector(ssh_event event, ssh_connector connector) +{ return ssh_connector_set_event(connector, event); } /** - * @brief Poll all the sockets and sessions associated through an event object.i + * @brief Poll all the sockets and sessions associated through an event object. * * If any of the events are set after the poll, the call back functions of the * sessions or sockets will be called. * This function should be called once within the programs main loop. + * In case of failure, the errno should be consulted to find more information + * about the failure set by underlying poll imlpementation. * * @param event The ssh_event object to poll. * @@ -951,13 +1002,15 @@ int ssh_event_add_connector(ssh_event event, ssh_connector connector){ * means an infinite timeout. This parameter is passed to * the poll() function. * @returns SSH_OK on success. - * SSH_ERROR Error happened during the poll. - * SSH_AGAIN Timeout occured + * SSH_ERROR Error happened during the poll. Check errno to get more + * details about why it failed. + * SSH_AGAIN Timeout occurred */ -int ssh_event_dopoll(ssh_event event, int timeout) { +int ssh_event_dopoll(ssh_event event, int timeout) +{ int rc; - if(event == NULL || event->ctx == NULL) { + if (event == NULL || event->ctx == NULL) { return SSH_ERROR; } rc = ssh_poll_ctx_dopoll(event->ctx, timeout); @@ -973,7 +1026,8 @@ int ssh_event_dopoll(ssh_event event, int timeout) { * @returns SSH_OK on success * SSH_ERROR on failure */ -int ssh_event_remove_fd(ssh_event event, socket_t fd) { +int ssh_event_remove_fd(ssh_event event, socket_t fd) +{ register size_t i, used; int rc = SSH_ERROR; @@ -1019,7 +1073,8 @@ int ssh_event_remove_fd(ssh_event event, socket_t fd) { * @returns SSH_OK on success * SSH_ERROR on failure */ -int ssh_event_remove_session(ssh_event event, ssh_session session) { +int ssh_event_remove_session(ssh_event event, ssh_session session) +{ ssh_poll_handle p; register size_t i, used; int rc = SSH_ERROR; @@ -1027,14 +1082,14 @@ int ssh_event_remove_session(ssh_event event, ssh_session session) { struct ssh_iterator *iterator; #endif - if(event == NULL || event->ctx == NULL || session == NULL) { + if (event == NULL || event->ctx == NULL || session == NULL) { return SSH_ERROR; } used = event->ctx->polls_used; - for(i = 0; i < used; i++) { - p = event->ctx->pollptrs[i]; - if(p->session == session){ + for (i = 0; i < used; i++) { + p = event->ctx->pollptrs[i]; + if (p->session == session) { /* * ssh_poll_ctx_remove() decrements * event->ctx->polls_used @@ -1054,8 +1109,8 @@ int ssh_event_remove_session(ssh_event event, ssh_session session) { } #ifdef WITH_SERVER iterator = ssh_list_get_iterator(event->sessions); - while(iterator != NULL) { - if((ssh_session)iterator->data == session) { + while (iterator != NULL) { + if ((ssh_session)iterator->data == session) { ssh_list_remove(event->sessions, iterator); /* there should be only one instance of this session */ break; @@ -1073,7 +1128,8 @@ int ssh_event_remove_session(ssh_event event, ssh_session session) { * @return SSH_OK on success * @return SSH_ERROR on failure */ -int ssh_event_remove_connector(ssh_event event, ssh_connector connector){ +int ssh_event_remove_connector(ssh_event event, ssh_connector connector) +{ (void)event; return ssh_connector_remove_event(connector); } @@ -1091,13 +1147,13 @@ void ssh_event_free(ssh_event event) size_t used, i; ssh_poll_handle p; - if(event == NULL) { + if (event == NULL) { return; } if (event->ctx != NULL) { used = event->ctx->polls_used; - for(i = 0; i < used; i++) { + for (i = 0; i < used; i++) { p = event->ctx->pollptrs[i]; if (p->session != NULL) { ssh_poll_ctx_remove(event->ctx, p); @@ -1110,7 +1166,7 @@ void ssh_event_free(ssh_event event) ssh_poll_ctx_free(event->ctx); } #ifdef WITH_SERVER - if(event->sessions != NULL) { + if (event->sessions != NULL) { ssh_list_free(event->sessions); } #endif @@ -37,10 +37,14 @@ * * SCP protocol over SSH functions * + * @deprecated Please use SFTP instead + * * @{ */ /** + * @deprecated Please use SFTP instead + * * @brief Create a new scp session. * * @param[in] session The SSH session to use. @@ -62,7 +66,7 @@ ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location) { ssh_scp scp = NULL; - if (session == NULL) { + if (session == NULL || location == NULL) { goto error; } @@ -108,6 +112,8 @@ error: } /** + * @deprecated Please use SFTP instead + * * @brief Initialize the scp channel. * * @param[in] scp The scp context to initialize. @@ -140,7 +146,7 @@ int ssh_scp_init(ssh_scp scp) return SSH_ERROR; } - SSH_LOG(SSH_LOG_PROTOCOL, "Initializing scp session %s %son location '%s'", + SSH_LOG(SSH_LOG_DEBUG, "Initializing scp session %s %son location '%s'", scp->mode == SSH_SCP_WRITE?"write":"read", scp->recursive ? "recursive " : "", scp->location); @@ -230,6 +236,8 @@ int ssh_scp_init(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Close the scp channel. * * @param[in] scp The scp context to close. @@ -277,6 +285,8 @@ int ssh_scp_close(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Free a scp context. * * @param[in] scp The context to free. @@ -304,6 +314,8 @@ void ssh_scp_free(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Create a directory in a scp in sink mode. * * @param[in] scp The scp handle. @@ -313,7 +325,7 @@ void ssh_scp_free(ssh_scp scp) * @param[in] mode The UNIX permissions for the new directory, e.g. 0755. * * @returns SSH_OK if the directory has been created, SSH_ERROR if - * an error occured. + * an error occurred. * * @see ssh_scp_leave_directory() */ @@ -364,7 +376,7 @@ int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode) goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, + SSH_LOG(SSH_LOG_DEBUG, "SCP pushing directory %s with permissions '%s'", vis_encoded, perms); @@ -399,10 +411,12 @@ error: } /** + * @deprecated Please use SFTP instead + * * @brief Leave a directory. * * @returns SSH_OK if the directory has been left, SSH_ERROR if an - * error occured. + * error occurred. * * @see ssh_scp_push_directory() */ @@ -436,6 +450,8 @@ int ssh_scp_leave_directory(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Initialize the sending of a file to a scp in sink mode, using a 64-bit * size. * @@ -449,7 +465,7 @@ int ssh_scp_leave_directory(ssh_scp scp) * @param[in] mode The UNIX permissions for the new file, e.g. 0644. * * @returns SSH_OK if the file is ready to be sent, SSH_ERROR if an - * error occured. + * error occurred. * * @see ssh_scp_push_file() */ @@ -501,7 +517,7 @@ int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, goto error; } - SSH_LOG(SSH_LOG_PROTOCOL, + SSH_LOG(SSH_LOG_DEBUG, "SCP pushing file %s, size %" PRIu64 " with permissions '%s'", vis_encoded, size, perms); @@ -540,6 +556,8 @@ error: } /** + * @deprecated Please use SFTP instead + * * @brief Initialize the sending of a file to a scp in sink mode. * * @param[in] scp The scp handle. @@ -552,7 +570,7 @@ error: * @param[in] mode The UNIX permissions for the new file, e.g. 0644. * * @returns SSH_OK if the file is ready to be sent, SSH_ERROR if an - * error occured. + * error occurred. */ int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int mode) { @@ -562,6 +580,8 @@ int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int mode) /** * @internal * + * @deprecated Please use SFTP instead + * * @brief Wait for a response of the scp server. * * @param[in] scp The scp handle. @@ -569,7 +589,7 @@ int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int mode) * @param[out] response A pointer where the response message must be copied if * any. This pointer must then be free'd. * - * @returns The return code, SSH_ERROR a error occured. + * @returns The return code, SSH_ERROR a error occurred. */ int ssh_scp_response(ssh_scp scp, char **response) { @@ -628,6 +648,8 @@ int ssh_scp_response(ssh_scp scp, char **response) } /** + * @deprecated Please use SFTP instead + * * @brief Write into a remote scp file. * * @param[in] scp The scp handle. @@ -637,7 +659,7 @@ int ssh_scp_response(ssh_scp scp, char **response) * @param[in] len The number of bytes to write. * * @returns SSH_OK if the write was successful, SSH_ERROR an error - * occured while writing. + * occurred while writing. */ int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len) { @@ -671,7 +693,6 @@ int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len) scp->processed += w; } else { scp->state = SSH_SCP_ERROR; - //return = channel_get_exit_status(scp->channel); return SSH_ERROR; } @@ -702,6 +723,8 @@ int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len) } /** + * @deprecated Please use SFTP instead + * * @brief Read a string on a channel, terminated by '\n' * * @param[in] scp The scp handle. @@ -713,7 +736,7 @@ int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len) * null-terminated. * * @returns SSH_OK if the string was read, SSH_ERROR if an error - * occured while reading. + * occurred while reading. */ int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len) { @@ -748,6 +771,8 @@ int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len) } /** + * @deprecated Please use SFTP instead + * * @brief Wait for a scp request (file, directory). * * @returns SSH_SCP_REQUEST_NEWFILE: The other side is sending @@ -800,7 +825,7 @@ int ssh_scp_pull_request(ssh_scp scp) *p = '\0'; } - SSH_LOG(SSH_LOG_PROTOCOL, "Received SCP request: '%s'", buffer); + SSH_LOG(SSH_LOG_DEBUG, "Received SCP request: '%s'", buffer); switch(buffer[0]) { case 'C': /* File */ @@ -859,7 +884,7 @@ int ssh_scp_pull_request(ssh_scp scp) return SSH_ERROR; } - /* a parsing error occured */ + /* a parsing error occurred */ error: SAFE_FREE(name); SAFE_FREE(mode); @@ -869,6 +894,8 @@ error: } /** + * @deprecated Please use SFTP instead + * * @brief Deny the transfer of a file or creation of a directory coming from the * remote party. * @@ -915,6 +942,8 @@ int ssh_scp_deny_request(ssh_scp scp, const char *reason) } /** + * @deprecated Please use SFTP instead + * * @brief Accepts transfer of a file or creation of a directory coming from the * remote party. * @@ -951,14 +980,18 @@ int ssh_scp_accept_request(ssh_scp scp) return SSH_OK; } -/** @brief Read from a remote scp file +/** + * @deprecated Please use SFTP instead + * + * @brief Read from a remote scp file + * * @param[in] scp The scp handle. * * @param[in] buffer The destination buffer. * * @param[in] size The size of the buffer. * - * @returns The nNumber of bytes read, SSH_ERROR if an error occured + * @returns The number of bytes read, SSH_ERROR if an error occurred * while reading. */ int ssh_scp_read(ssh_scp scp, void *buffer, size_t size) @@ -1022,6 +1055,8 @@ int ssh_scp_read(ssh_scp scp, void *buffer, size_t size) } /** + * @deprecated Please use SFTP instead + * * @brief Get the name of the directory or file being pushed from the other * party. * @@ -1038,6 +1073,8 @@ const char *ssh_scp_request_get_filename(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Get the permissions of the directory or file being pushed from the * other party. * @@ -1052,7 +1089,10 @@ int ssh_scp_request_get_permissions(ssh_scp scp) return scp->request_mode; } -/** @brief Get the size of the file being pushed from the other party. +/** + * @deprecated Please use SFTP instead + * + * @brief Get the size of the file being pushed from the other party. * * @returns The numeric size of the file being read. * @warning The real size may not fit in a 32 bits field and may @@ -1067,7 +1107,10 @@ size_t ssh_scp_request_get_size(ssh_scp scp) return (size_t)scp->filelen; } -/** @brief Get the size of the file being pushed from the other party. +/** + * @deprecated Please use SFTP instead + * + * @brief Get the size of the file being pushed from the other party. * * @returns The numeric size of the file being read. */ @@ -1080,6 +1123,8 @@ uint64_t ssh_scp_request_get_size64(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Convert a scp text mode to an integer. * * @param[in] mode The mode to convert, e.g. "0644". @@ -1093,6 +1138,8 @@ int ssh_scp_integer_mode(const char *mode) } /** + * @deprecated Please use SFTP instead + * * @brief Convert a unix mode into a scp string. * * @param[in] mode The mode to convert, e.g. 420 or 0644. @@ -1108,6 +1155,8 @@ char *ssh_scp_string_mode(int mode) } /** + * @deprecated Please use SFTP instead + * * @brief Get the warning string from a scp handle. * * @param[in] scp The scp handle. diff --git a/src/server.c b/src/server.c index 841a1c42..28c3c015 100644 --- a/src/server.c +++ b/src/server.c @@ -92,7 +92,11 @@ int server_set_kex(ssh_session session) size_t len; int ok; - ZERO_STRUCTP(server); + /* Skip if already set, for example for the rekey or when we do the guessing + * it could have been already used to make some protocol decisions. */ + if (server->methods[0] != NULL) { + return SSH_OK; + } ok = ssh_get_random(server->cookie, 16, 0); if (!ok) { @@ -113,15 +117,6 @@ int server_set_kex(ssh_session session) ",%s", session->srv.ecdsa_key->type_c); } #endif -#ifdef HAVE_DSA - if (session->srv.dsa_key != NULL) { - len = strlen(hostkeys); - keytype = ssh_key_type(session->srv.dsa_key); - - snprintf(hostkeys + len, sizeof(hostkeys) - len, - ",%s", ssh_key_type_to_char(keytype)); - } -#endif if (session->srv.rsa_key != NULL) { /* We support also the SHA2 variants */ len = strlen(hostkeys); @@ -160,7 +155,8 @@ int server_set_kex(ssh_session session) rc = ssh_options_set_algo(session, SSH_HOSTKEYS, - kept); + kept, + &session->opts.wanted_methods[SSH_HOSTKEYS]); SAFE_FREE(kept); if (rc < 0) { return -1; @@ -191,7 +187,13 @@ int server_set_kex(ssh_session session) } } - return 0; + /* Do not append the extensions during rekey */ + if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) { + return SSH_OK; + } + + rc = ssh_kex_append_extensions(session, server); + return rc; } int ssh_server_init_kex(ssh_session session) { @@ -281,9 +283,6 @@ ssh_get_key_params(ssh_session session, int rc; switch(session->srv.hostkey) { - case SSH_KEYTYPE_DSS: - *privkey = session->srv.dsa_key; - break; case SSH_KEYTYPE_RSA: *privkey = session->srv.rsa_key; break; @@ -335,117 +334,127 @@ ssh_get_key_params(ssh_session session, * @brief A function to be called each time a step has been done in the * connection. */ -static void ssh_server_connection_callback(ssh_session session){ +static void ssh_server_connection_callback(ssh_session session) +{ int rc; - switch(session->session_state){ - case SSH_SESSION_STATE_NONE: - case SSH_SESSION_STATE_CONNECTING: - case SSH_SESSION_STATE_SOCKET_CONNECTED: - break; - case SSH_SESSION_STATE_BANNER_RECEIVED: - if (session->clientbanner == NULL) { + switch (session->session_state) { + case SSH_SESSION_STATE_NONE: + case SSH_SESSION_STATE_CONNECTING: + case SSH_SESSION_STATE_SOCKET_CONNECTED: + break; + case SSH_SESSION_STATE_BANNER_RECEIVED: + if (session->clientbanner == NULL) { + goto error; + } + set_status(session, 0.4f); + SSH_LOG(SSH_LOG_DEBUG, + "SSH client banner: %s", session->clientbanner); + + /* Here we analyze the different protocols the server allows. */ + rc = ssh_analyze_banner(session, 1); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "No version of SSH protocol usable (banner: %s)", + session->clientbanner); + goto error; + } + + /* from now, the packet layer is handling incoming packets */ + ssh_packet_register_socket_callback(session, session->socket); + + ssh_packet_set_default_callbacks(session); + set_status(session, 0.5f); + session->session_state = SSH_SESSION_STATE_INITIAL_KEX; + rc = ssh_send_kex(session); + if (rc < 0) { + goto error; + } + break; + case SSH_SESSION_STATE_INITIAL_KEX: + /* TODO: This state should disappear in favor of get_key handle */ + break; + case SSH_SESSION_STATE_KEXINIT_RECEIVED: + set_status(session, 0.6f); + if ((session->flags & SSH_SESSION_FLAG_KEXINIT_SENT) == 0) { + rc = server_set_kex(session); + if (rc == SSH_ERROR) { goto error; } - set_status(session, 0.4f); - SSH_LOG(SSH_LOG_PROTOCOL, - "SSH client banner: %s", session->clientbanner); - - /* Here we analyze the different protocols the server allows. */ - rc = ssh_analyze_banner(session, 1); + /* We are in a rekeying, so we need to send the server kex */ + rc = ssh_send_kex(session); if (rc < 0) { - ssh_set_error(session, SSH_FATAL, - "No version of SSH protocol usable (banner: %s)", - session->clientbanner); goto error; } + } + ssh_list_kex(&session->next_crypto->client_kex); // log client kex + rc = ssh_kex_select_methods(session); + if (rc < 0) { + goto error; + } + rc = crypt_set_algorithms_server(session); + if (rc == SSH_ERROR) { + goto error; + } + set_status(session, 0.8f); + session->session_state = SSH_SESSION_STATE_DH; + break; + case SSH_SESSION_STATE_DH: + if (session->dh_handshake_state == DH_STATE_FINISHED) { - /* from now, the packet layer is handling incoming packets */ - session->socket_callbacks.data=ssh_packet_socket_callback; - ssh_packet_register_socket_callback(session, session->socket); - - ssh_packet_set_default_callbacks(session); - set_status(session, 0.5f); - session->session_state=SSH_SESSION_STATE_INITIAL_KEX; - if (ssh_send_kex(session, 1) < 0) { - goto error; - } - break; - case SSH_SESSION_STATE_INITIAL_KEX: - /* TODO: This state should disappear in favor of get_key handle */ - break; - case SSH_SESSION_STATE_KEXINIT_RECEIVED: - set_status(session,0.6f); - if(session->next_crypto->server_kex.methods[0]==NULL){ - if(server_set_kex(session) == SSH_ERROR) - goto error; - /* We are in a rekeying, so we need to send the server kex */ - if(ssh_send_kex(session, 1) < 0) - goto error; - } - ssh_list_kex(&session->next_crypto->client_kex); // log client kex - if (ssh_kex_select_methods(session) < 0) { + rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); + if (rc != SSH_OK) { goto error; } - if (crypt_set_algorithms_server(session) == SSH_ERROR) - goto error; - set_status(session,0.8f); - session->session_state=SSH_SESSION_STATE_DH; - break; - case SSH_SESSION_STATE_DH: - if(session->dh_handshake_state==DH_STATE_FINISHED){ - - rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); - if (rc != SSH_OK) { - goto error; - } + /* + * If the client supports extension negotiation, we will send + * our supported extensions now. This is the first message after + * sending NEWKEYS message and after turning on crypto. + */ + if (session->extensions & SSH_EXT_NEGOTIATION && + session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { /* - * If the client supports extension negotiation, we will send - * our supported extensions now. This is the first message after - * sending NEWKEYS message and after turning on crypto. + * Only send an SSH_MSG_EXT_INFO message the first time the + * client undergoes NEWKEYS. It is unexpected for this message + * to be sent upon rekey, and may cause clients to log error + * messages. + * + * The session_state can not be used for this purpose because it + * is re-set to SSH_SESSION_STATE_KEXINIT_RECEIVED during rekey. + * So, use the connected flag which transitions from non-zero + * below. + * + * See also: + * - https://bugzilla.mindrot.org/show_bug.cgi?id=2929 */ - if (session->extensions & SSH_EXT_NEGOTIATION && - session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { - - /* - * Only send an SSH_MSG_EXT_INFO message the first time the client - * undergoes NEWKEYS. It is unexpected for this message to be sent - * upon rekey, and may cause clients to log error messages. - * - * The session_state can not be used for this purpose because it is - * re-set to SSH_SESSION_STATE_KEXINIT_RECEIVED during rekey. So, - * use the connected flag which transitions from non-zero below. - * - * See also: - * - https://bugzilla.mindrot.org/show_bug.cgi?id=2929 - */ - if (session->connected == 0) { - ssh_server_send_extensions(session); - } + if (session->connected == 0) { + ssh_server_send_extensions(session); } + } - set_status(session,1.0f); - session->connected = 1; - session->session_state=SSH_SESSION_STATE_AUTHENTICATING; - if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) - session->session_state = SSH_SESSION_STATE_AUTHENTICATED; + set_status(session, 1.0f); + session->connected = 1; + session->session_state = SSH_SESSION_STATE_AUTHENTICATING; + if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) + session->session_state = SSH_SESSION_STATE_AUTHENTICATED; - } - break; - case SSH_SESSION_STATE_AUTHENTICATING: - break; - case SSH_SESSION_STATE_ERROR: - goto error; - default: - ssh_set_error(session,SSH_FATAL,"Invalid state %d",session->session_state); + } + break; + case SSH_SESSION_STATE_AUTHENTICATING: + break; + case SSH_SESSION_STATE_ERROR: + goto error; + default: + ssh_set_error(session, SSH_FATAL, "Invalid state %d", + session->session_state); } return; error: ssh_socket_close(session->socket); session->alive = 0; - session->session_state=SSH_SESSION_STATE_ERROR; + session->session_state = SSH_SESSION_STATE_ERROR; } /** @@ -459,16 +468,17 @@ error: * @param user is a pointer to session * @returns Number of bytes processed, or zero if the banner is not complete. */ -static int callback_receive_banner(const void *data, size_t len, void *user) { - char *buffer = (char *) data; - ssh_session session = (ssh_session) user; +static size_t callback_receive_banner(const void *data, size_t len, void *user) +{ + char *buffer = (char *)data; + ssh_session session = (ssh_session)user; char *str = NULL; size_t i; - int ret=0; + size_t processed = 0; for (i = 0; i < len; i++) { #ifdef WITH_PCAP - if(session->pcap_ctx && buffer[i] == '\n') { + if (session->pcap_ctx && buffer[i] == '\n') { ssh_pcap_context_write(session->pcap_ctx, SSH_PCAP_DIR_IN, buffer, @@ -477,33 +487,34 @@ static int callback_receive_banner(const void *data, size_t len, void *user) { } #endif if (buffer[i] == '\r') { - buffer[i]='\0'; + buffer[i] = '\0'; } if (buffer[i] == '\n') { - buffer[i]='\0'; + buffer[i] = '\0'; str = strdup(buffer); /* number of bytes read */ - ret = i + 1; + processed = i + 1; session->clientbanner = str; session->session_state = SSH_SESSION_STATE_BANNER_RECEIVED; SSH_LOG(SSH_LOG_PACKET, "Received banner: %s", str); session->ssh_connection_callback(session); - return ret; + return processed; } - if(i > 127) { + if (i > 127) { /* Too big banner */ session->session_state = SSH_SESSION_STATE_ERROR; - ssh_set_error(session, SSH_FATAL, "Receiving banner: too large banner"); + ssh_set_error(session, SSH_FATAL, + "Receiving banner: too large banner"); return 0; } } - return ret; + return processed; } /* returns 0 until the key exchange is not finished */ @@ -524,11 +535,40 @@ void ssh_set_auth_methods(ssh_session session, int auth_methods) session->auth.supported_methods = (uint32_t)auth_methods & 0x3fU; } +int ssh_send_issue_banner(ssh_session session, const ssh_string banner) +{ + int rc = SSH_ERROR; + + if (session == NULL) { + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sending a server issue banner"); + + rc = ssh_buffer_pack(session->out_buffer, + "bSs", + SSH2_MSG_USERAUTH_BANNER, + banner, + ""); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + rc = ssh_packet_send(session); + return rc; +} + /* Do the banner and key exchange */ -int ssh_handle_key_exchange(ssh_session session) { +int ssh_handle_key_exchange(ssh_session session) +{ int rc; - if (session->session_state != SSH_SESSION_STATE_NONE) - goto pending; + + if (session->session_state != SSH_SESSION_STATE_NONE) { + goto pending; + } + rc = ssh_send_banner(session, 1); if (rc < 0) { return SSH_ERROR; @@ -539,27 +579,28 @@ int ssh_handle_key_exchange(ssh_session session) { session->ssh_connection_callback = ssh_server_connection_callback; session->session_state = SSH_SESSION_STATE_SOCKET_CONNECTED; ssh_socket_set_callbacks(session->socket,&session->socket_callbacks); - session->socket_callbacks.data=callback_receive_banner; - session->socket_callbacks.exception=ssh_socket_exception_callback; - session->socket_callbacks.userdata=session; + session->socket_callbacks.data = callback_receive_banner; + session->socket_callbacks.exception = ssh_socket_exception_callback; + session->socket_callbacks.userdata = session; rc = server_set_kex(session); if (rc < 0) { return SSH_ERROR; } - pending: +pending: rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, - ssh_server_kex_termination,session); + ssh_server_kex_termination,session); SSH_LOG(SSH_LOG_PACKET, "ssh_handle_key_exchange: current state : %d", - session->session_state); - if (rc != SSH_OK) - return rc; + session->session_state); + if (rc != SSH_OK) { + return rc; + } if (session->session_state == SSH_SESSION_STATE_ERROR || session->session_state == SSH_SESSION_STATE_DISCONNECTED) { - return SSH_ERROR; + return SSH_ERROR; } - return SSH_OK; + return SSH_OK; } /* messages */ @@ -648,7 +689,7 @@ static int ssh_message_channel_request_reply_default(ssh_message msg) { channel = msg->channel_request.channel->remote_channel; SSH_LOG(SSH_LOG_PACKET, - "Sending a default channel_request denied to channel %d", channel); + "Sending a default channel_request denied to channel %" PRIu32, channel); rc = ssh_buffer_pack(msg->session->out_buffer, "bd", @@ -672,6 +713,13 @@ static int ssh_message_service_request_reply_default(ssh_message msg) { return ssh_message_service_reply_success(msg); } +/** + * @brief Sends SERVICE_ACCEPT to the client + * + * @param msg The message to reply to + * + * @returns SSH_OK when success otherwise SSH_ERROR + */ int ssh_message_service_reply_success(ssh_message msg) { ssh_session session; int rc; @@ -696,6 +744,15 @@ int ssh_message_service_reply_success(ssh_message msg) { return rc; } +/** + * @brief Send a global request success message + * + * @param msg The message + * + * @param bound_port The remote bind port + * + * @returns SSH_OK on success, otherwise SSH_ERROR + */ int ssh_message_global_request_reply_success(ssh_message msg, uint16_t bound_port) { int rc; @@ -707,7 +764,7 @@ int ssh_message_global_request_reply_success(ssh_message msg, uint16_t bound_por goto error; } - if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD + if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD && msg->global_request.bind_port == 0) { rc = ssh_buffer_pack(msg->session->out_buffer, "d", bound_port); if (rc != SSH_OK) { @@ -719,7 +776,7 @@ int ssh_message_global_request_reply_success(ssh_message msg, uint16_t bound_por return ssh_packet_send(msg->session); } - if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD + if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD && msg->global_request.bind_port == 0) { SSH_LOG(SSH_LOG_PACKET, "The client doesn't want to know the remote port!"); @@ -774,6 +831,13 @@ int ssh_message_reply_default(ssh_message msg) { return -1; } +/** + * @brief Gets the service name from the service request message + * + * @param msg The service request message + * + * @returns the service name from the message + */ const char *ssh_message_service_service(ssh_message msg){ if (msg == NULL) { return NULL; @@ -805,7 +869,6 @@ ssh_key ssh_message_auth_pubkey(ssh_message msg) { return msg->auth_request.pubkey; } -/* Get the publickey of an auth request */ ssh_public_key ssh_message_auth_publickey(ssh_message msg){ if (msg == NULL) { return NULL; @@ -821,6 +884,13 @@ enum ssh_publickey_state_e ssh_message_auth_publickey_state(ssh_message msg){ return msg->auth_request.signature_state; } +/** + * @brief Check if the message is a keyboard-interactive response + * + * @param msg The message to check + * + * @returns 1 if the message is a response, otherwise 0 + */ int ssh_message_auth_kbdint_is_response(ssh_message msg) { if (msg == NULL) { return -1; @@ -830,6 +900,17 @@ int ssh_message_auth_kbdint_is_response(ssh_message msg) { } /* FIXME: methods should be unsigned */ +/** + * @brief Sets the supported authentication methods to a message + * + * @param msg The message + * + * @param methods Methods to set to the message. + * The supported methods are listed in ssh_set_auth_methods + * @see ssh_set_auth_methods + * + * @returns 0 on success, otherwise -1 + */ int ssh_message_auth_set_methods(ssh_message msg, int methods) { if (msg == NULL || msg->session == NULL) { return -1; @@ -884,9 +965,8 @@ int ssh_message_auth_interactive_request(ssh_message msg, const char *name, /* fill in the kbdint structure */ if (msg->session->kbdint == NULL) { - SSH_LOG(SSH_LOG_PROTOCOL, "Warning: Got a " - "keyboard-interactive response but it " - "seems we didn't send the request."); + SSH_LOG(SSH_LOG_DEBUG, "Warning: Got a keyboard-interactive response " + "but it seems we didn't send the request."); msg->session->kbdint = ssh_kbdint_new(); if (msg->session->kbdint == NULL) { @@ -950,6 +1030,17 @@ int ssh_message_auth_interactive_request(ssh_message msg, const char *name, return rc; } +/** + * @brief Sends SSH2_MSG_USERAUTH_SUCCESS or SSH2_MSG_USERAUTH_FAILURE message + * depending on the success of the authentication method + * + * @param session The session to reply to + * + * @param partial Denotes if the authentication process was partially completed + * (unsuccessful) + * + * @returns SSH_OK on success, otherwise SSH_ERROR + */ int ssh_auth_reply_success(ssh_session session, int partial) { struct ssh_crypto_struct *crypto = NULL; @@ -981,13 +1072,13 @@ int ssh_auth_reply_success(ssh_session session, int partial) crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); if (crypto != NULL && crypto->delayed_compress_out) { - SSH_LOG(SSH_LOG_PROTOCOL, "Enabling delayed compression OUT"); + SSH_LOG(SSH_LOG_DEBUG, "Enabling delayed compression OUT"); crypto->do_compress_out = 1; } crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); if (crypto != NULL && crypto->delayed_compress_in) { - SSH_LOG(SSH_LOG_PROTOCOL, "Enabling delayed compression IN"); + SSH_LOG(SSH_LOG_DEBUG, "Enabling delayed compression IN"); crypto->do_compress_in = 1; } return r; @@ -999,7 +1090,17 @@ int ssh_message_auth_reply_success(ssh_message msg, int partial) { return ssh_auth_reply_success(msg->session, partial); } -/* Answer OK to a pubkey auth request */ +/** + * @brief Answer SSH2_MSG_USERAUTH_PK_OK to a pubkey authentication request + * + * @param msg The message + * + * @param algo The algorithm of the accepted public key + * + * @param pubkey The accepted public key + * + * @returns SSH_OK on success, otherwise SSH_ERROR + */ int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pubkey) { int rc; if (msg == NULL) { @@ -1020,12 +1121,19 @@ int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pu return rc; } +/** + * @brief Answer SSH2_MSG_USERAUTH_PK_OK to a pubkey authentication request + * + * @param msg The message + * + * @returns SSH_OK on success, otherwise SSH_ERROR + */ int ssh_message_auth_reply_pk_ok_simple(ssh_message msg) { ssh_string algo; ssh_string pubkey_blob = NULL; int ret; - algo = ssh_string_from_char(msg->auth_request.pubkey->type_c); + algo = ssh_string_from_char(msg->auth_request.sigtype); if (algo == NULL) { return SSH_ERROR; } @@ -1170,6 +1278,13 @@ int ssh_execute_message_callbacks(ssh_session session){ return SSH_OK; } +/** + * @brief Sends a keepalive message to the session + * + * @param session The session to send the message to + * + * @returns SSH_OK + */ int ssh_send_keepalive(ssh_session session) { /* Client denies the request, so the error code is not meaningful */ diff --git a/src/session.c b/src/session.c index b5ffcbc2..279352b6 100644 --- a/src/session.c +++ b/src/session.c @@ -43,7 +43,7 @@ #define FIRST_CHANNEL 42 // why not ? it helps to find bugs. /** - * @defgroup libssh_session The SSH session functions. + * @defgroup libssh_session The SSH session functions * @ingroup libssh * * Functions that manage a session. @@ -97,36 +97,53 @@ ssh_session ssh_new(void) ssh_set_blocking(session, 1); session->maxchannel = FIRST_CHANNEL; -#ifndef _WIN32 session->agent = ssh_agent_new(session); if (session->agent == NULL) { goto err; } -#endif /* _WIN32 */ /* OPTIONS */ session->opts.StrictHostKeyChecking = 1; - session->opts.port = 0; + session->opts.port = 22; session->opts.fd = -1; session->opts.compressionlevel = 7; session->opts.nodelay = 0; + session->opts.identities_only = false; + session->opts.control_master = SSH_CONTROL_MASTER_NO; session->opts.flags = SSH_OPT_FLAG_PASSWORD_AUTH | SSH_OPT_FLAG_PUBKEY_AUTH | SSH_OPT_FLAG_KBDINT_AUTH | SSH_OPT_FLAG_GSSAPI_AUTH; + session->opts.exp_flags = 0; + session->opts.identity = ssh_list_new(); if (session->opts.identity == NULL) { goto err; } + session->opts.identity_non_exp = ssh_list_new(); + if (session->opts.identity_non_exp == NULL) { + goto err; + } + + session->opts.certificate = ssh_list_new(); + if (session->opts.certificate == NULL) { + goto err; + } + session->opts.certificate_non_exp = ssh_list_new(); + if (session->opts.certificate_non_exp == NULL) { + goto err; + } + /* the default certificates are loaded automatically from the default + * identities later */ id = strdup("%d/id_ed25519"); if (id == NULL) { goto err; } - rc = ssh_list_append(session->opts.identity, id); + rc = ssh_list_append(session->opts.identity_non_exp, id); if (rc == SSH_ERROR) { goto err; } @@ -136,7 +153,7 @@ ssh_session ssh_new(void) if (id == NULL) { goto err; } - rc = ssh_list_append(session->opts.identity, id); + rc = ssh_list_append(session->opts.identity_non_exp, id); if (rc == SSH_ERROR) { goto err; } @@ -146,22 +163,11 @@ ssh_session ssh_new(void) if (id == NULL) { goto err; } - rc = ssh_list_append(session->opts.identity, id); + rc = ssh_list_append(session->opts.identity_non_exp, id); if (rc == SSH_ERROR) { goto err; } -#ifdef HAVE_DSA - id = strdup("%d/id_dsa"); - if (id == NULL) { - goto err; - } - rc = ssh_list_append(session->opts.identity, id); - if (rc == SSH_ERROR) { - goto err; - } -#endif - /* Explicitly initialize states */ session->session_state = SSH_SESSION_STATE_NONE; session->pending_call_state = SSH_PENDING_CALL_NONE; @@ -242,12 +248,8 @@ void ssh_free(ssh_session session) crypto_free(session->current_crypto); crypto_free(session->next_crypto); -#ifndef _WIN32 ssh_agent_free(session->agent); -#endif /* _WIN32 */ - ssh_key_free(session->srv.dsa_key); - session->srv.dsa_key = NULL; ssh_key_free(session->srv.rsa_key); session->srv.rsa_key = NULL; ssh_key_free(session->srv.ecdsa_key); @@ -286,22 +288,56 @@ void ssh_free(ssh_session session) ssh_list_free(session->opts.identity); } + if (session->opts.identity_non_exp) { + char *id; + + for (id = ssh_list_pop_head(char *, session->opts.identity_non_exp); + id != NULL; + id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) { + SAFE_FREE(id); + } + ssh_list_free(session->opts.identity_non_exp); + } + + if (session->opts.certificate) { + char *cert = NULL; + + for (cert = ssh_list_pop_head(char *, session->opts.certificate); + cert != NULL; + cert = ssh_list_pop_head(char *, session->opts.certificate)) { + SAFE_FREE(cert); + } + ssh_list_free(session->opts.certificate); + } + + if (session->opts.certificate_non_exp) { + char *cert = NULL; + + for (cert = ssh_list_pop_head(char *, session->opts.certificate_non_exp); + cert != NULL; + cert = ssh_list_pop_head(char *, session->opts.certificate_non_exp)) { + SAFE_FREE(cert); + } + ssh_list_free(session->opts.certificate_non_exp); + } + while ((b = ssh_list_pop_head(struct ssh_buffer_struct *, session->out_queue)) != NULL) { SSH_BUFFER_FREE(b); } ssh_list_free(session->out_queue); -#ifndef _WIN32 - ssh_agent_state_free (session->agent_state); -#endif + ssh_agent_state_free(session->agent_state); session->agent_state = NULL; SAFE_FREE(session->auth.auto_state); SAFE_FREE(session->serverbanner); SAFE_FREE(session->clientbanner); SAFE_FREE(session->banner); + SAFE_FREE(session->disconnect_message); + SAFE_FREE(session->peer_discon_msg); + SAFE_FREE(session->opts.agent_socket); SAFE_FREE(session->opts.bindaddr); SAFE_FREE(session->opts.custombanner); SAFE_FREE(session->opts.moduli_file); @@ -314,6 +350,7 @@ void ssh_free(ssh_session session) SAFE_FREE(session->opts.gss_server_identity); SAFE_FREE(session->opts.gss_client_identity); SAFE_FREE(session->opts.pubkey_accepted_types); + SAFE_FREE(session->opts.control_path); for (i = 0; i < SSH_KEX_METHODS; i++) { if (session->opts.wanted_methods[i]) { @@ -459,20 +496,36 @@ const char* ssh_get_hmac_out(ssh_session session) { } /** + * @internal + * @brief Close the connection socket if it is a socket created by us. + * Does not close the sockets provided by the user through options API. + */ +void +ssh_session_socket_close(ssh_session session) +{ + if (session->opts.fd == SSH_INVALID_SOCKET) { + ssh_socket_close(session->socket); + } + session->alive = 0; + session->session_state = SSH_SESSION_STATE_ERROR; +} + +/** * @brief Disconnect impolitely from a remote host by closing the socket. * * Suitable if you forked and want to destroy this session. * * @param[in] session The SSH session to disconnect. */ -void ssh_silent_disconnect(ssh_session session) { - if (session == NULL) { - return; - } +void +ssh_silent_disconnect(ssh_session session) +{ + if (session == NULL) { + return; + } - ssh_socket_close(session->socket); - session->alive = 0; - ssh_disconnect(session); + ssh_session_socket_close(session); + ssh_disconnect(session); } /** @@ -633,9 +686,10 @@ void ssh_set_fd_except(ssh_session session) { * * @return SSH_OK on success, SSH_ERROR otherwise. */ -int ssh_handle_packets(ssh_session session, int timeout) { - ssh_poll_handle spoll; - ssh_poll_ctx ctx; +int ssh_handle_packets(ssh_session session, int timeout) +{ + ssh_poll_handle spoll = NULL; + ssh_poll_ctx ctx = NULL; int tm = timeout; int rc; @@ -644,20 +698,29 @@ int ssh_handle_packets(ssh_session session, int timeout) { } spoll = ssh_socket_get_poll_handle(session->socket); + if (spoll == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } ssh_poll_add_events(spoll, POLLIN); ctx = ssh_poll_get_ctx(spoll); - if (!ctx) { + if (ctx == NULL) { ctx = ssh_poll_get_default_ctx(session); + if (ctx == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } ssh_poll_ctx_add(ctx, spoll); } if (timeout == SSH_TIMEOUT_USER) { - if (ssh_is_blocking(session)) - tm = ssh_make_milliseconds(session->opts.timeout, - session->opts.timeout_usec); - else - tm = 0; + if (ssh_is_blocking(session)) { + tm = ssh_make_milliseconds(session->opts.timeout, + session->opts.timeout_usec); + } else { + tm = 0; + } } rc = ssh_poll_ctx_dopoll(ctx, tm); if (rc == SSH_ERROR) { @@ -680,7 +743,7 @@ int ssh_handle_packets(ssh_session session, int timeout) { * @param[in] timeout Set an upper limit on the time for which this function * will block, in milliseconds. Specifying * SSH_TIMEOUT_INFINITE (-1) means an infinite timeout. - * Specifying SSH_TIMEOUT_USER means to use the timeout + * Specifying SSH_TIMEOUT_USER means using the timeout * specified in options. 0 means poll will return * immediately. * SSH_TIMEOUT_DEFAULT uses the session timeout if set or @@ -694,13 +757,13 @@ int ssh_handle_packets(ssh_session session, int timeout) { * SSH_ERROR otherwise. */ int ssh_handle_packets_termination(ssh_session session, - long timeout, + int timeout, ssh_termination_function fct, void *user) { struct ssh_timestamp ts; - long timeout_ms = SSH_TIMEOUT_INFINITE; - long tm; + int timeout_ms = SSH_TIMEOUT_INFINITE; + int tm; int ret = SSH_OK; /* If a timeout has been provided, use it */ @@ -819,11 +882,11 @@ const char *ssh_get_disconnect_message(ssh_session session) { if (session->session_state != SSH_SESSION_STATE_DISCONNECTED) { ssh_set_error(session, SSH_REQUEST_DENIED, "Connection not closed yet"); - } else if(!session->discon_msg) { + } else if(!session->peer_discon_msg) { ssh_set_error(session, SSH_FATAL, "Connection correctly closed but no disconnect message"); } else { - return session->discon_msg; + return session->peer_discon_msg; } return NULL; @@ -857,7 +920,9 @@ void ssh_socket_exception_callback(int code, int errno_code, void *user){ if (errno_code == 0 && code == SSH_SOCKET_EXCEPTION_EOF) { ssh_set_error(session, SSH_FATAL, "Socket error: disconnected"); } else { - ssh_set_error(session, SSH_FATAL, "Socket error: %s", strerror(errno_code)); + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(session, SSH_FATAL, "Socket error: %s", + ssh_strerror(errno_code, err_msg, SSH_ERRNO_MSG_MAX)); } session->ssh_connection_callback(session); @@ -934,7 +999,7 @@ error: /** * @brief Set the session data counters. * - * This functions sets the counter structures to be used to calculate data + * This function sets the counter structures to be used to calculate data * which comes in and goes out through the session at different levels. * * @code @@ -977,8 +1042,8 @@ int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) { ssh_key pubkey = NULL; ssh_string pubkey_blob = NULL; - MD5CTX ctx; - unsigned char *h; + MD5CTX ctx = NULL; + unsigned char *h = NULL; int rc; if (session == NULL || hash == NULL) { @@ -998,40 +1063,50 @@ int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) *hash = NULL; if (session->current_crypto == NULL || session->current_crypto->server_pubkey == NULL) { - ssh_set_error(session,SSH_FATAL,"No current cryptographic context"); + ssh_set_error(session, SSH_FATAL, "No current cryptographic context"); + return SSH_ERROR; + } + + rc = ssh_get_server_publickey(session, &pubkey); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); + ssh_key_free(pubkey); + if (rc != SSH_OK) { return SSH_ERROR; } h = calloc(MD5_DIGEST_LEN, sizeof(unsigned char)); if (h == NULL) { + SSH_STRING_FREE(pubkey_blob); return SSH_ERROR; } ctx = md5_init(); if (ctx == NULL) { + SSH_STRING_FREE(pubkey_blob); SAFE_FREE(h); return SSH_ERROR; } - rc = ssh_get_server_publickey(session, &pubkey); + rc = md5_update(ctx, + ssh_string_data(pubkey_blob), + ssh_string_len(pubkey_blob)); if (rc != SSH_OK) { - md5_final(h, ctx); + SSH_STRING_FREE(pubkey_blob); + md5_ctx_free(ctx); SAFE_FREE(h); - return SSH_ERROR; + return rc; } - - rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); - ssh_key_free(pubkey); + SSH_STRING_FREE(pubkey_blob); + rc = md5_final(h, ctx); if (rc != SSH_OK) { - md5_final(h, ctx); SAFE_FREE(h); - return SSH_ERROR; + return rc; } - md5_update(ctx, ssh_string_data(pubkey_blob), ssh_string_len(pubkey_blob)); - SSH_STRING_FREE(pubkey_blob); - md5_final(h, ctx); - *hash = h; return MD5_DIGEST_LEN; @@ -1040,14 +1115,15 @@ int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) /** * @brief Deallocate the hash obtained by ssh_get_pubkey_hash. * - * This is required under Microsoft platform as this library might use a + * This is required under Microsoft platform as this library might use a * different C library than your software, hence a different heap. * * @param[in] hash The buffer to deallocate. * * @see ssh_get_pubkey_hash() */ -void ssh_clean_pubkey_hash(unsigned char **hash) { +void ssh_clean_pubkey_hash(unsigned char **hash) +{ SAFE_FREE(*hash); } @@ -1057,9 +1133,9 @@ void ssh_clean_pubkey_hash(unsigned char **hash) { * @param[in] session The session to get the key from. * * @param[out] key A pointer to store the allocated key. You need to free - * the key. + * the key using ssh_key_free(). * - * @return SSH_OK on success, SSH_ERROR on errror. + * @return SSH_OK on success, SSH_ERROR on error. * * @see ssh_key_free() */ @@ -1101,15 +1177,15 @@ int ssh_get_publickey(ssh_session session, ssh_key *key) * * @param[in] type The type of the hash you want. * - * @param[in] hash A pointer to store the allocated buffer. It can be + * @param[out] hash A pointer to store the allocated buffer. It can be * freed using ssh_clean_pubkey_hash(). * * @param[in] hlen The length of the hash. * - * @return 0 on success, -1 if an error occured. + * @return 0 on success, -1 if an error occurred. * * @warning It is very important that you verify at some moment that the hash - * matches a known server. If you don't do it, cryptography wont help + * matches a known server. If you don't do it, cryptography won't help * you at making things secure. * OpenSSH uses SHA256 to print public key digests. * @@ -1124,7 +1200,7 @@ int ssh_get_publickey_hash(const ssh_key key, size_t *hlen) { ssh_string blob; - unsigned char *h; + unsigned char *h = NULL; int rc; rc = ssh_pki_export_pubkey_blob(key, &blob); @@ -1150,8 +1226,17 @@ int ssh_get_publickey_hash(const ssh_key key, goto out; } - sha1_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); - sha1_final(h, ctx); + rc = sha1_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); + if (rc != SSH_OK) { + free(h); + sha1_ctx_free(ctx); + goto out; + } + rc = sha1_final(h, ctx); + if (rc != SSH_OK) { + free(h); + goto out; + } *hlen = SHA_DIGEST_LEN; } @@ -1173,8 +1258,17 @@ int ssh_get_publickey_hash(const ssh_key key, goto out; } - sha256_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); - sha256_final(h, ctx); + rc = sha256_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); + if (rc != SSH_OK) { + free(h); + sha256_ctx_free(ctx); + goto out; + } + rc = sha256_final(h, ctx); + if (rc != SSH_OK) { + free(h); + goto out; + } *hlen = SHA256_DIGEST_LEN; } @@ -1185,8 +1279,8 @@ int ssh_get_publickey_hash(const ssh_key key, /* In FIPS mode, we cannot use MD5 */ if (ssh_fips_mode()) { - SSH_LOG(SSH_LOG_WARN, "In FIPS mode MD5 is not allowed." - "Try using SSH_PUBLICKEY_HASH_SHA256"); + SSH_LOG(SSH_LOG_TRACE, "In FIPS mode MD5 is not allowed." + "Try using SSH_PUBLICKEY_HASH_SHA256"); rc = SSH_ERROR; goto out; } @@ -1204,8 +1298,17 @@ int ssh_get_publickey_hash(const ssh_key key, goto out; } - md5_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); - md5_final(h, ctx); + rc = md5_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); + if (rc != SSH_OK) { + free(h); + md5_ctx_free(ctx); + goto out; + } + rc = md5_final(h, ctx); + if (rc != SSH_OK) { + free(h); + goto out; + } *hlen = MD5_DIGEST_LEN; } @@ -56,21 +56,12 @@ #ifdef WITH_SFTP -/* Buffer size maximum is 256M */ -#define SFTP_PACKET_SIZE_MAX 0x10000000 - 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; @@ -266,66 +257,38 @@ error: return NULL; } -int sftp_server_init(sftp_session sftp){ - ssh_session session = sftp->session; - sftp_packet packet = NULL; - ssh_buffer reply = NULL; - uint32_t version; - int rc; - - packet = sftp_packet_read(sftp); - if (packet == NULL) { - return -1; - } - - if (packet->type != SSH_FXP_INIT) { - ssh_set_error(session, SSH_FATAL, - "Packet read of type %d instead of SSH_FXP_INIT", - packet->type); - - return -1; - } - - SSH_LOG(SSH_LOG_PACKET, "Received SSH_FXP_INIT"); - - ssh_buffer_get_u32(packet->payload, &version); - version = ntohl(version); - SSH_LOG(SSH_LOG_PACKET, "Client version: %d", version); - sftp->client_version = (int)version; - - reply = ssh_buffer_new(); - if (reply == NULL) { - ssh_set_error_oom(session); - return -1; - } +/* @deprecated in favor of sftp_server_new() and callbacks based sftp server */ +int sftp_server_init(sftp_session sftp) +{ + ssh_session session = sftp->session; + sftp_client_message msg = NULL; + int rc; - rc = ssh_buffer_pack(reply, "dssss", - LIBSFTP_VERSION, - "posix-rename@openssh.com", - "1", - "hardlink@openssh.com", - "1"); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - SSH_BUFFER_FREE(reply); - return -1; - } + /* handles setting the sftp->client_version */ + msg = sftp_get_client_message(sftp); + if (msg == NULL) { + return -1; + } - if (sftp_packet_write(sftp, SSH_FXP_VERSION, reply) < 0) { - SSH_BUFFER_FREE(reply); - return -1; - } - SSH_BUFFER_FREE(reply); + if (msg->type != SSH_FXP_INIT) { + ssh_set_error(session, + SSH_FATAL, + "Packet read of type %d instead of SSH_FXP_INIT", + msg->type); + return -1; + } - SSH_LOG(SSH_LOG_PROTOCOL, "Server version sent"); + SSH_LOG(SSH_LOG_PACKET, "Received SSH_FXP_INIT"); - if (version > LIBSFTP_VERSION) { - sftp->version = LIBSFTP_VERSION; - } else { - sftp->version = (int)version; - } + rc = sftp_reply_version(msg); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to process the SSH_FXP_INIT message"); + return -1; + } - return 0; + return 0; } void sftp_server_free(sftp_session sftp) @@ -353,6 +316,7 @@ void sftp_server_free(sftp_session sftp) SAFE_FREE(sftp); } + #endif /* WITH_SERVER */ void sftp_free(sftp_session sftp) @@ -383,169 +347,62 @@ void sftp_free(sftp_session sftp) SAFE_FREE(sftp->read_packet); sftp_ext_free(sftp->ext); + sftp_limits_free(sftp->limits); SAFE_FREE(sftp); } -ssize_t sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload) -{ - uint8_t header[5] = {0}; - uint32_t payload_size; - ssize_t size; - int rc; - - /* Add size of type */ - payload_size = ssh_buffer_get_len(payload) + sizeof(uint8_t); - PUSH_BE_U32(header, 0, payload_size); - PUSH_BE_U8(header, 4, type); - - rc = ssh_buffer_prepend_data(payload, header, sizeof(header)); - if (rc < 0) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - - size = ssh_channel_write(sftp->channel, - ssh_buffer_get(payload), - ssh_buffer_get_len(payload)); - if (size < 0) { - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - - if ((uint32_t)size != ssh_buffer_get_len(payload)) { - SSH_LOG(SSH_LOG_PACKET, - "Had to write %d bytes, wrote only %zd", - ssh_buffer_get_len(payload), - size); - } - - return size; -} - -sftp_packet sftp_packet_read(sftp_session sftp) +/* @internal + * Process the incoming data and copy them from the SSH packet buffer to the + * SFTP packet buffer. + * @returns number of decoded bytes. + */ +int +sftp_decode_channel_data_to_packet(sftp_session sftp, void *data, uint32_t len) { - uint8_t tmpbuf[4]; - uint8_t *buffer = NULL; sftp_packet packet = sftp->read_packet; - uint32_t size; int nread; - bool is_eof; - int rc; - - packet->sftp = sftp; + int payload_len; + unsigned int data_offset; + int to_read, rc; - /* - * If the packet has a payload, then just reinit the buffer, otherwise - * allocate a new one. - */ - if (packet->payload != NULL) { - rc = ssh_buffer_reinit(packet->payload); - if (rc != 0) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } - } else { - packet->payload = ssh_buffer_new(); - if (packet->payload == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } + if (packet->sftp == NULL) { + packet->sftp = sftp; } - nread = 0; - do { - int s; - - // read from channel until 4 bytes have been read or an error occurs - s = ssh_channel_read(sftp->channel, tmpbuf + nread, 4 - nread, 0); - if (s < 0) { - goto error; - } else if (s == 0) { - is_eof = ssh_channel_is_eof(sftp->channel); - if (is_eof) { - ssh_set_error(sftp->session, - SSH_FATAL, - "Received EOF while reading sftp packet size"); - sftp_set_error(sftp, SSH_FX_EOF); - goto error; - } - } else { - nread += s; - } - } while (nread < 4); - - size = PULL_BE_U32(tmpbuf, 0); - if (size == 0 || size > SFTP_PACKET_SIZE_MAX) { - ssh_set_error(sftp->session, SSH_FATAL, "Invalid sftp packet size!"); - sftp_set_error(sftp, SSH_FX_FAILURE); - goto error; + data_offset = sizeof(uint32_t) + sizeof(uint8_t); + /* not enough bytes to read */ + if (len < data_offset) { + return SSH_ERROR; } - do { - nread = ssh_channel_read(sftp->channel, tmpbuf, 1, 0); - if (nread < 0) { - goto error; - } else if (nread == 0) { - is_eof = ssh_channel_is_eof(sftp->channel); - if (is_eof) { - ssh_set_error(sftp->session, - SSH_FATAL, - "Received EOF while reading sftp packet type"); - sftp_set_error(sftp, SSH_FX_EOF); - goto error; - } - } - } while (nread < 1); - - packet->type = tmpbuf[0]; + payload_len = PULL_BE_U32(data, 0); + packet->type = PULL_BE_U8(data, 4); - /* Remove the packet type size */ - size -= sizeof(uint8_t); - - /* Allocate the receive buffer from payload */ - buffer = ssh_buffer_allocate(packet->payload, size); - if (buffer == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - goto error; + /* We should check the legality of payload length */ + if (payload_len + sizeof(uint32_t) > len || payload_len < 0) { + return SSH_ERROR; } - while (size > 0 && size < SFTP_PACKET_SIZE_MAX) { - nread = ssh_channel_read(sftp->channel, buffer, size, 0); - if (nread < 0) { - /* TODO: check if there are cases where an error needs to be set here */ - goto error; - } - if (nread > 0) { - buffer += nread; - size -= nread; - } else { /* nread == 0 */ - /* Retry the reading unless the remote was closed */ - is_eof = ssh_channel_is_eof(sftp->channel); - if (is_eof) { - ssh_set_error(sftp->session, - SSH_REQUEST_DENIED, - "Received EOF while reading sftp packet"); - sftp_set_error(sftp, SSH_FX_EOF); - goto error; - } - } + to_read = payload_len - sizeof(uint8_t); + rc = ssh_buffer_add_data(packet->payload, + (void*)((uint8_t *)data + data_offset), + to_read); + if (rc != 0) { + return SSH_ERROR; } + nread = ssh_buffer_get_len(packet->payload); - return packet; -error: - ssh_buffer_reinit(packet->payload); - return NULL; -} + /* We should check if we copied the whole data */ + if (nread != to_read) { + return SSH_ERROR; + } -static void sftp_set_error(sftp_session sftp, int errnum) { - if (sftp != NULL) { - sftp->errnum = errnum; - } + /* + * We should return how many bytes we decoded, including packet length header + * and the payload length. + */ + return payload_len + sizeof(uint32_t); } /* Get the last sftp error */ @@ -557,199 +414,152 @@ int sftp_get_error(sftp_session sftp) { return sftp->errnum; } -static void sftp_message_free(sftp_message msg) -{ - if (msg == NULL) { - return; - } - - SSH_BUFFER_FREE(msg->payload); - SAFE_FREE(msg); -} +static sftp_limits_t sftp_limits_use_extension(sftp_session sftp); +static sftp_limits_t sftp_limits_use_default(sftp_session sftp); -static sftp_message sftp_get_message(sftp_packet packet) +/* Initialize the sftp session with the server. */ +int sftp_init(sftp_session sftp) { - sftp_session sftp = packet->sftp; - sftp_message msg = NULL; + sftp_packet packet = NULL; + ssh_buffer buffer = NULL; + char *ext_name = NULL; + char *ext_data = NULL; + uint32_t version; int rc; - switch(packet->type) { - case SSH_FXP_STATUS: - case SSH_FXP_HANDLE: - case SSH_FXP_DATA: - case SSH_FXP_ATTRS: - case SSH_FXP_NAME: - case SSH_FXP_EXTENDED_REPLY: - break; - default: - ssh_set_error(packet->sftp->session, - SSH_FATAL, - "Unknown packet type %d", - packet->type); - sftp_set_error(packet->sftp, SSH_FX_FAILURE); - return NULL; - } - - msg = calloc(1, sizeof(struct sftp_message_struct)); - if (msg == NULL) { + buffer = ssh_buffer_new(); + if (buffer == NULL) { ssh_set_error_oom(sftp->session); - sftp_set_error(packet->sftp, SSH_FX_FAILURE); - return NULL; + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; } - msg->sftp = packet->sftp; - msg->packet_type = packet->type; - - /* Move the payload from the packet to the message */ - msg->payload = packet->payload; - packet->payload = NULL; - - rc = ssh_buffer_unpack(msg->payload, "d", &msg->id); + rc = ssh_buffer_pack(buffer, "d", LIBSFTP_VERSION); if (rc != SSH_OK) { - ssh_set_error(packet->sftp->session, SSH_FATAL, - "Invalid packet %d: no ID", packet->type); - sftp_message_free(msg); - sftp_set_error(packet->sftp, SSH_FX_FAILURE); - return NULL; + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; } - SSH_LOG(SSH_LOG_PACKET, - "Packet with id %d type %d", - msg->id, - msg->packet_type); - - return msg; -} + rc = sftp_packet_write(sftp, SSH_FXP_INIT, buffer); + if (rc == SSH_ERROR) { + SSH_BUFFER_FREE(buffer); + return -1; + } -static int sftp_read_and_dispatch(sftp_session sftp) -{ - sftp_packet packet = NULL; - sftp_message msg = NULL; + SSH_BUFFER_FREE(buffer); packet = sftp_packet_read(sftp); if (packet == NULL) { - /* something nasty happened reading the packet */ return -1; } - msg = sftp_get_message(packet); - if (msg == NULL) { + if (packet->type != SSH_FXP_VERSION) { + ssh_set_error(sftp->session, SSH_FATAL, + "Received a %d messages instead of SSH_FXP_VERSION", + packet->type); return -1; } - if (sftp_enqueue(sftp, msg) < 0) { - sftp_message_free(msg); + /* TODO: are we sure there are 4 bytes ready? */ + rc = ssh_buffer_unpack(packet->payload, "d", &version); + if (rc != SSH_OK){ + sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } - return 0; -} + SSH_LOG(SSH_LOG_DEBUG, + "SFTP server version %" PRIu32, + version); + rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); + while (rc == SSH_OK) { + uint32_t count = sftp->ext->count; + char **tmp; -void sftp_packet_free(sftp_packet packet) -{ - if (packet == NULL) { - return; - } + rc = ssh_buffer_unpack(packet->payload, "s", &ext_data); + if (rc == SSH_ERROR) { + break; + } - SSH_BUFFER_FREE(packet->payload); - free(packet); -} + SSH_LOG(SSH_LOG_DEBUG, + "SFTP server extension: %s, version: %s", + ext_name, ext_data); -/* Initialize the sftp session with the server. */ -int sftp_init(sftp_session sftp) { - sftp_packet packet = NULL; - ssh_buffer buffer = NULL; - char *ext_name = NULL; - char *ext_data = NULL; - uint32_t version; - int rc; + count++; + tmp = realloc(sftp->ext->name, count * sizeof(char *)); + if (tmp == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(ext_name); + SAFE_FREE(ext_data); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } - buffer = ssh_buffer_new(); - if (buffer == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } + tmp[count - 1] = ext_name; + sftp->ext->name = tmp; - rc = ssh_buffer_pack(buffer, "d", LIBSFTP_VERSION); - if (rc != SSH_OK) { - ssh_set_error_oom(sftp->session); - SSH_BUFFER_FREE(buffer); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - if (sftp_packet_write(sftp, SSH_FXP_INIT, buffer) < 0) { - SSH_BUFFER_FREE(buffer); - return -1; - } - SSH_BUFFER_FREE(buffer); + tmp = realloc(sftp->ext->data, count * sizeof(char *)); + if (tmp == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(ext_name); + SAFE_FREE(ext_data); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } - packet = sftp_packet_read(sftp); - if (packet == NULL) { - return -1; - } + tmp[count - 1] = ext_data; + sftp->ext->data = tmp; - if (packet->type != SSH_FXP_VERSION) { - ssh_set_error(sftp->session, SSH_FATAL, - "Received a %d messages instead of SSH_FXP_VERSION", packet->type); - return -1; - } + sftp->ext->count = count; - /* TODO: are we sure there are 4 bytes ready? */ - rc = ssh_buffer_unpack(packet->payload, "d", &version); - if (rc != SSH_OK){ - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - SSH_LOG(SSH_LOG_PROTOCOL, - "SFTP server version %d", - version); - rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); - while (rc == SSH_OK) { - uint32_t count = sftp->ext->count; - char **tmp; - - rc = ssh_buffer_unpack(packet->payload, "s", &ext_data); - if (rc == SSH_ERROR) { - break; + rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); } - SSH_LOG(SSH_LOG_PROTOCOL, - "SFTP server extension: %s, version: %s", - ext_name, ext_data); + sftp->version = sftp->server_version = (int)version; - count++; - tmp = realloc(sftp->ext->name, count * sizeof(char *)); - if (tmp == NULL) { - ssh_set_error_oom(sftp->session); - SAFE_FREE(ext_name); - SAFE_FREE(ext_data); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - tmp[count - 1] = ext_name; - sftp->ext->name = tmp; + /* Set the limits */ + rc = sftp_extension_supported(sftp, "limits@openssh.com", "1"); + if (rc == 1) { + /* Get the ssh and sftp errors */ + const char *static_ssh_err_msg = ssh_get_error(sftp->session); + int ssh_err_code = ssh_get_error_code(sftp->session); + int sftp_err_code = sftp_get_error(sftp); + char *ssh_err_msg = strdup(static_ssh_err_msg); + if (ssh_err_msg == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } - tmp = realloc(sftp->ext->data, count * sizeof(char *)); - if (tmp == NULL) { - ssh_set_error_oom(sftp->session); - SAFE_FREE(ext_name); - SAFE_FREE(ext_data); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; + sftp->limits = sftp_limits_use_extension(sftp); + if (sftp->limits == NULL) { + /* fallback and use the default limits on failure */ + SSH_LOG(SSH_LOG_TRACE, + "Failed to get the limits from a server claiming to " + "support the limits@openssh.com extension, falling back " + "and using the default limits"); + + /* Restore the sftp and ssh errors to their previous state */ + ssh_set_error(sftp->session, ssh_err_code, "%s", ssh_err_msg); + sftp_set_error(sftp, sftp_err_code); + SAFE_FREE(ssh_err_msg); + + sftp->limits = sftp_limits_use_default(sftp); + if (sftp->limits == NULL) { + return -1; + } + } else { + SAFE_FREE(ssh_err_msg); + } + } else { + sftp->limits = sftp_limits_use_default(sftp); + if (sftp->limits == NULL) { + return -1; + } } - tmp[count - 1] = ext_data; - sftp->ext->data = tmp; - - sftp->ext->count = count; - - rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); - } - - sftp->version = sftp->server_version = (int)version; - - return 0; + return 0; } unsigned int sftp_extensions_get_count(sftp_session sftp) { @@ -815,166 +625,6 @@ int sftp_extension_supported(sftp_session sftp, const char *name, return 0; } -static sftp_request_queue request_queue_new(sftp_message msg) { - sftp_request_queue queue = NULL; - - queue = calloc(1, sizeof(struct sftp_request_queue_struct)); - if (queue == NULL) { - ssh_set_error_oom(msg->sftp->session); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - - queue->message = msg; - - return queue; -} - -static void request_queue_free(sftp_request_queue queue) { - if (queue == NULL) { - return; - } - - ZERO_STRUCTP(queue); - SAFE_FREE(queue); -} - -static int sftp_enqueue(sftp_session sftp, sftp_message msg) { - sftp_request_queue queue = NULL; - sftp_request_queue ptr; - - queue = request_queue_new(msg); - if (queue == NULL) { - return -1; - } - - SSH_LOG(SSH_LOG_PACKET, - "Queued msg id %d type %d", - msg->id, msg->packet_type); - - if(sftp->queue == NULL) { - sftp->queue = queue; - } else { - ptr = sftp->queue; - while(ptr->next) { - ptr=ptr->next; /* find end of linked list */ - } - ptr->next = queue; /* add it on bottom */ - } - - return 0; -} - -/* - * Pulls of a message from the queue based on the ID. - * Returns NULL if no message has been found. - */ -static sftp_message sftp_dequeue(sftp_session sftp, uint32_t id){ - sftp_request_queue prev = NULL; - sftp_request_queue queue; - sftp_message msg; - - if(sftp->queue == NULL) { - return NULL; - } - - queue = sftp->queue; - while (queue) { - if(queue->message->id == id) { - /* remove from queue */ - if (prev == NULL) { - sftp->queue = queue->next; - } else { - prev->next = queue->next; - } - msg = queue->message; - request_queue_free(queue); - SSH_LOG(SSH_LOG_PACKET, - "Dequeued msg id %d type %d", - msg->id, - msg->packet_type); - return msg; - } - prev = queue; - queue = queue->next; - } - - return NULL; -} - -/* - * Assigns a new SFTP ID for new requests and assures there is no collision - * between them. - * Returns a new ID ready to use in a request - */ -static inline uint32_t sftp_get_new_id(sftp_session session) { - return ++session->id_counter; -} - -static sftp_status_message parse_status_msg(sftp_message msg){ - sftp_status_message status; - int rc; - - if (msg->packet_type != SSH_FXP_STATUS) { - ssh_set_error(msg->sftp->session, SSH_FATAL, - "Not a ssh_fxp_status message passed in!"); - sftp_set_error(msg->sftp, SSH_FX_BAD_MESSAGE); - return NULL; - } - - status = calloc(1, sizeof(struct sftp_status_message_struct)); - if (status == NULL) { - ssh_set_error_oom(msg->sftp->session); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - - status->id = msg->id; - rc = ssh_buffer_unpack(msg->payload, "d", - &status->status); - if (rc != SSH_OK){ - SAFE_FREE(status); - ssh_set_error(msg->sftp->session, SSH_FATAL, - "Invalid SSH_FXP_STATUS message"); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - rc = ssh_buffer_unpack(msg->payload, "ss", - &status->errormsg, - &status->langmsg); - - if(rc != SSH_OK && msg->sftp->version >=3){ - /* These are mandatory from version 3 */ - SAFE_FREE(status); - ssh_set_error(msg->sftp->session, SSH_FATAL, - "Invalid SSH_FXP_STATUS message"); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - if (status->errormsg == NULL) - status->errormsg = strdup("No error message in packet"); - if (status->langmsg == NULL) - status->langmsg = strdup(""); - if (status->errormsg == NULL || status->langmsg == NULL) { - ssh_set_error_oom(msg->sftp->session); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - status_msg_free(status); - return NULL; - } - - return status; -} - -static void status_msg_free(sftp_status_message status){ - if (status == NULL) { - return; - } - - SAFE_FREE(status->errormsg); - SAFE_FREE(status->langmsg); - SAFE_FREE(status); -} - static sftp_file parse_handle_msg(sftp_message msg){ sftp_file file; @@ -1014,7 +664,7 @@ sftp_dir sftp_opendir(sftp_session sftp, const char *path) sftp_file file = NULL; sftp_dir dir = NULL; sftp_status_message status; - ssh_buffer payload; + ssh_buffer payload = NULL; uint32_t id; int rc; @@ -1065,7 +715,7 @@ sftp_dir sftp_opendir(sftp_session sftp, const char *path) } sftp_set_error(sftp, status->status); ssh_set_error(sftp->session, SSH_REQUEST_DENIED, - "SFTP server: %s", status->errormsg); + "SFTP server: %s", status->errormsg); status_msg_free(status); return NULL; case SSH_FXP_HANDLE: @@ -1092,452 +742,14 @@ sftp_dir sftp_opendir(sftp_session sftp, const char *path) return dir; default: ssh_set_error(sftp->session, SSH_FATAL, - "Received message %d during opendir!", msg->packet_type); + "Received message %d during opendir!", + msg->packet_type); sftp_message_free(msg); } return NULL; } -/* - * Parse the attributes from a payload from some messages. It is coded on - * baselines from the protocol version 4. - * This code is more or less dead but maybe we need it in future. - */ -static sftp_attributes sftp_parse_attr_4(sftp_session sftp, ssh_buffer buf, - int expectnames) { - sftp_attributes attr; - ssh_string owner = NULL; - ssh_string group = NULL; - uint32_t flags = 0; - int ok = 0; - - /* unused member variable */ - (void) expectnames; - - attr = calloc(1, sizeof(struct sftp_attributes_struct)); - if (attr == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } - - /* This isn't really a loop, but it is like a try..catch.. */ - do { - if (ssh_buffer_get_u32(buf, &flags) != 4) { - break; - } - - flags = ntohl(flags); - attr->flags = flags; - - if (flags & SSH_FILEXFER_ATTR_SIZE) { - if (ssh_buffer_get_u64(buf, &attr->size) != 8) { - break; - } - attr->size = ntohll(attr->size); - } - - if (flags & SSH_FILEXFER_ATTR_OWNERGROUP) { - owner = ssh_buffer_get_ssh_string(buf); - if (owner == NULL) { - break; - } - attr->owner = ssh_string_to_char(owner); - SSH_STRING_FREE(owner); - if (attr->owner == NULL) { - break; - } - - group = ssh_buffer_get_ssh_string(buf); - if (group == NULL) { - break; - } - attr->group = ssh_string_to_char(group); - SSH_STRING_FREE(group); - if (attr->group == NULL) { - break; - } - } - - if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { - if (ssh_buffer_get_u32(buf, &attr->permissions) != 4) { - break; - } - attr->permissions = ntohl(attr->permissions); - - /* FIXME on windows! */ - switch (attr->permissions & SSH_S_IFMT) { - case SSH_S_IFSOCK: - case SSH_S_IFBLK: - case SSH_S_IFCHR: - case SSH_S_IFIFO: - attr->type = SSH_FILEXFER_TYPE_SPECIAL; - break; - case SSH_S_IFLNK: - attr->type = SSH_FILEXFER_TYPE_SYMLINK; - break; - case SSH_S_IFREG: - attr->type = SSH_FILEXFER_TYPE_REGULAR; - break; - case SSH_S_IFDIR: - attr->type = SSH_FILEXFER_TYPE_DIRECTORY; - break; - default: - attr->type = SSH_FILEXFER_TYPE_UNKNOWN; - break; - } - } - - if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) { - if (ssh_buffer_get_u64(buf, &attr->atime64) != 8) { - break; - } - attr->atime64 = ntohll(attr->atime64); - - if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { - if (ssh_buffer_get_u32(buf, &attr->atime_nseconds) != 4) { - break; - } - attr->atime_nseconds = ntohl(attr->atime_nseconds); - } - } - - if (flags & SSH_FILEXFER_ATTR_CREATETIME) { - if (ssh_buffer_get_u64(buf, &attr->createtime) != 8) { - break; - } - attr->createtime = ntohll(attr->createtime); - - if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { - if (ssh_buffer_get_u32(buf, &attr->createtime_nseconds) != 4) { - break; - } - attr->createtime_nseconds = ntohl(attr->createtime_nseconds); - } - } - - if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) { - if (ssh_buffer_get_u64(buf, &attr->mtime64) != 8) { - break; - } - attr->mtime64 = ntohll(attr->mtime64); - - if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { - if (ssh_buffer_get_u32(buf, &attr->mtime_nseconds) != 4) { - break; - } - attr->mtime_nseconds = ntohl(attr->mtime_nseconds); - } - } - - if (flags & SSH_FILEXFER_ATTR_ACL) { - if ((attr->acl = ssh_buffer_get_ssh_string(buf)) == NULL) { - break; - } - } - - if (flags & SSH_FILEXFER_ATTR_EXTENDED) { - if (ssh_buffer_get_u32(buf,&attr->extended_count) != 4) { - break; - } - attr->extended_count = ntohl(attr->extended_count); - - while(attr->extended_count && - (attr->extended_type = ssh_buffer_get_ssh_string(buf)) && - (attr->extended_data = ssh_buffer_get_ssh_string(buf))){ - attr->extended_count--; - } - - if (attr->extended_count) { - break; - } - } - ok = 1; - } while (0); - - if (ok == 0) { - /* break issued somewhere */ - SSH_STRING_FREE(attr->acl); - SSH_STRING_FREE(attr->extended_type); - SSH_STRING_FREE(attr->extended_data); - SAFE_FREE(attr->owner); - SAFE_FREE(attr->group); - SAFE_FREE(attr); - - ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); - - return NULL; - } - - return attr; -} - -enum sftp_longname_field_e { - SFTP_LONGNAME_PERM = 0, - SFTP_LONGNAME_FIXME, - SFTP_LONGNAME_OWNER, - SFTP_LONGNAME_GROUP, - SFTP_LONGNAME_SIZE, - SFTP_LONGNAME_DATE, - SFTP_LONGNAME_TIME, - SFTP_LONGNAME_NAME, -}; - -static char *sftp_parse_longname(const char *longname, - enum sftp_longname_field_e longname_field) { - const char *p, *q; - size_t len, field = 0; - - p = longname; - /* Find the beginning of the field which is specified by sftp_longname_field_e. */ - while(field != longname_field) { - if(isspace(*p)) { - field++; - p++; - while(*p && isspace(*p)) { - p++; - } - } else { - p++; - } - } - - q = p; - while (! isspace(*q)) { - q++; - } - - len = q - p; - - return strndup(p, len); -} - -/* sftp version 0-3 code. It is different from the v4 */ -/* maybe a paste of the draft is better than the code */ -/* - uint32 flags - uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE - uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID - uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID - uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS - uint32 atime present only if flag SSH_FILEXFER_ACMODTIME - uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME - uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED - string extended_type - string extended_data - ... more extended data (extended_type - extended_data pairs), - so that number of pairs equals extended_count */ -static sftp_attributes sftp_parse_attr_3(sftp_session sftp, ssh_buffer buf, - int expectname) { - sftp_attributes attr; - int rc; - - attr = calloc(1, sizeof(struct sftp_attributes_struct)); - if (attr == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } - - if (expectname) { - rc = ssh_buffer_unpack(buf, "ss", - &attr->name, - &attr->longname); - if (rc != SSH_OK){ - goto error; - } - SSH_LOG(SSH_LOG_PROTOCOL, "Name: %s", attr->name); - - /* Set owner and group if we talk to openssh and have the longname */ - if (ssh_get_openssh_version(sftp->session)) { - attr->owner = sftp_parse_longname(attr->longname, SFTP_LONGNAME_OWNER); - if (attr->owner == NULL) { - goto error; - } - - attr->group = sftp_parse_longname(attr->longname, SFTP_LONGNAME_GROUP); - if (attr->group == NULL) { - goto error; - } - } - } - - rc = ssh_buffer_unpack(buf, "d", &attr->flags); - if (rc != SSH_OK){ - goto error; - } - SSH_LOG(SSH_LOG_PROTOCOL, - "Flags: %.8"PRIx32"\n", (uint32_t) attr->flags); - - if (attr->flags & SSH_FILEXFER_ATTR_SIZE) { - rc = ssh_buffer_unpack(buf, "q", &attr->size); - if(rc != SSH_OK) { - goto error; - } - SSH_LOG(SSH_LOG_PROTOCOL, - "Size: %"PRIu64"\n", - (uint64_t) attr->size); - } - - if (attr->flags & SSH_FILEXFER_ATTR_UIDGID) { - rc = ssh_buffer_unpack(buf, "dd", - &attr->uid, - &attr->gid); - if (rc != SSH_OK){ - goto error; - } - } - - if (attr->flags & SSH_FILEXFER_ATTR_PERMISSIONS) { - rc = ssh_buffer_unpack(buf, "d", &attr->permissions); - if (rc != SSH_OK){ - goto error; - } - - switch (attr->permissions & SSH_S_IFMT) { - case SSH_S_IFSOCK: - case SSH_S_IFBLK: - case SSH_S_IFCHR: - case SSH_S_IFIFO: - attr->type = SSH_FILEXFER_TYPE_SPECIAL; - break; - case SSH_S_IFLNK: - attr->type = SSH_FILEXFER_TYPE_SYMLINK; - break; - case SSH_S_IFREG: - attr->type = SSH_FILEXFER_TYPE_REGULAR; - break; - case SSH_S_IFDIR: - attr->type = SSH_FILEXFER_TYPE_DIRECTORY; - break; - default: - attr->type = SSH_FILEXFER_TYPE_UNKNOWN; - break; - } - } - - if (attr->flags & SSH_FILEXFER_ATTR_ACMODTIME) { - rc = ssh_buffer_unpack(buf, "dd", - &attr->atime, - &attr->mtime); - if (rc != SSH_OK){ - goto error; - } - } - - if (attr->flags & SSH_FILEXFER_ATTR_EXTENDED) { - rc = ssh_buffer_unpack(buf, "d", &attr->extended_count); - if (rc != SSH_OK){ - goto error; - } - - if (attr->extended_count > 0){ - rc = ssh_buffer_unpack(buf, "ss", - &attr->extended_type, - &attr->extended_data); - if (rc != SSH_OK){ - goto error; - } - attr->extended_count--; - } - /* just ignore the remaining extensions */ - - while (attr->extended_count > 0){ - ssh_string tmp1,tmp2; - rc = ssh_buffer_unpack(buf, "SS", &tmp1, &tmp2); - if (rc != SSH_OK){ - goto error; - } - SAFE_FREE(tmp1); - SAFE_FREE(tmp2); - attr->extended_count--; - } - } - - return attr; - - error: - SSH_STRING_FREE(attr->extended_type); - SSH_STRING_FREE(attr->extended_data); - SAFE_FREE(attr->name); - SAFE_FREE(attr->longname); - SAFE_FREE(attr->owner); - SAFE_FREE(attr->group); - SAFE_FREE(attr); - ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); - sftp_set_error(sftp, SSH_FX_FAILURE); - - return NULL; -} - -int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr) -{ - uint32_t flags = (attr ? attr->flags : 0); - int rc; - - flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID | - SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); - - rc = ssh_buffer_pack(buffer, "d", flags); - if (rc != SSH_OK) { - return -1; - } - - if (attr != NULL) { - if (flags & SSH_FILEXFER_ATTR_SIZE) { - rc = ssh_buffer_pack(buffer, "q", attr->size); - if (rc != SSH_OK) { - return -1; - } - } - - if (flags & SSH_FILEXFER_ATTR_UIDGID) { - rc = ssh_buffer_pack(buffer, "dd", attr->uid, attr->gid); - if (rc != SSH_OK) { - return -1; - } - } - - if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { - rc = ssh_buffer_pack(buffer, "d", attr->permissions); - if (rc != SSH_OK) { - return -1; - } - } - - if (flags & SSH_FILEXFER_ATTR_ACMODTIME) { - rc = ssh_buffer_pack(buffer, "dd", attr->atime, attr->mtime); - if (rc != SSH_OK) { - return -1; - } - } - } - return 0; -} - - -sftp_attributes sftp_parse_attr(sftp_session session, - ssh_buffer buf, - int expectname) -{ - switch(session->version) { - case 4: - return sftp_parse_attr_4(session, buf, expectname); - case 3: - case 2: - case 1: - case 0: - return sftp_parse_attr_3(session, buf, expectname); - default: - ssh_set_error(session->session, SSH_FATAL, - "Version %d unsupported by client", session->server_version); - return NULL; - } - - return NULL; -} - /* Get the version of the SFTP protocol supported by the server */ int sftp_server_version(sftp_session sftp) { return sftp->server_version; @@ -1581,7 +793,7 @@ sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) } SSH_LOG(SSH_LOG_PACKET, - "Sent a ssh_fxp_readdir with id %d", id); + "Sent a ssh_fxp_readdir with id %" PRIu32, id); while (msg == NULL) { if (sftp_read_and_dispatch(sftp) < 0) { @@ -1609,7 +821,7 @@ sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) } ssh_set_error(sftp->session, SSH_FATAL, - "Unknown error status: %d", status->status); + "Unknown error status: %" PRIu32, status->status); status_msg_free(status); return NULL; @@ -1638,7 +850,7 @@ sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) return NULL; } - SSH_LOG(SSH_LOG_PROTOCOL, "Count is %d", dir->count); + SSH_LOG(SSH_LOG_DEBUG, "Count is %" PRIu32, dir->count); attr = sftp_parse_attr(sftp, dir->buffer, 1); if (attr == NULL) { @@ -1755,6 +967,10 @@ static int sftp_handle_close(sftp_session sftp, ssh_string handle) int sftp_close(sftp_file file){ int err = SSH_NO_ERROR; + if (file == NULL) { + return err; + } + SAFE_FREE(file->name); if (file->handle){ err = sftp_handle_close(file->sftp,file->handle); @@ -1792,7 +1008,7 @@ sftp_file sftp_open(sftp_session sftp, sftp_status_message status; struct sftp_attributes_struct attr; sftp_file handle; - ssh_buffer buffer; + ssh_buffer buffer = NULL; sftp_attributes stat_data; uint32_t sftp_flags = 0; uint32_t id; @@ -1824,7 +1040,8 @@ sftp_file sftp_open(sftp_session sftp, if ((flags & O_APPEND) == O_APPEND) { sftp_flags |= SSH_FXF_APPEND; } - SSH_LOG(SSH_LOG_PACKET,"Opening file %s with sftp flags %x",file,sftp_flags); + SSH_LOG(SSH_LOG_PACKET, "Opening file %s with sftp flags %" PRIx32, + file, sftp_flags); id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, @@ -1862,62 +1079,66 @@ sftp_file sftp_open(sftp_session sftp, } switch (msg->packet_type) { - case SSH_FXP_STATUS: - status = parse_status_msg(msg); - sftp_message_free(msg); - if (status == NULL) { - return NULL; - } - sftp_set_error(sftp, status->status); - ssh_set_error(sftp->session, SSH_REQUEST_DENIED, - "SFTP server: %s", status->errormsg); - status_msg_free(status); + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return NULL; + case SSH_FXP_HANDLE: + handle = parse_handle_msg(msg); + if (handle == NULL) { return NULL; - case SSH_FXP_HANDLE: - handle = parse_handle_msg(msg); - if (handle == NULL) { + } + sftp_message_free(msg); + if ((flags & O_APPEND) == O_APPEND) { + stat_data = sftp_stat(sftp, file); + if (stat_data == NULL) { + sftp_close(handle); return NULL; } - sftp_message_free(msg); - if ((flags & O_APPEND) == O_APPEND) { - stat_data = sftp_stat(sftp, file); - if (stat_data == NULL) { - sftp_close(handle); - return NULL; - } - if ((stat_data->flags & SSH_FILEXFER_ATTR_SIZE) != SSH_FILEXFER_ATTR_SIZE) { - ssh_set_error(sftp->session, - SSH_FATAL, - "Cannot open in append mode. Unknown file size."); - sftp_close(handle); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } - - handle->offset = stat_data->size; + if ((stat_data->flags & SSH_FILEXFER_ATTR_SIZE) != SSH_FILEXFER_ATTR_SIZE) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Cannot open in append mode. Unknown file size."); + sftp_attributes_free(stat_data); + sftp_close(handle); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; } - return handle; - default: - ssh_set_error(sftp->session, SSH_FATAL, - "Received message %d during open!", msg->packet_type); - sftp_message_free(msg); - sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + + handle->offset = stat_data->size; + sftp_attributes_free(stat_data); + } + return handle; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during open!", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } return NULL; } -void sftp_file_set_nonblocking(sftp_file handle){ - handle->nonblocking=1; +void sftp_file_set_nonblocking(sftp_file handle) +{ + handle->nonblocking = 1; } -void sftp_file_set_blocking(sftp_file handle){ - handle->nonblocking=0; +void sftp_file_set_blocking(sftp_file handle) +{ + handle->nonblocking = 0; } /* Read from a file using an opened sftp file handle. */ ssize_t sftp_read(sftp_file handle, void *buf, size_t count) { - sftp_session sftp = handle->sftp; + sftp_session sftp; sftp_message msg = NULL; sftp_status_message status; ssh_string datastring; @@ -1926,10 +1147,27 @@ ssize_t sftp_read(sftp_file handle, void *buf, size_t count) { uint32_t id; int rc; + if (handle == NULL) { + return -1; + } + sftp = handle->sftp; + if (handle->eof) { return 0; } + /* + * limit the reads to the maximum specified in Section 3 of + * https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02 + * or to the values provided by the limits@openssh.com extension. + * + * TODO: We should iterate over the blocks rather than writing less than + * requested to provide less surprises to the calling applications. + */ + if (count > sftp->limits->max_read_length) { + count = sftp->limits->max_read_length; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -2125,7 +1363,7 @@ int sftp_async_read(sftp_file file, void *data, uint32_t size, uint32_t id){ if (ssh_string_len(datastring) > size) { ssh_set_error(sftp->session, SSH_FATAL, "Received a too big DATA packet from sftp server: " - "%zu and asked for %u", + "%zu and asked for %" PRIu32, ssh_string_len(datastring), size); SSH_STRING_FREE(datastring); return SSH_ERROR; @@ -2147,7 +1385,7 @@ int sftp_async_read(sftp_file file, void *data, uint32_t size, uint32_t id){ } ssize_t sftp_write(sftp_file file, const void *buf, size_t count) { - sftp_session sftp = file->sftp; + sftp_session sftp; sftp_message msg = NULL; sftp_status_message status; ssh_buffer buffer; @@ -2156,6 +1394,11 @@ ssize_t sftp_write(sftp_file file, const void *buf, size_t count) { size_t packetlen; int rc; + if (file == NULL) { + return -1; + } + sftp = file->sftp; + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -2165,6 +1408,18 @@ ssize_t sftp_write(sftp_file file, const void *buf, size_t count) { id = sftp_get_new_id(file->sftp); + /* + * limit the writes to the maximum specified in Section 3 of + * https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02 + * or to the values provided by the limits@openssh.com extension. + * + * TODO: We should iterate over the blocks rather than writing less than + * requested to provide less surprises to the calling applications. + */ + if (count > sftp->limits->max_write_length) { + count = sftp->limits->max_write_length; + } + rc = ssh_buffer_pack(buffer, "dSqdP", id, @@ -2178,8 +1433,8 @@ ssize_t sftp_write(sftp_file file, const void *buf, size_t count) { sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } - packetlen=ssh_buffer_get_len(buffer); len = sftp_packet_write(file->sftp, SSH_FXP_WRITE, buffer); + packetlen=ssh_buffer_get_len(buffer); SSH_BUFFER_FREE(buffer); if (len < 0) { return -1; @@ -2514,85 +1769,115 @@ int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode) } /* code written by nick */ -int sftp_rename(sftp_session sftp, const char *original, const char *newname) { - sftp_status_message status = NULL; - sftp_message msg = NULL; - ssh_buffer buffer; - uint32_t id; - int rc; +int sftp_rename(sftp_session sftp, const char *original, const char *newname) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + const char *extension_name = "posix-rename@openssh.com"; + int request_type; + int rc; - buffer = ssh_buffer_new(); - if (buffer == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } - id = sftp_get_new_id(sftp); + id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, - "dss", - id, - original, - newname); - if (rc != SSH_OK) { - ssh_set_error_oom(sftp->session); - SSH_BUFFER_FREE(buffer); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } + /* + * posix-rename@openssh.com extension will be used + * if it is supported by sftp + */ + if (sftp_extension_supported(sftp, + extension_name, + "1")) { + rc = ssh_buffer_pack(buffer, + "dsss", + id, + extension_name, + original, + newname); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } - if (sftp->version >= 4){ - /* POSIX rename atomically replaces newpath, we should do the same - * only available on >=v4 */ - ssh_buffer_add_u32(buffer, SSH_FXF_RENAME_OVERWRITE); - } + request_type = SSH_FXP_EXTENDED; + } else { + rc = ssh_buffer_pack(buffer, + "dss", + id, + original, + newname); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } - if (sftp_packet_write(sftp, SSH_FXP_RENAME, buffer) < 0) { - SSH_BUFFER_FREE(buffer); - return -1; - } - SSH_BUFFER_FREE(buffer); + if (sftp->version >= 4) { + /* + * POSIX rename atomically replaces newpath, + * we should do the same only available on >=v4 + */ + ssh_buffer_add_u32(buffer, SSH_FXF_RENAME_OVERWRITE); + } - while (msg == NULL) { - if (sftp_read_and_dispatch(sftp) < 0) { - return -1; + request_type = SSH_FXP_RENAME; } - msg = sftp_dequeue(sftp, id); - } - /* By specification, this command only returns SSH_FXP_STATUS */ - if (msg->packet_type == SSH_FXP_STATUS) { - status = parse_status_msg(msg); - sftp_message_free(msg); - if (status == NULL) { - return -1; + rc = sftp_packet_write(sftp, request_type, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; } - sftp_set_error(sftp, status->status); - switch (status->status) { - case SSH_FX_OK: + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * Status should be SSH_FX_OK if the command was successful, + * if it didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); status_msg_free(status); - return 0; - default: - break; + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to rename", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } - /* - * Status should be SSH_FX_OK if the command was successful, if it didn't, - * then there was an error - */ - ssh_set_error(sftp->session, SSH_REQUEST_DENIED, - "SFTP server: %s", status->errormsg); - status_msg_free(status); - return -1; - } else { - ssh_set_error(sftp->session, SSH_FATAL, - "Received message %d when attempting to rename", - msg->packet_type); - sftp_message_free(msg); - sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); - } - return -1; + return -1; } /* Code written by Nick */ @@ -2720,7 +2005,8 @@ int sftp_utimes(sftp_session sftp, const char *file, return sftp_setstat(sftp, file, &attr); } -int sftp_symlink(sftp_session sftp, const char *target, const char *dest) { +int sftp_symlink(sftp_session sftp, const char *target, const char *dest) +{ sftp_status_message status = NULL; sftp_message msg = NULL; ssh_buffer buffer; @@ -2744,7 +2030,10 @@ int sftp_symlink(sftp_session sftp, const char *target, const char *dest) { id = sftp_get_new_id(sftp); - /* TODO check for version number if they ever fix it. */ + /* The OpenSSH sftp server has order of the arguments reversed, see the + * section "4.1 sftp: Reversal of arguments to SSH_FXP_SYMLINK' in + * https://github.com/openssh/openssh-portable/blob/master/PROTOCOL + * for more information */ if (ssh_get_openssh_version(sftp->session)) { rc = ssh_buffer_pack(buffer, "dss", @@ -2904,6 +2193,94 @@ char *sftp_readlink(sftp_session sftp, const char *path) return NULL; } +int sftp_hardlink(sftp_session sftp, const char *oldpath, const char *newpath) +{ + ssh_buffer buffer = NULL; + uint32_t id; + const char *extension_name = "hardlink@openssh.com"; + sftp_status_message status = NULL; + sftp_message msg = NULL; + int rc; + + if (sftp == NULL) { + return -1; + } + + if (oldpath == NULL || newpath == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "dsss", + id, + extension_name, + oldpath, + newpath); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * Status should be SSH_FX_OK if the command was successful, + * if it didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to create hardlink", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + static sftp_statvfs_t sftp_parse_statvfs(sftp_session sftp, ssh_buffer buf) { sftp_statvfs_t statvfs; int rc; @@ -3204,6 +2581,183 @@ void sftp_statvfs_free(sftp_statvfs_t statvfs) { SAFE_FREE(statvfs); } +static sftp_limits_t sftp_limits_new(void) +{ + return calloc(1, sizeof(struct sftp_limits_struct)); +} + +static sftp_limits_t sftp_parse_limits(sftp_session sftp, ssh_buffer buf) +{ + sftp_limits_t limits = NULL; + int rc; + + limits = sftp_limits_new(); + if (limits == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_unpack(buf, "qqqq", + &limits->max_packet_length, /** maximum number of bytes in a single sftp packet */ + &limits->max_read_length, /** maximum length in a SSH_FXP_READ packet */ + &limits->max_write_length, /** maximum length in a SSH_FXP_WRITE packet */ + &limits->max_open_handles /** maximum number of active handles allowed by server */ + ); + if (rc != SSH_OK) { + SAFE_FREE(limits); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid limits structure"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return limits; +} + +static sftp_limits_t sftp_limits_use_extension(sftp_session sftp) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) + return NULL; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "ds", + id, + "limits@openssh.com"); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + sftp_limits_t limits = sftp_parse_limits(sftp, msg->payload); + sftp_message_free(msg); + if (limits == NULL) { + return NULL; + } + + return limits; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d when attempting to get limits", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +static sftp_limits_t sftp_limits_use_default(sftp_session sftp) +{ + sftp_limits_t limits = NULL; + + if (sftp == NULL) { + return NULL; + } + + limits = sftp_limits_new(); + if (limits == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + limits->max_packet_length = 34000; + limits->max_read_length = 32768; + limits->max_write_length = 32768; + + /* + * For max-open-handles field openssh says : + * If the server doesn't enforce a specific limit, then the field may + * be set to 0. This implies the server relies on the OS to enforce + * limits (e.g. available memory or file handles), and such limits + * might be dynamic. The client SHOULD take care to not try to exceed + * reasonable limits. + */ + limits->max_open_handles = 0; + + return limits; +} + +sftp_limits_t sftp_limits(sftp_session sftp) +{ + sftp_limits_t limits = NULL; + + if (sftp == NULL) { + return NULL; + } + + if (sftp->limits == NULL) { + ssh_set_error(sftp, SSH_FATAL, + "Uninitialized sftp session, " + "sftp_init() was not called or failed"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + limits = sftp_limits_new(); + if (limits == NULL) { + ssh_set_error_oom(sftp); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + memcpy(limits, sftp->limits, sizeof(struct sftp_limits_struct)); + return limits; +} + +void sftp_limits_free(sftp_limits_t limits) +{ + if (limits == NULL) { + return; + } + + SAFE_FREE(limits); +} + /* another code written by Nick */ char *sftp_canonicalize_path(sftp_session sftp, const char *path) { @@ -3449,4 +3003,98 @@ sftp_attributes sftp_fstat(sftp_file file) return NULL; } +char *sftp_expand_path(sftp_session sftp, const char *path) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; + + if (sftp == NULL) { + return NULL; + } + + if (path == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "NULL received as an argument instead of the path to expand"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "dss", + id, + "expand-path@openssh.com", + path); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + while (msg == NULL) { + rc = sftp_read_and_dispatch(sftp); + if (rc < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_NAME) { + uint32_t ignored = 0; + char *cname = NULL; + + rc = ssh_buffer_unpack(msg->payload, + "ds", + &ignored, + &cname); + sftp_message_free(msg); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to parse expanded path"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return cname; + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to expand path", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + #endif /* WITH_SFTP */ diff --git a/src/sftp_aio.c b/src/sftp_aio.c new file mode 100644 index 00000000..c1c54561 --- /dev/null +++ b/src/sftp_aio.c @@ -0,0 +1,500 @@ +/* + * sftp_aio.c - Secure FTP functions for asynchronous i/o + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2008 by Aris Adamantiadis + * Copyright (c) 2008-2018 by Andreas Schneider <asn@cryptomilk.org> + * Copyright (c) 2023 by Eshan Kelkar <eshankelkar@galorithm.com> + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/sftp.h" +#include "libssh/sftp_priv.h" +#include "libssh/buffer.h" +#include "libssh/session.h" + +#ifdef WITH_SFTP + +struct sftp_aio_struct { + sftp_file file; + uint32_t id; + size_t len; +}; + +static sftp_aio sftp_aio_new(void) +{ + sftp_aio aio = NULL; + aio = calloc(1, sizeof(struct sftp_aio_struct)); + return aio; +} + +void sftp_aio_free(sftp_aio aio) +{ + SAFE_FREE(aio); +} + +ssize_t sftp_aio_begin_read(sftp_file file, size_t len, sftp_aio *aio) +{ + sftp_session sftp = NULL; + ssh_buffer buffer = NULL; + sftp_aio aio_handle = NULL; + uint32_t id; + int rc; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + return SSH_ERROR; + } + + sftp = file->sftp; + if (len == 0) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, 0 passed as the number of " + "bytes to read"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + /* Apply a cap on the length a user is allowed to read */ + if (len > sftp->limits->max_read_length) { + len = sftp->limits->max_read_length; + } + + if (aio == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed instead of a pointer to " + "a location to store an sftp aio handle"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "dSqd", + id, + file->handle, + file->offset, + len); + + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle = sftp_aio_new(); + if (aio_handle == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle->file = file; + aio_handle->id = id; + aio_handle->len = len; + + rc = sftp_packet_write(sftp, SSH_FXP_READ, buffer); + SSH_BUFFER_FREE(buffer); + if (rc == SSH_ERROR) { + SFTP_AIO_FREE(aio_handle); + return SSH_ERROR; + } + + /* Assume we read len bytes from the file */ + file->offset += len; + *aio = aio_handle; + return len; +} + +ssize_t sftp_aio_wait_read(sftp_aio *aio, + void *buf, + size_t buf_size) +{ + sftp_file file = NULL; + size_t bytes_requested; + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status = NULL; + uint32_t string_len, host_len; + int rc, err; + + /* + * This function releases the memory of the structure + * that (*aio) points to in all cases except when the + * return value is SSH_AGAIN. + * + * If the return value is SSH_AGAIN, the user should call this + * function again to get the response for the request corresponding + * to the structure that (*aio) points to, hence we don't release the + * structure's memory when SSH_AGAIN is returned. + */ + + if (aio == NULL || *aio == NULL) { + return SSH_ERROR; + } + + file = (*aio)->file; + bytes_requested = (*aio)->len; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + sftp = file->sftp; + if (bytes_requested == 0) { + /* should never happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid sftp aio, len for requested i/o is 0"); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + if (buf == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed " + "instead of a buffer's address"); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + if (buf_size < bytes_requested) { + ssh_set_error(sftp->session, SSH_FATAL, + "Buffer size (%zu, passed by the caller) is " + "smaller than the number of bytes requested " + "to read (%zu, as per the supplied sftp aio)", + buf_size, bytes_requested); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + /* handle an existing request */ + while (msg == NULL) { + if (file->nonblocking) { + if (ssh_channel_poll(sftp->channel, 0) == 0) { + /* we cannot block */ + return SSH_AGAIN; + } + } + + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + msg = sftp_dequeue(sftp, (*aio)->id); + } + + /* + * Release memory for the structure that (*aio) points to + * as all further points of return are for success or + * failure. + */ + SFTP_AIO_FREE(*aio); + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return SSH_ERROR; + } + + sftp_set_error(sftp, status->status); + if (status->status != SSH_FX_EOF) { + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server : %s", status->errormsg); + err = SSH_ERROR; + } else { + file->eof = 1; + /* Update the offset correctly */ + file->offset = file->offset - bytes_requested; + err = SSH_OK; + } + + status_msg_free(status); + return err; + + case SSH_FXP_DATA: + rc = ssh_buffer_get_u32(msg->payload, &string_len); + if (rc == 0) { + /* Insufficient data in the buffer */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received invalid DATA packet from sftp server"); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + sftp_message_free(msg); + return SSH_ERROR; + } + + host_len = ntohl(string_len); + if (host_len > buf_size) { + /* + * This should never happen, as according to the + * SFTP protocol the server reads bytes less than + * or equal to the number of bytes requested to read. + * + * And we have checked before that the buffer size is + * greater than or equal to the number of bytes requested + * to read, hence code of this if block should never + * get executed. + */ + ssh_set_error(sftp->session, SSH_FATAL, + "DATA packet (%u bytes) received from sftp server " + "cannot fit into the supplied buffer (%zu bytes)", + host_len, buf_size); + sftp_set_error(sftp, SSH_FX_FAILURE); + sftp_message_free(msg); + return SSH_ERROR; + } + + string_len = ssh_buffer_get_data(msg->payload, buf, host_len); + if (string_len != host_len) { + /* should never happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received invalid DATA packet from sftp server"); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + sftp_message_free(msg); + return SSH_ERROR; + } + + /* Update the offset with the correct value */ + file->offset = file->offset - (bytes_requested - string_len); + sftp_message_free(msg); + return string_len; + + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during read!", msg->packet_type); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + sftp_message_free(msg); + return SSH_ERROR; + } + + return SSH_ERROR; /* not reached */ +} + +ssize_t sftp_aio_begin_write(sftp_file file, + const void *buf, + size_t len, + sftp_aio *aio) +{ + sftp_session sftp = NULL; + ssh_buffer buffer = NULL; + sftp_aio aio_handle = NULL; + uint32_t id; + int rc; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + return SSH_ERROR; + } + + sftp = file->sftp; + if (buf == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed instead " + "of a buffer's address"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + if (len == 0) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, 0 passed as the number " + "of bytes to write"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + /* Apply a cap on the length a user is allowed to write */ + if (len > sftp->limits->max_write_length) { + len = sftp->limits->max_write_length; + } + + if (aio == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed instead of a pointer to " + "a location to store an sftp aio handle"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + id = sftp_get_new_id(sftp); + rc = ssh_buffer_pack(buffer, + "dSqdP", + id, + file->handle, + file->offset, + len, /* len of datastring */ + len, buf); + + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle = sftp_aio_new(); + if (aio_handle == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle->file = file; + aio_handle->id = id; + aio_handle->len = len; + + rc = sftp_packet_write(sftp, SSH_FXP_WRITE, buffer); + SSH_BUFFER_FREE(buffer); + if (rc == SSH_ERROR) { + SFTP_AIO_FREE(aio_handle); + return SSH_ERROR; + } + + /* Assume we wrote len bytes to the file */ + file->offset += len; + *aio = aio_handle; + return len; +} + +ssize_t sftp_aio_wait_write(sftp_aio *aio) +{ + sftp_file file = NULL; + size_t bytes_requested; + + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status = NULL; + + /* + * This function releases the memory of the structure + * that (*aio) points to in all cases except when the + * return value is SSH_AGAIN. + * + * If the return value is SSH_AGAIN, the user should call this + * function again to get the response for the request corresponding + * to the structure that (*aio) points to, hence we don't release the + * structure's memory when SSH_AGAIN is returned. + */ + + if (aio == NULL || *aio == NULL) { + return SSH_ERROR; + } + + file = (*aio)->file; + bytes_requested = (*aio)->len; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + sftp = file->sftp; + if (bytes_requested == 0) { + /* This should never happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid sftp aio, len for requested i/o is 0"); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + while (msg == NULL) { + if (file->nonblocking) { + if (ssh_channel_poll(sftp->channel, 0) == 0) { + /* we cannot block */ + return SSH_AGAIN; + } + } + + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + msg = sftp_dequeue(sftp, (*aio)->id); + } + + /* + * Release memory for the structure that (*aio) points to + * as all further points of return are for success or + * failure. + */ + SFTP_AIO_FREE(*aio); + + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return SSH_ERROR; + } + + sftp_set_error(sftp, status->status); + if (status->status == SSH_FX_OK) { + status_msg_free(status); + return bytes_requested; + } + + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return SSH_ERROR; + } + + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during write!", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + return SSH_ERROR; +} + +#endif /* WITH_SFTP */ diff --git a/src/sftp_common.c b/src/sftp_common.c new file mode 100644 index 00000000..005fa9da --- /dev/null +++ b/src/sftp_common.c @@ -0,0 +1,913 @@ +/* + * sftp_common.c - Secure FTP functions which are private and are used + * internally by other sftp api functions spread across + * various source files. + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2008 by Aris Adamantiadis + * Copyright (c) 2008-2018 by Andreas Schneider <asn@cryptomilk.org> + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include <ctype.h> + +#include "libssh/sftp.h" +#include "libssh/sftp_priv.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/bytearray.h" + +#ifdef WITH_SFTP + +/* Buffer size maximum is 256M */ +#define SFTP_PACKET_SIZE_MAX 0x10000000 + +sftp_packet sftp_packet_read(sftp_session sftp) +{ + uint8_t tmpbuf[4]; + uint8_t *buffer = NULL; + sftp_packet packet = sftp->read_packet; + uint32_t size; + int nread; + bool is_eof; + int rc; + + packet->sftp = sftp; + + /* + * If the packet has a payload, then just reinit the buffer, otherwise + * allocate a new one. + */ + if (packet->payload != NULL) { + rc = ssh_buffer_reinit(packet->payload); + if (rc != 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + } else { + packet->payload = ssh_buffer_new(); + if (packet->payload == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + } + + nread = 0; + do { + int s; + + /* read from channel until 4 bytes have been read or an error occurs */ + s = ssh_channel_read(sftp->channel, tmpbuf + nread, 4 - nread, 0); + if (s < 0) { + goto error; + } else if (s == 0) { + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received EOF while reading sftp packet size"); + sftp_set_error(sftp, SSH_FX_EOF); + goto error; + } + } else { + nread += s; + } + } while (nread < 4); + + size = PULL_BE_U32(tmpbuf, 0); + if (size == 0 || size > SFTP_PACKET_SIZE_MAX) { + ssh_set_error(sftp->session, SSH_FATAL, "Invalid sftp packet size!"); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto error; + } + + do { + nread = ssh_channel_read(sftp->channel, tmpbuf, 1, 0); + if (nread < 0) { + goto error; + } else if (nread == 0) { + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received EOF while reading sftp packet type"); + sftp_set_error(sftp, SSH_FX_EOF); + goto error; + } + } + } while (nread < 1); + + packet->type = tmpbuf[0]; + + /* Remove the packet type size */ + size -= sizeof(uint8_t); + + /* Allocate the receive buffer from payload */ + buffer = ssh_buffer_allocate(packet->payload, size); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto error; + } + while (size > 0 && size < SFTP_PACKET_SIZE_MAX) { + nread = ssh_channel_read(sftp->channel, buffer, size, 0); + if (nread < 0) { + /* TODO: check if there are cases where an error needs to be set here */ + goto error; + } + + if (nread > 0) { + buffer += nread; + size -= nread; + } else { /* nread == 0 */ + /* Retry the reading unless the remote was closed */ + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "Received EOF while reading sftp packet"); + sftp_set_error(sftp, SSH_FX_EOF); + goto error; + } + } + } + + return packet; +error: + ssh_buffer_reinit(packet->payload); + return NULL; +} + +int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload) +{ + uint8_t header[5] = {0}; + uint32_t payload_size; + int size; + int rc; + + /* Add size of type */ + payload_size = ssh_buffer_get_len(payload) + sizeof(uint8_t); + PUSH_BE_U32(header, 0, payload_size); + PUSH_BE_U8(header, 4, type); + + rc = ssh_buffer_prepend_data(payload, header, sizeof(header)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + size = ssh_channel_write(sftp->channel, + ssh_buffer_get(payload), + ssh_buffer_get_len(payload)); + if (size < 0) { + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + if ((uint32_t)size != ssh_buffer_get_len(payload)) { + SSH_LOG(SSH_LOG_PACKET, + "Had to write %" PRIu32 " bytes, wrote only %d", + ssh_buffer_get_len(payload), + size); + } + + return size; +} + +void sftp_packet_free(sftp_packet packet) +{ + if (packet == NULL) { + return; + } + + SSH_BUFFER_FREE(packet->payload); + free(packet); +} + +int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr) +{ + uint32_t flags = (attr ? attr->flags : 0); + int rc; + + flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID | + SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); + + rc = ssh_buffer_pack(buffer, "d", flags); + if (rc != SSH_OK) { + return -1; + } + + if (attr != NULL) { + if (flags & SSH_FILEXFER_ATTR_SIZE) { + rc = ssh_buffer_pack(buffer, "q", attr->size); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_UIDGID) { + rc = ssh_buffer_pack(buffer, "dd", attr->uid, attr->gid); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + rc = ssh_buffer_pack(buffer, "d", attr->permissions); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_ACMODTIME) { + rc = ssh_buffer_pack(buffer, "dd", attr->atime, attr->mtime); + if (rc != SSH_OK) { + return -1; + } + } + } + + return 0; +} + +/* + * Parse the attributes from a payload from some messages. It is coded on + * baselines from the protocol version 4. + * This code is more or less dead but maybe we will need it in the future. + */ +static sftp_attributes sftp_parse_attr_4(sftp_session sftp, + ssh_buffer buf, + int expectnames) +{ + sftp_attributes attr = NULL; + ssh_string owner = NULL; + ssh_string group = NULL; + uint32_t flags = 0; + int ok = 0; + + /* unused member variable */ + (void) expectnames; + + attr = calloc(1, sizeof(struct sftp_attributes_struct)); + if (attr == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + /* This isn't really a loop, but it is like a try..catch.. */ + do { + if (ssh_buffer_get_u32(buf, &flags) != 4) { + break; + } + + flags = ntohl(flags); + attr->flags = flags; + + if (flags & SSH_FILEXFER_ATTR_SIZE) { + if (ssh_buffer_get_u64(buf, &attr->size) != 8) { + break; + } + attr->size = ntohll(attr->size); + } + + if (flags & SSH_FILEXFER_ATTR_OWNERGROUP) { + owner = ssh_buffer_get_ssh_string(buf); + if (owner == NULL) { + break; + } + attr->owner = ssh_string_to_char(owner); + SSH_STRING_FREE(owner); + if (attr->owner == NULL) { + break; + } + + group = ssh_buffer_get_ssh_string(buf); + if (group == NULL) { + break; + } + attr->group = ssh_string_to_char(group); + SSH_STRING_FREE(group); + if (attr->group == NULL) { + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + if (ssh_buffer_get_u32(buf, &attr->permissions) != 4) { + break; + } + attr->permissions = ntohl(attr->permissions); + + /* FIXME on windows! */ + switch (attr->permissions & SSH_S_IFMT) { + case SSH_S_IFSOCK: + case SSH_S_IFBLK: + case SSH_S_IFCHR: + case SSH_S_IFIFO: + attr->type = SSH_FILEXFER_TYPE_SPECIAL; + break; + case SSH_S_IFLNK: + attr->type = SSH_FILEXFER_TYPE_SYMLINK; + break; + case SSH_S_IFREG: + attr->type = SSH_FILEXFER_TYPE_REGULAR; + break; + case SSH_S_IFDIR: + attr->type = SSH_FILEXFER_TYPE_DIRECTORY; + break; + default: + attr->type = SSH_FILEXFER_TYPE_UNKNOWN; + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) { + if (ssh_buffer_get_u64(buf, &attr->atime64) != 8) { + break; + } + attr->atime64 = ntohll(attr->atime64); + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->atime_nseconds) != 4) { + break; + } + attr->atime_nseconds = ntohl(attr->atime_nseconds); + } + } + + if (flags & SSH_FILEXFER_ATTR_CREATETIME) { + if (ssh_buffer_get_u64(buf, &attr->createtime) != 8) { + break; + } + attr->createtime = ntohll(attr->createtime); + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->createtime_nseconds) != 4) { + break; + } + attr->createtime_nseconds = ntohl(attr->createtime_nseconds); + } + } + + if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) { + if (ssh_buffer_get_u64(buf, &attr->mtime64) != 8) { + break; + } + attr->mtime64 = ntohll(attr->mtime64); + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->mtime_nseconds) != 4) { + break; + } + attr->mtime_nseconds = ntohl(attr->mtime_nseconds); + } + } + + if (flags & SSH_FILEXFER_ATTR_ACL) { + if ((attr->acl = ssh_buffer_get_ssh_string(buf)) == NULL) { + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_EXTENDED) { + if (ssh_buffer_get_u32(buf,&attr->extended_count) != 4) { + break; + } + attr->extended_count = ntohl(attr->extended_count); + + while (attr->extended_count && + (attr->extended_type = ssh_buffer_get_ssh_string(buf)) && + (attr->extended_data = ssh_buffer_get_ssh_string(buf))) { + attr->extended_count--; + } + + if (attr->extended_count) { + break; + } + } + ok = 1; + } while (0); + + if (ok == 0) { + /* break issued somewhere */ + SSH_STRING_FREE(attr->acl); + SSH_STRING_FREE(attr->extended_type); + SSH_STRING_FREE(attr->extended_data); + SAFE_FREE(attr->owner); + SAFE_FREE(attr->group); + SAFE_FREE(attr); + + ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); + + return NULL; + } + + return attr; +} + +enum sftp_longname_field_e { + SFTP_LONGNAME_PERM = 0, + SFTP_LONGNAME_FIXME, + SFTP_LONGNAME_OWNER, + SFTP_LONGNAME_GROUP, + SFTP_LONGNAME_SIZE, + SFTP_LONGNAME_DATE, + SFTP_LONGNAME_TIME, + SFTP_LONGNAME_NAME, +}; + +static char * sftp_parse_longname(const char *longname, + enum sftp_longname_field_e longname_field) +{ + const char *p, *q; + size_t len, field = 0; + + p = longname; + /* + * Find the beginning of the field which is specified + * by sftp_longname_field_e. + */ + while (field != longname_field) { + if (isspace(*p)) { + field++; + p++; + while (*p && isspace(*p)) { + p++; + } + } else { + p++; + } + } + + q = p; + while (! isspace(*q)) { + q++; + } + + len = q - p; + + return strndup(p, len); +} + +/* sftp version 0-3 code. It is different from the v4 */ +/* maybe a paste of the draft is better than the code */ +/* + uint32 flags + uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE + uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID + uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID + uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS + uint32 atime present only if flag SSH_FILEXFER_ACMODTIME + uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME + uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED + string extended_type + string extended_data + ... more extended data (extended_type - extended_data pairs), + so that number of pairs equals extended_count */ +static sftp_attributes sftp_parse_attr_3(sftp_session sftp, + ssh_buffer buf, + int expectname) +{ + sftp_attributes attr; + int rc; + + attr = calloc(1, sizeof(struct sftp_attributes_struct)); + if (attr == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + if (expectname) { + rc = ssh_buffer_unpack(buf, "ss", + &attr->name, + &attr->longname); + if (rc != SSH_OK){ + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Name: %s", attr->name); + + /* Set owner and group if we talk to openssh and have the longname */ + if (ssh_get_openssh_version(sftp->session)) { + attr->owner = sftp_parse_longname(attr->longname, + SFTP_LONGNAME_OWNER); + if (attr->owner == NULL) { + goto error; + } + + attr->group = sftp_parse_longname(attr->longname, + SFTP_LONGNAME_GROUP); + if (attr->group == NULL) { + goto error; + } + } + } + + rc = ssh_buffer_unpack(buf, "d", &attr->flags); + if (rc != SSH_OK){ + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, + "Flags: %.8" PRIx32 "\n", attr->flags); + + if (attr->flags & SSH_FILEXFER_ATTR_SIZE) { + rc = ssh_buffer_unpack(buf, "q", &attr->size); + if(rc != SSH_OK) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, + "Size: %" PRIu64 "\n", + (uint64_t) attr->size); + } + + if (attr->flags & SSH_FILEXFER_ATTR_UIDGID) { + rc = ssh_buffer_unpack(buf, "dd", + &attr->uid, + &attr->gid); + if (rc != SSH_OK) { + goto error; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + rc = ssh_buffer_unpack(buf, "d", &attr->permissions); + if (rc != SSH_OK) { + goto error; + } + + switch (attr->permissions & SSH_S_IFMT) { + case SSH_S_IFSOCK: + case SSH_S_IFBLK: + case SSH_S_IFCHR: + case SSH_S_IFIFO: + attr->type = SSH_FILEXFER_TYPE_SPECIAL; + break; + case SSH_S_IFLNK: + attr->type = SSH_FILEXFER_TYPE_SYMLINK; + break; + case SSH_S_IFREG: + attr->type = SSH_FILEXFER_TYPE_REGULAR; + break; + case SSH_S_IFDIR: + attr->type = SSH_FILEXFER_TYPE_DIRECTORY; + break; + default: + attr->type = SSH_FILEXFER_TYPE_UNKNOWN; + break; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_ACMODTIME) { + rc = ssh_buffer_unpack(buf, "dd", + &attr->atime, + &attr->mtime); + if (rc != SSH_OK) { + goto error; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_EXTENDED) { + rc = ssh_buffer_unpack(buf, "d", &attr->extended_count); + if (rc != SSH_OK) { + goto error; + } + + if (attr->extended_count > 0) { + rc = ssh_buffer_unpack(buf, "ss", + &attr->extended_type, + &attr->extended_data); + if (rc != SSH_OK) { + goto error; + } + attr->extended_count--; + } + /* just ignore the remaining extensions */ + + while (attr->extended_count > 0) { + ssh_string tmp1,tmp2; + rc = ssh_buffer_unpack(buf, "SS", &tmp1, &tmp2); + if (rc != SSH_OK){ + goto error; + } + SAFE_FREE(tmp1); + SAFE_FREE(tmp2); + attr->extended_count--; + } + } + + return attr; + +error: + SSH_STRING_FREE(attr->extended_type); + SSH_STRING_FREE(attr->extended_data); + SAFE_FREE(attr->name); + SAFE_FREE(attr->longname); + SAFE_FREE(attr->owner); + SAFE_FREE(attr->group); + SAFE_FREE(attr); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); + sftp_set_error(sftp, SSH_FX_FAILURE); + + return NULL; +} + +sftp_attributes sftp_parse_attr(sftp_session session, + ssh_buffer buf, + int expectname) +{ + switch (session->version) { + case 4: + return sftp_parse_attr_4(session, buf, expectname); + case 3: + case 2: + case 1: + case 0: + return sftp_parse_attr_3(session, buf, expectname); + default: + ssh_set_error(session->session, SSH_FATAL, + "Version %d unsupported by client", + session->server_version); + return NULL; + } + + return NULL; +} + +void sftp_set_error(sftp_session sftp, int errnum) +{ + if (sftp != NULL) { + sftp->errnum = errnum; + } +} + +void sftp_message_free(sftp_message msg) +{ + if (msg == NULL) { + return; + } + + SSH_BUFFER_FREE(msg->payload); + SAFE_FREE(msg); +} + +static sftp_request_queue request_queue_new(sftp_message msg) +{ + sftp_request_queue queue = NULL; + + queue = calloc(1, sizeof(struct sftp_request_queue_struct)); + if (queue == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + queue->message = msg; + + return queue; +} + +static void request_queue_free(sftp_request_queue queue) +{ + if (queue == NULL) { + return; + } + + ZERO_STRUCTP(queue); + SAFE_FREE(queue); +} + +static int sftp_enqueue(sftp_session sftp, sftp_message msg) +{ + sftp_request_queue queue = NULL; + sftp_request_queue ptr; + + queue = request_queue_new(msg); + if (queue == NULL) { + return -1; + } + + SSH_LOG(SSH_LOG_PACKET, + "Queued msg id %" PRIu32 " type %d", + msg->id, msg->packet_type); + + if(sftp->queue == NULL) { + sftp->queue = queue; + } else { + ptr = sftp->queue; + while(ptr->next) { + ptr=ptr->next; /* find end of linked list */ + } + ptr->next = queue; /* add it on bottom */ + } + + return 0; +} + +/* + * Pulls a message from the queue based on the ID. + * Returns NULL if no message has been found. + */ +sftp_message sftp_dequeue(sftp_session sftp, uint32_t id) +{ + sftp_request_queue prev = NULL; + sftp_request_queue queue; + sftp_message msg; + + if(sftp->queue == NULL) { + return NULL; + } + + queue = sftp->queue; + while (queue) { + if (queue->message->id == id) { + /* remove from queue */ + if (prev == NULL) { + sftp->queue = queue->next; + } else { + prev->next = queue->next; + } + msg = queue->message; + request_queue_free(queue); + SSH_LOG(SSH_LOG_PACKET, + "Dequeued msg id %" PRIu32 " type %d", + msg->id, + msg->packet_type); + return msg; + } + prev = queue; + queue = queue->next; + } + + return NULL; +} + +static sftp_message sftp_get_message(sftp_packet packet) +{ + sftp_session sftp = packet->sftp; + sftp_message msg = NULL; + int rc; + + switch (packet->type) { + case SSH_FXP_STATUS: + case SSH_FXP_HANDLE: + case SSH_FXP_DATA: + case SSH_FXP_ATTRS: + case SSH_FXP_NAME: + case SSH_FXP_EXTENDED_REPLY: + break; + default: + ssh_set_error(packet->sftp->session, + SSH_FATAL, + "Unknown packet type %d", + packet->type); + sftp_set_error(packet->sftp, SSH_FX_FAILURE); + return NULL; + } + + msg = calloc(1, sizeof(struct sftp_message_struct)); + if (msg == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(packet->sftp, SSH_FX_FAILURE); + return NULL; + } + + msg->sftp = packet->sftp; + msg->packet_type = packet->type; + + /* Move the payload from the packet to the message */ + msg->payload = packet->payload; + packet->payload = NULL; + + rc = ssh_buffer_unpack(msg->payload, "d", &msg->id); + if (rc != SSH_OK) { + ssh_set_error(packet->sftp->session, SSH_FATAL, + "Invalid packet %d: no ID", packet->type); + sftp_message_free(msg); + sftp_set_error(packet->sftp, SSH_FX_FAILURE); + return NULL; + } + + SSH_LOG(SSH_LOG_PACKET, + "Packet with id %" PRIu32 " type %d", + msg->id, + msg->packet_type); + + return msg; +} + +int sftp_read_and_dispatch(sftp_session sftp) +{ + sftp_packet packet = NULL; + sftp_message msg = NULL; + + packet = sftp_packet_read(sftp); + if (packet == NULL) { + /* something nasty happened reading the packet */ + return -1; + } + + msg = sftp_get_message(packet); + if (msg == NULL) { + return -1; + } + + if (sftp_enqueue(sftp, msg) < 0) { + sftp_message_free(msg); + return -1; + } + + return 0; +} + +sftp_status_message parse_status_msg(sftp_message msg) +{ + sftp_status_message status = NULL; + int rc; + + if (msg->packet_type != SSH_FXP_STATUS) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Not a ssh_fxp_status message passed in!"); + sftp_set_error(msg->sftp, SSH_FX_BAD_MESSAGE); + return NULL; + } + + status = calloc(1, sizeof(struct sftp_status_message_struct)); + if (status == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + status->id = msg->id; + rc = ssh_buffer_unpack(msg->payload, "d", + &status->status); + if (rc != SSH_OK) { + SAFE_FREE(status); + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_STATUS message"); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_unpack(msg->payload, "ss", + &status->errormsg, + &status->langmsg); + + if (rc != SSH_OK && msg->sftp->version >= 3) { + /* These are mandatory from version 3 */ + SAFE_FREE(status); + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_STATUS message"); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + if (status->errormsg == NULL) + status->errormsg = strdup("No error message in packet"); + + if (status->langmsg == NULL) + status->langmsg = strdup(""); + + if (status->errormsg == NULL || status->langmsg == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + status_msg_free(status); + return NULL; + } + + return status; +} + +void status_msg_free(sftp_status_message status) +{ + if (status == NULL) { + return; + } + + SAFE_FREE(status->errormsg); + SAFE_FREE(status->langmsg); + SAFE_FREE(status); +} + +#endif /* WITH_SFTP */ diff --git a/src/sftpserver.c b/src/sftpserver.c index 9117f155..7d8070b1 100644 --- a/src/sftpserver.c +++ b/src/sftpserver.c @@ -3,7 +3,8 @@ * * This file is part of the SSH Library * - * Copyright (c) 2005 by Aris Adamantiadis + * Copyright (c) 2005 Aris Adamantiadis + * Copyright (c) 2022 Zeyu Sheng <shengzeyu19_98@163.com> * * The SSH Library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -23,16 +24,23 @@ #include "config.h" -#include <stdio.h> #ifndef _WIN32 #include <netinet/in.h> #include <arpa/inet.h> +#include <dirent.h> +#include <sys/statvfs.h> #endif +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/stat.h> +#include <time.h> #include "libssh/libssh.h" #include "libssh/sftp.h" #include "libssh/sftp_priv.h" +#include "libssh/sftpserver.h" #include "libssh/ssh2.h" #include "libssh/priv.h" #include "libssh/buffer.h" @@ -40,518 +48,1720 @@ #define SFTP_HANDLES 256 -sftp_client_message sftp_get_client_message(sftp_session sftp) { - ssh_session session = sftp->session; - sftp_packet packet; - sftp_client_message msg; - ssh_buffer payload; - int rc; +#define MAX_ENTRIES_NUM_IN_PACKET 50 +#define MAX_LONG_NAME_LEN 350 - msg = malloc(sizeof (struct sftp_client_message_struct)); - if (msg == NULL) { - ssh_set_error_oom(session); - return NULL; - } - ZERO_STRUCTP(msg); +static sftp_client_message +sftp_make_client_message(sftp_session sftp, sftp_packet packet) +{ + ssh_session session = sftp->session; + sftp_client_message msg = NULL; + ssh_buffer payload = NULL; + int rc; + int version; + + msg = calloc(1, sizeof(struct sftp_client_message_struct)); + if (msg == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + payload = packet->payload; + msg->type = packet->type; + msg->sftp = sftp; + + /* take a copy of the whole packet */ + msg->complete_message = ssh_buffer_new(); + if (msg->complete_message == NULL) { + ssh_set_error_oom(session); + goto error; + } - packet = sftp_packet_read(sftp); - if (packet == NULL) { - ssh_set_error_oom(session); + rc = ssh_buffer_add_data(msg->complete_message, + ssh_buffer_get(payload), + ssh_buffer_get_len(payload)); + if (rc < 0) { + goto error; + } + + if (msg->type != SSH_FXP_INIT) { + rc = ssh_buffer_get_u32(payload, &msg->id); + if (rc != sizeof(uint32_t)) { + goto error; + } + } + + switch (msg->type) { + case SSH_FXP_INIT: + rc = ssh_buffer_unpack(payload, + "d", + &version); + if (rc != SSH_OK) { + printf("unpack init failed!\n"); + goto error; + } + version = ntohl(version); + sftp->client_version = version; + break; + case SSH_FXP_CLOSE: + case SSH_FXP_READDIR: + msg->handle = ssh_buffer_get_ssh_string(payload); + if (msg->handle == NULL) { + goto error; + } + break; + case SSH_FXP_READ: + rc = ssh_buffer_unpack(payload, + "Sqd", + &msg->handle, + &msg->offset, + &msg->len); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_WRITE: + rc = ssh_buffer_unpack(payload, + "SqS", + &msg->handle, + &msg->offset, + &msg->data); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_REMOVE: + case SSH_FXP_RMDIR: + case SSH_FXP_OPENDIR: + case SSH_FXP_READLINK: + case SSH_FXP_REALPATH: + rc = ssh_buffer_unpack(payload, + "s", + &msg->filename); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_RENAME: + case SSH_FXP_SYMLINK: + rc = ssh_buffer_unpack(payload, + "sS", + &msg->filename, + &msg->data); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_MKDIR: + case SSH_FXP_SETSTAT: + rc = ssh_buffer_unpack(payload, + "s", + &msg->filename); + if (rc != SSH_OK) { + goto error; + } + msg->attr = sftp_parse_attr(sftp, payload, 0); + if (msg->attr == NULL) { + goto error; + } + break; + case SSH_FXP_FSETSTAT: + msg->handle = ssh_buffer_get_ssh_string(payload); + if (msg->handle == NULL) { + goto error; + } + msg->attr = sftp_parse_attr(sftp, payload, 0); + if (msg->attr == NULL) { + goto error; + } + break; + case SSH_FXP_LSTAT: + case SSH_FXP_STAT: + rc = ssh_buffer_unpack(payload, + "s", + &msg->filename); + if (rc != SSH_OK) { + goto error; + } + if (sftp->version > 3) { + ssh_buffer_unpack(payload, "d", &msg->flags); + } + break; + case SSH_FXP_OPEN: + rc = ssh_buffer_unpack(payload, + "sd", + &msg->filename, + &msg->flags); + if (rc != SSH_OK) { + goto error; + } + msg->attr = sftp_parse_attr(sftp, payload, 0); + if (msg->attr == NULL) { + goto error; + } + break; + case SSH_FXP_FSTAT: + rc = ssh_buffer_unpack(payload, + "S", + &msg->handle); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_EXTENDED: + rc = ssh_buffer_unpack(payload, + "s", + &msg->submessage); + if (rc != SSH_OK) { + goto error; + } + + if (strcmp(msg->submessage, "hardlink@openssh.com") == 0 || + strcmp(msg->submessage, "posix-rename@openssh.com") == 0) { + rc = ssh_buffer_unpack(payload, + "sS", + &msg->filename, + &msg->data); + if (rc != SSH_OK) { + goto error; + } + } else if (strcmp(msg->submessage, "statvfs@openssh.com") == 0 ){ + rc = ssh_buffer_unpack(payload, + "s", + &msg->filename); + if (rc != SSH_OK) { + goto error; + } + } + break; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received unhandled sftp message %d", msg->type); + goto error; + } + + return msg; + +error: sftp_client_message_free(msg); return NULL; - } - - payload = packet->payload; - msg->type = packet->type; - msg->sftp = sftp; +} - /* take a copy of the whole packet */ - msg->complete_message = ssh_buffer_new(); - if (msg->complete_message == NULL) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } +sftp_client_message sftp_get_client_message(sftp_session sftp) +{ + ssh_session session = sftp->session; + sftp_packet packet; - rc = ssh_buffer_add_data(msg->complete_message, - ssh_buffer_get(payload), - ssh_buffer_get_len(payload)); - if (rc < 0) { + packet = sftp_packet_read(sftp); + if (packet == NULL) { ssh_set_error_oom(session); - sftp_client_message_free(msg); return NULL; - } + } + return sftp_make_client_message(sftp, packet); +} - ssh_buffer_get_u32(payload, &msg->id); +/** + * @brief Get the client message from a sftp packet. + * + * @param sftp The sftp session handle. + * + * @return The pointer to the generated sftp client message. + */ +static sftp_client_message +sftp_get_client_message_from_packet(sftp_session sftp) +{ + sftp_packet packet = NULL; - switch(msg->type) { - case SSH_FXP_CLOSE: - case SSH_FXP_READDIR: - msg->handle = ssh_buffer_get_ssh_string(payload); - if (msg->handle == NULL) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_READ: - rc = ssh_buffer_unpack(payload, - "Sqd", - &msg->handle, - &msg->offset, - &msg->len); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_WRITE: - rc = ssh_buffer_unpack(payload, - "SqS", - &msg->handle, - &msg->offset, - &msg->data); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_REMOVE: - case SSH_FXP_RMDIR: - case SSH_FXP_OPENDIR: - case SSH_FXP_READLINK: - case SSH_FXP_REALPATH: - rc = ssh_buffer_unpack(payload, - "s", - &msg->filename); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_RENAME: - case SSH_FXP_SYMLINK: - rc = ssh_buffer_unpack(payload, - "sS", - &msg->filename, - &msg->data); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_MKDIR: - case SSH_FXP_SETSTAT: - rc = ssh_buffer_unpack(payload, - "s", - &msg->filename); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - msg->attr = sftp_parse_attr(sftp, payload, 0); - if (msg->attr == NULL) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_FSETSTAT: - msg->handle = ssh_buffer_get_ssh_string(payload); - if (msg->handle == NULL) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - msg->attr = sftp_parse_attr(sftp, payload, 0); - if (msg->attr == NULL) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_LSTAT: - case SSH_FXP_STAT: - rc = ssh_buffer_unpack(payload, - "s", - &msg->filename); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - if(sftp->version > 3) { - ssh_buffer_unpack(payload, "d", &msg->flags); - } - break; - case SSH_FXP_OPEN: - rc = ssh_buffer_unpack(payload, - "sd", - &msg->filename, - &msg->flags); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - msg->attr = sftp_parse_attr(sftp, payload, 0); - if (msg->attr == NULL) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_FSTAT: - rc = ssh_buffer_unpack(payload, - "S", - &msg->handle); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - break; - case SSH_FXP_EXTENDED: - rc = ssh_buffer_unpack(payload, - "s", - &msg->submessage); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); + packet = sftp->read_packet; + if (packet == NULL) { return NULL; - } - - if (strcmp(msg->submessage, "hardlink@openssh.com") == 0 || - strcmp(msg->submessage, "posix-rename@openssh.com") == 0) { - rc = ssh_buffer_unpack(payload, - "sS", - &msg->filename, - &msg->data); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - sftp_client_message_free(msg); - return NULL; - } - } - break; - default: - ssh_set_error(sftp->session, SSH_FATAL, - "Received unhandled sftp message %d", msg->type); - sftp_client_message_free(msg); - return NULL; - } - - return msg; + } + return sftp_make_client_message(sftp, packet); } -/* Send an sftp client message. Can be used in cas of proxying */ -int sftp_send_client_message(sftp_session sftp, sftp_client_message msg){ - return sftp_packet_write(sftp, msg->type, msg->complete_message); +/* Send an sftp client message. Can be used in case of proxying */ +int sftp_send_client_message(sftp_session sftp, sftp_client_message msg) +{ + return sftp_packet_write(sftp, msg->type, msg->complete_message); } -uint8_t sftp_client_message_get_type(sftp_client_message msg){ - return msg->type; +uint8_t sftp_client_message_get_type(sftp_client_message msg) +{ + return msg->type; } -const char *sftp_client_message_get_filename(sftp_client_message msg){ - return msg->filename; +const char *sftp_client_message_get_filename(sftp_client_message msg) +{ + return msg->filename; } -void sftp_client_message_set_filename(sftp_client_message msg, const char *newname){ - free(msg->filename); - msg->filename = strdup(newname); +void +sftp_client_message_set_filename(sftp_client_message msg, const char *newname) +{ + free(msg->filename); + msg->filename = strdup(newname); } -const char *sftp_client_message_get_data(sftp_client_message msg){ - if (msg->str_data == NULL) - msg->str_data = ssh_string_to_char(msg->data); - return msg->str_data; +const char *sftp_client_message_get_data(sftp_client_message msg) +{ + if (msg->str_data == NULL) + msg->str_data = ssh_string_to_char(msg->data); + return msg->str_data; } -uint32_t sftp_client_message_get_flags(sftp_client_message msg){ - return msg->flags; +uint32_t sftp_client_message_get_flags(sftp_client_message msg) +{ + return msg->flags; } -const char *sftp_client_message_get_submessage(sftp_client_message msg){ - return msg->submessage; +const char *sftp_client_message_get_submessage(sftp_client_message msg) +{ + return msg->submessage; } -void sftp_client_message_free(sftp_client_message msg) { - if (msg == NULL) { - return; - } +void sftp_client_message_free(sftp_client_message msg) +{ + if (msg == NULL) { + return; + } - SAFE_FREE(msg->filename); - SAFE_FREE(msg->submessage); - SSH_STRING_FREE(msg->data); - SSH_STRING_FREE(msg->handle); - sftp_attributes_free(msg->attr); - SSH_BUFFER_FREE(msg->complete_message); - SAFE_FREE(msg->str_data); - ZERO_STRUCTP(msg); - SAFE_FREE(msg); + SAFE_FREE(msg->filename); + SAFE_FREE(msg->submessage); + SSH_STRING_FREE(msg->data); + SSH_STRING_FREE(msg->handle); + sftp_attributes_free(msg->attr); + SSH_BUFFER_FREE(msg->complete_message); + SAFE_FREE(msg->str_data); + ZERO_STRUCTP(msg); + SAFE_FREE(msg); } -int sftp_reply_name(sftp_client_message msg, const char *name, - sftp_attributes attr) { - ssh_buffer out; - ssh_string file; +int +sftp_reply_name(sftp_client_message msg, const char *name, sftp_attributes attr) +{ + ssh_buffer out; + ssh_string file; - out = ssh_buffer_new(); - if (out == NULL) { - return -1; - } + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } - file = ssh_string_from_char(name); - if (file == NULL) { - SSH_BUFFER_FREE(out); - return -1; - } - - if (ssh_buffer_add_u32(out, msg->id) < 0 || - ssh_buffer_add_u32(out, htonl(1)) < 0 || - ssh_buffer_add_ssh_string(out, file) < 0 || - ssh_buffer_add_ssh_string(out, file) < 0 || /* The protocol is broken here between 3 & 4 */ - buffer_add_attributes(out, attr) < 0 || - sftp_packet_write(msg->sftp, SSH_FXP_NAME, out) < 0) { + file = ssh_string_from_char(name); + if (file == NULL) { + SSH_BUFFER_FREE(out); + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending name %s", ssh_string_get_char(file)); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u32(out, htonl(1)) < 0 || + ssh_buffer_add_ssh_string(out, file) < 0 || + ssh_buffer_add_ssh_string(out, file) < 0 || /* The protocol is broken here between 3 & 4 */ + buffer_add_attributes(out, attr) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_NAME, out) < 0) { + SSH_BUFFER_FREE(out); + SSH_STRING_FREE(file); + return -1; + } SSH_BUFFER_FREE(out); SSH_STRING_FREE(file); - return -1; - } - SSH_BUFFER_FREE(out); - SSH_STRING_FREE(file); - return 0; + return 0; } -int sftp_reply_handle(sftp_client_message msg, ssh_string handle){ - ssh_buffer out; +int sftp_reply_handle(sftp_client_message msg, ssh_string handle) +{ + ssh_buffer out; - out = ssh_buffer_new(); - if (out == NULL) { - return -1; - } + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } - if (ssh_buffer_add_u32(out, msg->id) < 0 || - ssh_buffer_add_ssh_string(out, handle) < 0 || - sftp_packet_write(msg->sftp, SSH_FXP_HANDLE, out) < 0) { + ssh_log_hexdump("Sending handle:", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_ssh_string(out, handle) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_HANDLE, out) < 0) { + SSH_BUFFER_FREE(out); + return -1; + } SSH_BUFFER_FREE(out); - return -1; - } - SSH_BUFFER_FREE(out); - return 0; + return 0; } -int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr) { - ssh_buffer out; +int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr) +{ + ssh_buffer out; - out = ssh_buffer_new(); - if (out == NULL) { - return -1; - } + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } - if (ssh_buffer_add_u32(out, msg->id) < 0 || - buffer_add_attributes(out, attr) < 0 || - sftp_packet_write(msg->sftp, SSH_FXP_ATTRS, out) < 0) { + SSH_LOG(SSH_LOG_PROTOCOL, "Sending attr"); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + buffer_add_attributes(out, attr) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_ATTRS, out) < 0) { + SSH_BUFFER_FREE(out); + return -1; + } SSH_BUFFER_FREE(out); - return -1; - } - SSH_BUFFER_FREE(out); - return 0; + return 0; } -int sftp_reply_names_add(sftp_client_message msg, const char *file, - const char *longname, sftp_attributes attr) { - ssh_string name; +int +sftp_reply_names_add(sftp_client_message msg, const char *file, + const char *longname, sftp_attributes attr) +{ + ssh_string name; - name = ssh_string_from_char(file); - if (name == NULL) { - return -1; - } + name = ssh_string_from_char(file); + if (name == NULL) { + return -1; + } - if (msg->attrbuf == NULL) { - msg->attrbuf = ssh_buffer_new(); if (msg->attrbuf == NULL) { - SSH_STRING_FREE(name); - return -1; + msg->attrbuf = ssh_buffer_new(); + if (msg->attrbuf == NULL) { + SSH_STRING_FREE(name); + return -1; + } } - } - if (ssh_buffer_add_ssh_string(msg->attrbuf, name) < 0) { - SSH_STRING_FREE(name); - return -1; - } + if (ssh_buffer_add_ssh_string(msg->attrbuf, name) < 0) { + SSH_STRING_FREE(name); + return -1; + } - SSH_STRING_FREE(name); - name = ssh_string_from_char(longname); - if (name == NULL) { - return -1; - } - if (ssh_buffer_add_ssh_string(msg->attrbuf,name) < 0 || - buffer_add_attributes(msg->attrbuf,attr) < 0) { SSH_STRING_FREE(name); - return -1; - } - SSH_STRING_FREE(name); - msg->attr_num++; + name = ssh_string_from_char(longname); + if (name == NULL) { + return -1; + } + if (ssh_buffer_add_ssh_string(msg->attrbuf, name) < 0 || + buffer_add_attributes(msg->attrbuf, attr) < 0) { + SSH_STRING_FREE(name); + return -1; + } + SSH_STRING_FREE(name); + msg->attr_num++; - return 0; + return 0; } -int sftp_reply_names(sftp_client_message msg) { - ssh_buffer out; +int sftp_reply_names(sftp_client_message msg) +{ + ssh_buffer out; - out = ssh_buffer_new(); - if (out == NULL) { - SSH_BUFFER_FREE(msg->attrbuf); - return -1; - } + out = ssh_buffer_new(); + if (out == NULL) { + SSH_BUFFER_FREE(msg->attrbuf); + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending %d names", msg->attr_num); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u32(out, htonl(msg->attr_num)) < 0 || + ssh_buffer_add_data(out, ssh_buffer_get(msg->attrbuf), + ssh_buffer_get_len(msg->attrbuf)) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_NAME, out) < 0) { + SSH_BUFFER_FREE(out); + SSH_BUFFER_FREE(msg->attrbuf); + return -1; + } - if (ssh_buffer_add_u32(out, msg->id) < 0 || - ssh_buffer_add_u32(out, htonl(msg->attr_num)) < 0 || - ssh_buffer_add_data(out, ssh_buffer_get(msg->attrbuf), - ssh_buffer_get_len(msg->attrbuf)) < 0 || - sftp_packet_write(msg->sftp, SSH_FXP_NAME, out) < 0) { SSH_BUFFER_FREE(out); SSH_BUFFER_FREE(msg->attrbuf); - return -1; - } - - SSH_BUFFER_FREE(out); - SSH_BUFFER_FREE(msg->attrbuf); - msg->attr_num = 0; - msg->attrbuf = NULL; + msg->attr_num = 0; + msg->attrbuf = NULL; - return 0; + return 0; } -int sftp_reply_status(sftp_client_message msg, uint32_t status, - const char *message) { - ssh_buffer out; - ssh_string s; +int +sftp_reply_status(sftp_client_message msg, uint32_t status, const char *message) +{ + ssh_buffer out; + ssh_string s; - out = ssh_buffer_new(); - if (out == NULL) { - return -1; - } + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } - s = ssh_string_from_char(message ? message : ""); - if (s == NULL) { - SSH_BUFFER_FREE(out); - return -1; - } + s = ssh_string_from_char(message ? message : ""); + if (s == NULL) { + SSH_BUFFER_FREE(out); + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending status %d, message: %s", status, + ssh_string_get_char(s)); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u32(out, htonl(status)) < 0 || + ssh_buffer_add_ssh_string(out, s) < 0 || + ssh_buffer_add_u32(out, 0) < 0 || /* language string */ + sftp_packet_write(msg->sftp, SSH_FXP_STATUS, out) < 0) { + SSH_BUFFER_FREE(out); + SSH_STRING_FREE(s); + return -1; + } - if (ssh_buffer_add_u32(out, msg->id) < 0 || - ssh_buffer_add_u32(out, htonl(status)) < 0 || - ssh_buffer_add_ssh_string(out, s) < 0 || - ssh_buffer_add_u32(out, 0) < 0 || /* language string */ - sftp_packet_write(msg->sftp, SSH_FXP_STATUS, out) < 0) { SSH_BUFFER_FREE(out); SSH_STRING_FREE(s); - return -1; - } - - SSH_BUFFER_FREE(out); - SSH_STRING_FREE(s); - return 0; + return 0; } -int sftp_reply_data(sftp_client_message msg, const void *data, int len) { - ssh_buffer out; +int sftp_reply_data(sftp_client_message msg, const void *data, int len) +{ + ssh_buffer out; - out = ssh_buffer_new(); - if (out == NULL) { - return -1; - } + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } - if (ssh_buffer_add_u32(out, msg->id) < 0 || - ssh_buffer_add_u32(out, ntohl(len)) < 0 || - ssh_buffer_add_data(out, data, len) < 0 || - sftp_packet_write(msg->sftp, SSH_FXP_DATA, out) < 0) { + SSH_LOG(SSH_LOG_PROTOCOL, "Sending data, length: %d", len); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u32(out, ntohl(len)) < 0 || + ssh_buffer_add_data(out, data, len) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_DATA, out) < 0) { + SSH_BUFFER_FREE(out); + return -1; + } SSH_BUFFER_FREE(out); - return -1; - } - SSH_BUFFER_FREE(out); - return 0; + return 0; +} + +/** + * @brief Handle the statvfs request, return information the mounted file system. + * + * @param msg The sftp client message. + * + * @param st The statvfs state of target file. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +static int +sftp_reply_statvfs(sftp_client_message msg, sftp_statvfs_t st) +{ + int ret = 0; + ssh_buffer out; + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending statvfs reply"); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_bsize)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_frsize)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_blocks)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_bfree)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_bavail)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_files)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_ffree)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_favail)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_fsid)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_flag)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_namemax)) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_EXTENDED_REPLY, out) < 0) { + ret = -1; + } + SSH_BUFFER_FREE(out); + + return ret; +} + +int sftp_reply_version(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_session session = sftp->session; + int version; + ssh_buffer reply; + int rc; + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending version packet"); + + version = sftp->client_version; + reply = ssh_buffer_new(); + if (reply == NULL) { + ssh_set_error_oom(session); + return -1; + } + + rc = ssh_buffer_pack(reply, "dssssss", + LIBSFTP_VERSION, + "posix-rename@openssh.com", + "1", + "hardlink@openssh.com", + "1", + "statvfs@openssh.com", + "2"); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(reply); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_VERSION, reply); + if (rc < 0) { + SSH_BUFFER_FREE(reply); + return -1; + } + SSH_BUFFER_FREE(reply); + + SSH_LOG(SSH_LOG_PROTOCOL, "Server version sent"); + + if (version > LIBSFTP_VERSION) { + sftp->version = LIBSFTP_VERSION; + } else { + sftp->version = version; + } + + return SSH_OK; } + /* * This function will return you a new handle to give the client. * the function accepts an info that can be retrieved later with * the handle. Care is given that a corrupted handle won't give a * valid info (or worse). */ -ssh_string sftp_handle_alloc(sftp_session sftp, void *info) { - ssh_string ret; - uint32_t val; - uint32_t i; +ssh_string sftp_handle_alloc(sftp_session sftp, void *info) +{ + ssh_string ret; + uint32_t val; + uint32_t i; - if (sftp->handles == NULL) { - sftp->handles = calloc(SFTP_HANDLES, sizeof(void *)); if (sftp->handles == NULL) { - return NULL; + sftp->handles = calloc(SFTP_HANDLES, sizeof(void *)); + if (sftp->handles == NULL) { + return NULL; + } } - } - for (i = 0; i < SFTP_HANDLES; i++) { - if (sftp->handles[i] == NULL) { - break; + for (i = 0; i < SFTP_HANDLES; i++) { + if (sftp->handles[i] == NULL) { + break; + } } - } - if (i == SFTP_HANDLES) { - return NULL; /* no handle available */ - } + if (i == SFTP_HANDLES) { + return NULL; /* no handle available */ + } - val = i; - ret = ssh_string_new(4); - if (ret == NULL) { - return NULL; - } + val = i; + ret = ssh_string_new(4); + if (ret == NULL) { + return NULL; + } - memcpy(ssh_string_data(ret), &val, sizeof(uint32_t)); - sftp->handles[i] = info; + memcpy(ssh_string_data(ret), &val, sizeof(uint32_t)); + sftp->handles[i] = info; - return ret; + return ret; } -void *sftp_handle(sftp_session sftp, ssh_string handle){ - uint32_t val; +void *sftp_handle(sftp_session sftp, ssh_string handle) +{ + uint32_t val; - if (sftp->handles == NULL) { - return NULL; - } + if (sftp->handles == NULL) { + return NULL; + } - if (ssh_string_len(handle) != sizeof(uint32_t)) { - return NULL; - } + if (ssh_string_len(handle) != sizeof(uint32_t)) { + return NULL; + } - memcpy(&val, ssh_string_data(handle), sizeof(uint32_t)); + memcpy(&val, ssh_string_data(handle), sizeof(uint32_t)); - if (val > SFTP_HANDLES) { - return NULL; - } + if (val > SFTP_HANDLES) { + return NULL; + } + + return sftp->handles[val]; +} + +void sftp_handle_remove(sftp_session sftp, void *handle) +{ + int i; + + for (i = 0; i < SFTP_HANDLES; i++) { + if (sftp->handles[i] == handle) { + sftp->handles[i] = NULL; + break; + } + } +} + +/* Default SFTP handlers */ + +static const char * +ssh_str_error(int u_errno) +{ + switch (u_errno) { + case SSH_FX_NO_SUCH_FILE: + return "No such file"; + case SSH_FX_PERMISSION_DENIED: + return "Permission denied"; + case SSH_FX_BAD_MESSAGE: + return "Bad message"; + case SSH_FX_OP_UNSUPPORTED: + return "Operation not supported"; + default: + return "Operation failed"; + } +} + +static int +unix_errno_to_ssh_stat(int u_errno) +{ + int ret = SSH_OK; + switch (u_errno) { + case 0: + break; + case ENOENT: + case ENOTDIR: + case EBADF: + case ELOOP: + ret = SSH_FX_NO_SUCH_FILE; + break; + case EPERM: + case EACCES: + case EFAULT: + ret = SSH_FX_PERMISSION_DENIED; + break; + case ENAMETOOLONG: + case EINVAL: + ret = SSH_FX_BAD_MESSAGE; + break; + case ENOSYS: + ret = SSH_FX_OP_UNSUPPORTED; + break; + default: + ret = SSH_FX_FAILURE; + break; + } + + return ret; +} + +static void +stat_to_filexfer_attrib(const struct stat *z_st, struct sftp_attributes_struct *z_attr) +{ + z_attr->flags = 0 | (uint32_t)SSH_FILEXFER_ATTR_SIZE; + z_attr->size = z_st->st_size; + + z_attr->flags |= (uint32_t)SSH_FILEXFER_ATTR_UIDGID; + z_attr->uid = z_st->st_uid; + z_attr->gid = z_st->st_gid; - return sftp->handles[val]; + z_attr->flags |= (uint32_t)SSH_FILEXFER_ATTR_PERMISSIONS; + z_attr->permissions = z_st->st_mode; + + z_attr->flags |= (uint32_t)SSH_FILEXFER_ATTR_ACMODTIME; + z_attr->atime = z_st->st_atime; + z_attr->mtime = z_st->st_mtime; +} + +static void +clear_filexfer_attrib(struct sftp_attributes_struct *z_attr) +{ + z_attr->flags = 0; + z_attr->size = 0; + z_attr->uid = 0; + z_attr->gid = 0; + z_attr->permissions = 0; + z_attr->atime = 0; + z_attr->mtime = 0; +} + +#ifndef _WIN32 +/* internal */ +enum sftp_handle_type +{ + SFTP_NULL_HANDLE, + SFTP_DIR_HANDLE, + SFTP_FILE_HANDLE +}; + +struct sftp_handle +{ + enum sftp_handle_type type; + int fd; + DIR *dirp; + char *name; +}; + +SSH_SFTP_CALLBACK(process_unsupposed); +SSH_SFTP_CALLBACK(process_open); +SSH_SFTP_CALLBACK(process_read); +SSH_SFTP_CALLBACK(process_write); +SSH_SFTP_CALLBACK(process_close); +SSH_SFTP_CALLBACK(process_opendir); +SSH_SFTP_CALLBACK(process_readdir); +SSH_SFTP_CALLBACK(process_rmdir); +SSH_SFTP_CALLBACK(process_realpath); +SSH_SFTP_CALLBACK(process_mkdir); +SSH_SFTP_CALLBACK(process_lstat); +SSH_SFTP_CALLBACK(process_stat); +SSH_SFTP_CALLBACK(process_readlink); +SSH_SFTP_CALLBACK(process_symlink); +SSH_SFTP_CALLBACK(process_remove); +SSH_SFTP_CALLBACK(process_extended_statvfs); + +const struct sftp_message_handler message_handlers[] = { + {"open", NULL, SSH_FXP_OPEN, process_open}, + {"close", NULL, SSH_FXP_CLOSE, process_close}, + {"read", NULL, SSH_FXP_READ, process_read}, + {"write", NULL, SSH_FXP_WRITE, process_write}, + {"lstat", NULL, SSH_FXP_LSTAT, process_lstat}, + {"fstat", NULL, SSH_FXP_FSTAT, process_unsupposed}, + {"setstat", NULL, SSH_FXP_SETSTAT, process_unsupposed}, + {"fsetstat", NULL, SSH_FXP_FSETSTAT, process_unsupposed}, + {"opendir", NULL, SSH_FXP_OPENDIR, process_opendir}, + {"readdir", NULL, SSH_FXP_READDIR, process_readdir}, + {"remove", NULL, SSH_FXP_REMOVE, process_remove}, + {"mkdir", NULL, SSH_FXP_MKDIR, process_mkdir}, + {"rmdir", NULL, SSH_FXP_RMDIR, process_rmdir}, + {"realpath", NULL, SSH_FXP_REALPATH, process_realpath}, + {"stat", NULL, SSH_FXP_STAT, process_stat}, + {"rename", NULL, SSH_FXP_RENAME, process_unsupposed}, + {"readlink", NULL, SSH_FXP_READLINK, process_readlink}, + {"symlink", NULL, SSH_FXP_SYMLINK, process_symlink}, + {"init", NULL, SSH_FXP_INIT, sftp_reply_version}, + {NULL, NULL, 0, NULL}, +}; + +const struct sftp_message_handler extended_handlers[] = { + /* here are some extended type handlers */ + {"statvfs", "statvfs@openssh.com", 0, process_extended_statvfs}, + {NULL, NULL, 0, NULL}, +}; + +static int +process_open(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + uint32_t msg_flag = sftp_client_message_get_flags(client_msg); + uint32_t mode = client_msg->attr->permissions; + ssh_string handle_s = NULL; + struct sftp_handle *h = NULL; + int file_flag; + int fd = -1; + int status; + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing open: filename %s, mode=0%o" PRIu32, + filename, mode); + + if (((msg_flag & (uint32_t)SSH_FXF_READ) == SSH_FXF_READ) && + ((msg_flag & (uint32_t)SSH_FXF_WRITE) == SSH_FXF_WRITE)) { + file_flag = O_RDWR; // file must exist + if ((msg_flag & (uint32_t)SSH_FXF_CREAT) == SSH_FXF_CREAT) + file_flag |= O_CREAT; + } else if ((msg_flag & (uint32_t)SSH_FXF_WRITE) == SSH_FXF_WRITE) { + file_flag = O_WRONLY; + if ((msg_flag & (uint32_t)SSH_FXF_APPEND) == SSH_FXF_APPEND) + file_flag |= O_APPEND; + if ((msg_flag & (uint32_t)SSH_FXF_CREAT) == SSH_FXF_CREAT) + file_flag |= O_CREAT; + } else if ((msg_flag & (uint32_t)SSH_FXF_READ) == SSH_FXF_READ) { + file_flag = O_RDONLY; + } else { + SSH_LOG(SSH_LOG_PROTOCOL, "undefined message flag: %" PRIu32, msg_flag); + sftp_reply_status(client_msg, SSH_FX_FAILURE, "Flag error"); + return SSH_ERROR; + } + + fd = open(filename, file_flag, mode); + if (fd == -1) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "error open file with error: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, "Write error"); + return SSH_ERROR; + } + + h = calloc(1, sizeof (struct sftp_handle)); + if (h == NULL) { + close(fd); + SSH_LOG(SSH_LOG_PROTOCOL, "failed to allocate a new handle"); + sftp_reply_status(client_msg, SSH_FX_FAILURE, + "Failed to allocate new handle"); + return SSH_ERROR; + } + h->fd = fd; + h->type = SFTP_FILE_HANDLE; + handle_s = sftp_handle_alloc(client_msg->sftp, h); + if (handle_s != NULL) { + sftp_reply_handle(client_msg, handle_s); + ssh_string_free(handle_s); + } else { + close(fd); + SSH_LOG(SSH_LOG_PROTOCOL, "Failed to allocate handle"); + sftp_reply_status(client_msg, SSH_FX_FAILURE, + "Failed to allocate handle"); + } + + return SSH_OK; +} + +static int +process_read(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_string handle = client_msg->handle; + struct sftp_handle *h = NULL; + ssize_t allreadn = 0; + int fd = -1; + char *buffer = NULL; + int rv; + + ssh_log_hexdump("Processing read: handle:", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + h = sftp_handle(sftp, handle); + if (h->type == SFTP_FILE_HANDLE) { + fd = h->fd; + } + + if (fd < 0) { + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "invalid fd (%d) received from handle", fd); + return SSH_ERROR; + } + rv = lseek(fd, client_msg->offset, SEEK_SET); + if (rv == -1) { + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, + "error seeking file fd: %d at offset: %" PRIu64, + fd, client_msg->offset); + return SSH_ERROR; + } + + buffer = malloc(client_msg->len); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "Failed to allocate memory for read data"); + return SSH_ERROR; + } + do { + ssize_t readn = read(fd, buffer + allreadn, client_msg->len - allreadn); + if (readn < 0) { + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "read file error!"); + free(buffer); + return SSH_ERROR; + } else if (readn == 0) { + /* no more data to read, EOF ? */ + break; + } + allreadn += readn; + } while (allreadn < (ssize_t)client_msg->len); + + if (allreadn > 0) { + sftp_reply_data(client_msg, buffer, allreadn); + } else { + sftp_reply_status(client_msg, SSH_FX_EOF, NULL); + } + + free(buffer); + return SSH_OK; +} + +static int +process_write(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_string handle = client_msg->handle; + struct sftp_handle *h = NULL; + ssize_t written = 0; + int fd = -1; + const char *msg_data = NULL; + uint32_t len; + int rv; + + ssh_log_hexdump("Processing write: handle", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + h = sftp_handle(sftp, handle); + if (h->type == SFTP_FILE_HANDLE) { + fd = h->fd; + } + if (fd < 0) { + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "write file fd error!"); + return SSH_ERROR; + } + + msg_data = ssh_string_get_char(client_msg->data); + len = ssh_string_len(client_msg->data); + + rv = lseek(fd, client_msg->offset, SEEK_SET); + if (rv == -1) { + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "error seeking file at offset: %" PRIu64, + client_msg->offset); + } + do { + rv = write(fd, msg_data + written, len - written); + if (rv < 0) { + sftp_reply_status(client_msg, SSH_FX_FAILURE, "Write error"); + SSH_LOG(SSH_LOG_PROTOCOL, "file write error!"); + return SSH_ERROR; + } + written += rv; + } while (written < (int)len); + + sftp_reply_status(client_msg, SSH_FX_OK, NULL); + + return SSH_OK; +} + +static int +process_close(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_string handle = client_msg->handle; + struct sftp_handle *h = NULL; + int ret; + + ssh_log_hexdump("Processing close: handle:", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + h = sftp_handle(sftp, handle); + if (h->type == SFTP_FILE_HANDLE) { + int fd = h->fd; + close(fd); + ret = SSH_OK; + } else if (h->type == SFTP_DIR_HANDLE) { + DIR *dir = h->dirp; + closedir(dir); + ret = SSH_OK; + } else { + ret = SSH_ERROR; + } + SAFE_FREE(h->name); + sftp_handle_remove(sftp, h); + SAFE_FREE(h); + + if (ret == SSH_OK) { + sftp_reply_status(client_msg, SSH_FX_OK, NULL); + } else { + SSH_LOG(SSH_LOG_PROTOCOL, "closing file failed"); + sftp_reply_status(client_msg, SSH_FX_BAD_MESSAGE, "Invalid handle"); + } + + return SSH_OK; +} + +static int +process_opendir(sftp_client_message client_msg) +{ + DIR *dir = NULL; + const char *dir_name = sftp_client_message_get_filename(client_msg); + ssh_string handle_s = NULL; + struct sftp_handle *h = NULL; + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing opendir %s", dir_name); + + dir = opendir(dir_name); + if (dir == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "No such directory"); + return SSH_ERROR; + } + + h = calloc(1, sizeof (struct sftp_handle)); + if (h == NULL) { + closedir(dir); + SSH_LOG(SSH_LOG_PROTOCOL, "failed to allocate a new handle"); + sftp_reply_status(client_msg, SSH_FX_FAILURE, + "Failed to allocate new handle"); + return SSH_ERROR; + } + h->dirp = dir; + h->name = strdup(dir_name); + h->type = SFTP_DIR_HANDLE; + handle_s = sftp_handle_alloc(client_msg->sftp, h); + + if (handle_s != NULL) { + sftp_reply_handle(client_msg, handle_s); + ssh_string_free(handle_s); + } else { + closedir(dir); + sftp_reply_status(client_msg, SSH_FX_FAILURE, "No handle available"); + } + + return SSH_OK; +} + +static int +readdir_long_name(char *z_file_name, struct stat *z_st, char *z_long_name) +{ + char tmpbuf[MAX_LONG_NAME_LEN]; + char time[50]; + char *ptr = z_long_name; + int mode = z_st->st_mode; + + *ptr = '\0'; + + switch (mode & S_IFMT) { + case S_IFDIR: + *ptr++ = 'd'; + break; + default: + *ptr++ = '-'; + break; + } + + /* user */ + if (mode & 0400) + *ptr++ = 'r'; + else + *ptr++ = '-'; + + if (mode & 0200) + *ptr++ = 'w'; + else + *ptr++ = '-'; + + if (mode & 0100) { + if (mode & S_ISUID) + *ptr++ = 's'; + else + *ptr++ = 'x'; + } else + *ptr++ = '-'; + + /* group */ + if (mode & 040) + *ptr++ = 'r'; + else + *ptr++ = '-'; + if (mode & 020) + *ptr++ = 'w'; + else + *ptr++ = '-'; + if (mode & 010) + *ptr++ = 'x'; + else + *ptr++ = '-'; + + /* other */ + if (mode & 04) + *ptr++ = 'r'; + else + *ptr++ = '-'; + if (mode & 02) + *ptr++ = 'w'; + else + *ptr++ = '-'; + if (mode & 01) + *ptr++ = 'x'; + else + *ptr++ = '-'; + + *ptr++ = ' '; + *ptr = '\0'; + + snprintf(tmpbuf, sizeof(tmpbuf), "%3d %d %d %d", (int)z_st->st_nlink, + (int)z_st->st_uid, (int)z_st->st_gid, (int)z_st->st_size); + strcat(z_long_name, tmpbuf); + + ctime_r(&z_st->st_mtime, time); + if ((ptr = strchr(time, '\n'))) { + *ptr = '\0'; + } + snprintf(tmpbuf, sizeof(tmpbuf), " %s %s", time + 4, z_file_name); + strcat(z_long_name, tmpbuf); + + return SSH_OK; +} + +static int +process_readdir(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_string handle = client_msg->handle; + struct sftp_handle *h = NULL; + int ret = SSH_OK; + int entries = 0; + struct dirent *dentry = NULL; + DIR *dir = NULL; + char long_path[PATH_MAX]; + int srclen; + const char *handle_name = NULL; + + ssh_log_hexdump("Processing readdir: handle", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + h = sftp_handle(sftp, client_msg->handle); + if (h->type == SFTP_DIR_HANDLE) { + dir = h->dirp; + handle_name = h->name; + } + if (dir == NULL) { + SSH_LOG(SSH_LOG_PROTOCOL, "got wrong handle from msg"); + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + return SSH_ERROR; + } + + if (handle_name == NULL) { + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + return SSH_ERROR; + } + + srclen = strlen(handle_name); + if (srclen + 2 >= PATH_MAX) { + SSH_LOG(SSH_LOG_PROTOCOL, "handle string length exceed max length!"); + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + return SSH_ERROR; + } + + for (int i = 0; i < MAX_ENTRIES_NUM_IN_PACKET; i++) { + dentry = readdir(dir); + + if (dentry != NULL) { + struct sftp_attributes_struct attr; + struct stat st; + char long_name[MAX_LONG_NAME_LEN]; + + if (strlen(dentry->d_name) + srclen + 1 >= PATH_MAX) { + SSH_LOG(SSH_LOG_PROTOCOL, + "handle string length exceed max length!"); + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + return SSH_ERROR; + } + snprintf(long_path, PATH_MAX, "%s/%s", handle_name, dentry->d_name); + + if (lstat(long_path, &st) == 0) { + stat_to_filexfer_attrib(&st, &attr); + } else { + clear_filexfer_attrib(&attr); + } + + if (readdir_long_name(dentry->d_name, &st, long_name) == 0) { + sftp_reply_names_add(client_msg, dentry->d_name, long_name, &attr); + } else { + printf("readdir long name error\n"); + } + + entries++; + } else { + break; + } + } + + if (entries > 0) { + ret = sftp_reply_names(client_msg); + } else { + sftp_reply_status(client_msg, SSH_FX_EOF, NULL); + } + + return ret; +} + +static int +process_mkdir(sftp_client_message client_msg) +{ + int ret = SSH_OK; + const char *filename = sftp_client_message_get_filename(client_msg); + uint32_t msg_flags = client_msg->attr->flags; + uint32_t permission = client_msg->attr->permissions; + uint32_t mode = (msg_flags & (uint32_t)SSH_FILEXFER_ATTR_PERMISSIONS) + ? permission & (uint32_t)07777 : 0777; + int status = SSH_FX_OK; + int rv; + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing mkdir %s, mode=0%o" PRIu32, + filename, mode); + + if (filename == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + rv = mkdir(filename, mode); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "failed to mkdir: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + ret = SSH_ERROR; + } + + sftp_reply_status(client_msg, status, NULL); + + return ret; +} + +static int +process_rmdir(sftp_client_message client_msg) +{ + int ret = SSH_OK; + const char *filename = sftp_client_message_get_filename(client_msg); + int status = SSH_FX_OK; + int rv; + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing rmdir %s", filename); + + if (filename == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + rv = rmdir(filename); + if (rv < 0) { + status = unix_errno_to_ssh_stat(errno); + ret = SSH_ERROR; + } + + sftp_reply_status(client_msg, status, NULL); + + return ret; } -void sftp_handle_remove(sftp_session sftp, void *handle) { - int i; +static int +process_realpath(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + char *path = NULL; - for (i = 0; i < SFTP_HANDLES; i++) { - if (sftp->handles[i] == handle) { - sftp->handles[i] = NULL; - break; + SSH_LOG(SSH_LOG_PROTOCOL, "Processing realpath %s", filename); + + if (filename[0] == '\0') { + path = realpath(".", NULL); + } else { + path = realpath(filename, NULL); + } + if (path == NULL) { + int saved_errno = errno; + int status = unix_errno_to_ssh_stat(saved_errno); + const char *err_msg = ssh_str_error(status); + + SSH_LOG(SSH_LOG_PROTOCOL, "realpath failed: %s", strerror(saved_errno)); + sftp_reply_status(client_msg, status, err_msg); + return SSH_ERROR; + } + sftp_reply_name(client_msg, path, NULL); + free(path); + return SSH_OK; +} + +static int +process_lstat(sftp_client_message client_msg) +{ + int ret = SSH_OK; + const char *filename = sftp_client_message_get_filename(client_msg); + struct sftp_attributes_struct attr; + struct stat st; + int status = SSH_FX_OK; + int rv; + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing lstat %s", filename); + + if (filename == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + rv = lstat(filename, &st); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "lstat failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + ret = SSH_ERROR; + } else { + stat_to_filexfer_attrib(&st, &attr); + sftp_reply_attr(client_msg, &attr); + } + + return ret; +} + +static int +process_stat(sftp_client_message client_msg) +{ + int ret = SSH_OK; + const char *filename = sftp_client_message_get_filename(client_msg); + struct sftp_attributes_struct attr; + struct stat st; + int status = SSH_FX_OK; + int rv; + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing stat %s", filename); + + if (filename == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + rv = stat(filename, &st); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "lstat failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + ret = SSH_ERROR; + } else { + stat_to_filexfer_attrib(&st, &attr); + sftp_reply_attr(client_msg, &attr); + } + + return ret; +} + +static int +process_readlink(sftp_client_message client_msg) +{ + int ret = SSH_OK; + const char *filename = sftp_client_message_get_filename(client_msg); + char buf[PATH_MAX]; + int len = -1; + const char *err_msg; + int status = SSH_FX_OK; + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing readlink %s", filename); + + if (filename == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + len = readlink(filename, buf, sizeof(buf) - 1); + if (len < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "readlink failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + err_msg = ssh_str_error(status); + sftp_reply_status(client_msg, status, err_msg); + ret = SSH_ERROR; + } else { + buf[len] = '\0'; + sftp_reply_name(client_msg, buf, NULL); + } + + return ret; +} + +/* Note, that this function is using reversed order of the arguments than the + * OpenSSH sftp server as they have the arguments switched. See + * section "4.1 sftp: Reversal of arguments to SSH_FXP_SYMLINK' in + * https://github.com/openssh/openssh-portable/blob/master/PROTOCOL + * for more information */ +static int +process_symlink(sftp_client_message client_msg) +{ + int ret = SSH_OK; + const char *destpath = sftp_client_message_get_filename(client_msg); + const char *srcpath = ssh_string_get_char(client_msg->data); + int status = SSH_FX_OK; + int rv; + + SSH_LOG(SSH_LOG_PROTOCOL, "processing symlink: src=%s dest=%s", + srcpath, destpath); + + if (srcpath == NULL || destpath == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + rv = symlink(srcpath, destpath); + if (rv < 0) { + int saved_errno = errno; + status = unix_errno_to_ssh_stat(saved_errno); + SSH_LOG(SSH_LOG_PROTOCOL, "symlink failed: %s", strerror(saved_errno)); + sftp_reply_status(client_msg, status, "Write error"); + ret = SSH_ERROR; + } else { + sftp_reply_status(client_msg, SSH_FX_OK, "write success"); + } + + return ret; +} + +static int +process_remove(sftp_client_message client_msg) +{ + int ret = SSH_OK; + const char *filename = sftp_client_message_get_filename(client_msg); + int rv; + int status = SSH_FX_OK; + + SSH_LOG(SSH_LOG_PROTOCOL, "processing remove: %s", filename); + + rv = unlink(filename); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "unlink failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + ret = SSH_ERROR; + } + + sftp_reply_status(client_msg, status, NULL); + + return ret; +} + +static int +process_unsupposed(sftp_client_message client_msg) +{ + sftp_reply_status(client_msg, SSH_FX_OP_UNSUPPORTED, + "Operation not supported"); + SSH_LOG(SSH_LOG_PROTOCOL, "Message type %d not implemented", + sftp_client_message_get_type(client_msg)); + return SSH_OK; +} + +static int +process_extended_statvfs(sftp_client_message client_msg) +{ + const char *path = sftp_client_message_get_filename(client_msg); + sftp_statvfs_t sftp_statvfs; + struct statvfs st; + uint64_t flag; + int status; + int rv; + + SSH_LOG(SSH_LOG_PROTOCOL, "processing extended statvfs: %s", path); + + rv = statvfs(path, &st); + if (rv != 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "statvfs failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return SSH_ERROR; + } + + sftp_statvfs = calloc(1, sizeof(struct sftp_statvfs_struct)); + if (sftp_statvfs == NULL) { + SSH_LOG(SSH_LOG_PROTOCOL, "Failed to allocate statvfs structure"); + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + return SSH_ERROR; + } + flag = (st.f_flag & ST_RDONLY) ? SSH_FXE_STATVFS_ST_RDONLY : 0; + flag |= (st.f_flag & ST_NOSUID) ? SSH_FXE_STATVFS_ST_NOSUID : 0; + + sftp_statvfs->f_bsize = st.f_bsize; + sftp_statvfs->f_frsize = st.f_frsize; + sftp_statvfs->f_blocks = st.f_blocks; + sftp_statvfs->f_bfree = st.f_bfree; + sftp_statvfs->f_bavail = st.f_bavail; + sftp_statvfs->f_files = st.f_files; + sftp_statvfs->f_ffree = st.f_ffree; + sftp_statvfs->f_favail = st.f_favail; + sftp_statvfs->f_fsid = st.f_fsid; + sftp_statvfs->f_flag = flag; + sftp_statvfs->f_namemax = st.f_namemax; + + rv = sftp_reply_statvfs(client_msg, sftp_statvfs); + free(sftp_statvfs); + if (rv == 0) { + return SSH_OK; + } + return SSH_ERROR; +} + +static int +process_extended(sftp_client_message sftp_msg) +{ + int status = SSH_ERROR; + const char *subtype = sftp_msg->submessage; + sftp_server_message_callback handler = NULL; + + SSH_LOG(SSH_LOG_PROTOCOL, "processing extended message: %s", subtype); + + for (int i = 0; extended_handlers[i].cb != NULL; i++) { + if (strcmp(subtype, extended_handlers[i].extended_name) == 0) { + handler = extended_handlers[i].cb; + break; + } + } + if (handler != NULL) { + status = handler(sftp_msg); + return status; + } + + sftp_reply_status(sftp_msg, SSH_FX_OP_UNSUPPORTED, + "Extended Operation not supported"); + SSH_LOG(SSH_LOG_PROTOCOL, "Extended Message type %s not implemented", + subtype); + return SSH_OK; +} + +static int +dispatch_sftp_request(sftp_client_message sftp_msg) +{ + int status = SSH_ERROR; + sftp_server_message_callback handler = NULL; + uint8_t type = sftp_client_message_get_type(sftp_msg); + + SSH_LOG(SSH_LOG_PROTOCOL, "processing request type: %u", type); + + for (int i = 0; message_handlers[i].cb != NULL; i++) { + if (type == message_handlers[i].type) { + handler = message_handlers[i].cb; + break; + } + } + + if (handler != NULL) { + status = handler(sftp_msg); + } else { + sftp_reply_status(sftp_msg, SSH_FX_OP_UNSUPPORTED, + "Operation not supported"); + SSH_LOG(SSH_LOG_PROTOCOL, "Message type %u not implemented", type); + return SSH_OK; + } + + return status; +} + +static int +process_client_message(sftp_client_message client_msg) +{ + int status = SSH_OK; + if (client_msg == NULL) { + return SSH_ERROR; + } + + switch (client_msg->type) { + case SSH_FXP_EXTENDED: + status = process_extended(client_msg); + break; + default: + status = dispatch_sftp_request(client_msg); + } + + if (status != SSH_OK) + SSH_LOG(SSH_LOG_PROTOCOL, + "error occurred during processing client message!"); + + return status; +} + +/** + * @brief Default subsystem request handler for SFTP subsystem + * + * @param[in] session The ssh session + * @param[in] channel The existing ssh channel + * @param[in] subsystem The subsystem name. Only "sftp" is handled + * @param[out] userdata The pointer to sftp_session which will get the + * resulting SFTP session + * + * @return SSH_OK when the SFTP server was successfully initialized, SSH_ERROR + * otherwise. + */ +int +sftp_channel_default_subsystem_request(ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata) +{ + if (strcmp(subsystem, "sftp") == 0) { + sftp_session *sftp = (sftp_session *)userdata; + + /* initialize sftp session and file handler */ + *sftp = sftp_server_new(session, channel); + if (*sftp == NULL) { + return SSH_ERROR; + } + + return SSH_OK; + } + return SSH_ERROR; +} + +/** + * @brief Default data callback for sftp server + * + * @param[in] session The ssh session + * @param[in] channel The ssh channel with SFTP session opened + * @param[in] data The data to be processed. + * @param[in] len The length of input data to be processed + * @param[in] is_stderr Unused channel flag for stderr flagging + * @param[in] userdata The pointer to sftp_session + * + * @return number of bytes processed, -1 when error occurs. + */ +int +sftp_channel_default_data_callback(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *data, + uint32_t len, + UNUSED_PARAM(int is_stderr), + void *userdata) +{ + sftp_session *sftpp = (sftp_session *)userdata; + sftp_session sftp = NULL; + sftp_client_message msg; + int decode_len; + int rc; + + if (sftpp == NULL) { + SSH_LOG(SSH_LOG_WARNING, "NULL userdata passed to callback"); + return -1; } - } + sftp = *sftpp; + + decode_len = sftp_decode_channel_data_to_packet(sftp, data, len); + if (decode_len == -1) + return -1; + + msg = sftp_get_client_message_from_packet(sftp); + rc = process_client_message(msg); + sftp_client_message_free(msg); + if (rc != SSH_OK) + SSH_LOG(SSH_LOG_PROTOCOL, "process sftp failed!"); + + return decode_len; } +#else +/* Not available on Windows for now */ +int +sftp_channel_default_data_callback(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(void *data), + UNUSED_PARAM(uint32_t len), + UNUSED_PARAM(int is_stderr), + UNUSED_PARAM(void *userdata)) +{ + return -1; +} + +int +sftp_channel_default_subsystem_request(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *subsystem), + UNUSED_PARAM(void *userdata)) +{ + return SSH_ERROR; +} +#endif diff --git a/src/socket.c b/src/socket.c index bc18f382..9dc4cbdd 100644 --- a/src/socket.c +++ b/src/socket.c @@ -28,17 +28,15 @@ #ifdef _WIN32 #include <winsock2.h> #include <ws2tcpip.h> -#if _MSC_VER >= 1400 -#include <io.h> -#undef open -#define open _open -#undef close -#define close _close -#undef read -#define read _read -#undef write -#define write _write -#endif /* _MSC_VER */ +#ifndef UNIX_PATH_MAX + /* Inlining the key portions of afunix.h in Windows 10 SDK; + * that header isn't available in the mingw environment. */ +#define UNIX_PATH_MAX 108 +struct sockaddr_un { + ADDRESS_FAMILY sun_family; + char sun_path[UNIX_PATH_MAX]; +}; +#endif #else /* _WIN32 */ #include <fcntl.h> #include <sys/types.h> @@ -56,8 +54,6 @@ #include "libssh/session.h" /** - * @internal - * * @defgroup libssh_socket The SSH socket functions. * @ingroup libssh * @@ -214,6 +210,15 @@ void ssh_socket_set_callbacks(ssh_socket s, ssh_socket_callbacks callbacks) s->callbacks = callbacks; } +void ssh_socket_set_connected(ssh_socket s, struct ssh_poll_handle_struct *p) +{ + s->state = SSH_SOCKET_CONNECTED; + /* POLLOUT is the event to wait for in a nonblocking connect */ + if (p != NULL) { + ssh_poll_set_events(p, POLLIN | POLLOUT); + } +} + /** * @brief SSH poll callback. This callback will be used when an event * caught on the socket. @@ -221,7 +226,7 @@ void ssh_socket_set_callbacks(ssh_socket s, ssh_socket_callbacks callbacks) * @param p Poll object this callback belongs to. * @param fd The raw socket. * @param revents The current poll events on the socket. - * @param userdata Userdata to be passed to the callback function, + * @param v_s Userdata to be passed to the callback function, * in this case the socket object. * * @return 0 on success, < 0 when the poll object has been removed @@ -243,7 +248,8 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, if (!ssh_socket_is_open(s)) { return -1; } - SSH_LOG(SSH_LOG_TRACE, "Poll callback on socket %d (%s%s%s), out buffer %d",fd, + SSH_LOG(SSH_LOG_TRACE, + "Poll callback on socket %d (%s%s%s), out buffer %" PRIu32, fd, (revents & POLLIN) ? "POLLIN ":"", (revents & POLLOUT) ? "POLLOUT ":"", (revents & POLLERR) ? "POLLERR":"", @@ -314,12 +320,13 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, /* Call the callback */ if (s->callbacks != NULL && s->callbacks->data != NULL) { + size_t processed; do { - nread = s->callbacks->data(ssh_buffer_get(s->in_buffer), - ssh_buffer_get_len(s->in_buffer), - s->callbacks->userdata); - ssh_buffer_pass_bytes(s->in_buffer, nread); - } while ((nread > 0) && (s->state == SSH_SOCKET_CONNECTED)); + processed = s->callbacks->data(ssh_buffer_get(s->in_buffer), + ssh_buffer_get_len(s->in_buffer), + s->callbacks->userdata); + ssh_buffer_pass_bytes(s->in_buffer, processed); + } while ((processed > 0) && (s->state == SSH_SOCKET_CONNECTED)); /* p may have been freed, so don't use it * anymore in this function */ @@ -336,10 +343,7 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, /* First, POLLOUT is a sign we may be connected */ if (s->state == SSH_SOCKET_CONNECTING) { SSH_LOG(SSH_LOG_PACKET, "Received POLLOUT in connecting state"); - s->state = SSH_SOCKET_CONNECTED; - if (p != NULL) { - ssh_poll_set_events(p, POLLOUT | POLLIN); - } + ssh_socket_set_connected(s, p); rc = ssh_socket_set_blocking(ssh_socket_get_fd(s)); if (rc < 0) { @@ -367,7 +371,7 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, ssh_socket_nonblocking_flush(s); } else if (s->callbacks != NULL && s->callbacks->controlflow != NULL) { /* Otherwise advertise the upper level that write can be done */ - SSH_LOG(SSH_LOG_TRACE,"sending control flow event"); + SSH_LOG(SSH_LOG_TRACE, "sending control flow event"); s->callbacks->controlflow(SSH_SOCKET_FLOW_WRITEWONTBLOCK, s->callbacks->userdata); } @@ -392,7 +396,7 @@ ssh_poll_handle ssh_socket_get_poll_handle(ssh_socket s) if (s->poll_handle) { return s->poll_handle; } - s->poll_handle = ssh_poll_new(s->fd,0,ssh_socket_pollcallback,s); + s->poll_handle = ssh_poll_new(s->fd, 0, ssh_socket_pollcallback, s); return s->poll_handle; } @@ -410,10 +414,10 @@ void ssh_socket_free(ssh_socket s) SAFE_FREE(s); } -#ifndef _WIN32 int ssh_socket_unix(ssh_socket s, const char *path) { struct sockaddr_un sunaddr; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; socket_t fd; sunaddr.sun_family = AF_UNIX; snprintf(sunaddr.sun_path, sizeof(sunaddr.sun_path), "%s", path); @@ -422,28 +426,30 @@ int ssh_socket_unix(ssh_socket s, const char *path) if (fd == SSH_INVALID_SOCKET) { ssh_set_error(s->session, SSH_FATAL, "Error from socket(AF_UNIX, SOCK_STREAM, 0): %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return -1; } +#ifndef _WIN32 if (fcntl(fd, F_SETFD, 1) == -1) { ssh_set_error(s->session, SSH_FATAL, "Error from fcntl(fd, F_SETFD, 1): %s", - strerror(errno)); - close(fd); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + CLOSE_SOCKET(fd); return -1; } +#endif if (connect(fd, (struct sockaddr *) &sunaddr, sizeof(sunaddr)) < 0) { - ssh_set_error(s->session, SSH_FATAL, "Error from connect(): %s", - strerror(errno)); - close(fd); + ssh_set_error(s->session, SSH_FATAL, "Error from connect(%s): %s", + path, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + CLOSE_SOCKET(fd); return -1; } ssh_socket_set_fd(s,fd); return 0; } -#endif /** \internal * \brief closes a socket @@ -477,12 +483,14 @@ void ssh_socket_close(ssh_socket s) kill(pid, SIGTERM); while (waitpid(pid, &status, 0) == -1) { if (errno != EINTR) { - SSH_LOG(SSH_LOG_WARN, "waitpid failed: %s", strerror(errno)); + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + SSH_LOG(SSH_LOG_TRACE, "waitpid failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return; } } if (!WIFEXITED(status)) { - SSH_LOG(SSH_LOG_WARN, "Proxy command exitted abnormally"); + SSH_LOG(SSH_LOG_TRACE, "Proxy command exited abnormally"); return; } SSH_LOG(SSH_LOG_TRACE, "Proxy command returned %d", WEXITSTATUS(status)); @@ -495,22 +503,28 @@ void ssh_socket_close(ssh_socket s) * @brief sets the file descriptor of the socket. * @param[out] s ssh_socket to update * @param[in] fd file descriptor to set - * @warning this function updates boths the input and output + * @warning this function updates both the input and output * file descriptors */ void ssh_socket_set_fd(ssh_socket s, socket_t fd) { + ssh_poll_handle h = NULL; + s->fd = fd; if (s->poll_handle) { ssh_poll_set_fd(s->poll_handle,fd); } else { s->state = SSH_SOCKET_CONNECTING; + h = ssh_socket_get_poll_handle(s); + if (h == NULL) { + return; + } /* POLLOUT is the event to wait for in a nonblocking connect */ - ssh_poll_set_events(ssh_socket_get_poll_handle(s), POLLOUT); + ssh_poll_set_events(h, POLLOUT); #ifdef _WIN32 - ssh_poll_add_events(ssh_socket_get_poll_handle(s), POLLWRNORM); + ssh_poll_add_events(h, POLLWRNORM); #endif } } @@ -544,9 +558,9 @@ static ssize_t ssh_socket_unbuffered_read(ssh_socket s, return -1; } if (s->fd_is_socket) { - rc = recv(s->fd,buffer, len, 0); + rc = recv(s->fd, buffer, len, 0); } else { - rc = read(s->fd,buffer, len); + rc = read(s->fd, buffer, len); } #ifdef _WIN32 s->last_errno = WSAGetLastError(); @@ -557,6 +571,8 @@ static ssize_t ssh_socket_unbuffered_read(ssh_socket s, if (rc < 0) { s->data_except = 1; + } else { + SSH_LOG(SSH_LOG_TRACE, "read %zd", rc); } return rc; @@ -594,12 +610,13 @@ static ssize_t ssh_socket_unbuffered_write(ssh_socket s, /* Reactive the POLLOUT detector in the poll multiplexer system */ if (s->poll_handle) { SSH_LOG(SSH_LOG_PACKET, "Enabling POLLOUT for socket"); - ssh_poll_set_events(s->poll_handle,ssh_poll_get_events(s->poll_handle) | POLLOUT); + ssh_poll_add_events(s->poll_handle, POLLOUT); } if (w < 0) { s->data_except = 1; } + SSH_LOG(SSH_LOG_TRACE, "wrote %zd", w); return w; } @@ -638,7 +655,7 @@ void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd) * \returns SSH_OK, or SSH_ERROR * \warning has no effect on socket before a flush */ -int ssh_socket_write(ssh_socket s, const void *buffer, int len) +int ssh_socket_write(ssh_socket s, const void *buffer, uint32_t len) { if (len > 0) { if (ssh_buffer_add_data(s->out_buffer, buffer, len) < 0) { @@ -668,11 +685,12 @@ int ssh_socket_nonblocking_flush(ssh_socket s) s->last_errno, s->callbacks->userdata); } else { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; ssh_set_error(session, SSH_FATAL, "Writing packet: error on socket (or connection " "closed): %s", - strerror(s->last_errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); } return SSH_ERROR; @@ -701,11 +719,12 @@ int ssh_socket_nonblocking_flush(ssh_socket s) s->last_errno, s->callbacks->userdata); } else { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; ssh_set_error(session, SSH_FATAL, "Writing packet: error on socket (or connection " "closed): %s", - strerror(s->last_errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); } return SSH_ERROR; @@ -720,6 +739,8 @@ int ssh_socket_nonblocking_flush(ssh_socket s) /* Is there some data pending? */ len = ssh_buffer_get_len(s->out_buffer); if (s->poll_handle && len > 0) { + SSH_LOG(SSH_LOG_TRACE, + "did not send all the data, queuing pollout event"); /* force the poll system to catch pollout events */ ssh_poll_add_events(s->poll_handle, POLLOUT); @@ -829,7 +850,7 @@ int ssh_socket_set_blocking(socket_t fd) /** * @internal * @brief Launches a socket connection - * If a the socket connected callback has been defined and + * If the socket connected callback has been defined and * a poll object exists, this call will be non blocking. * @param s socket to connect. * @param host hostname or ip address to connect to. @@ -837,8 +858,6 @@ int ssh_socket_set_blocking(socket_t fd) * @param bind_addr address to bind to, or NULL for default. * @returns SSH_OK socket is being connected. * @returns SSH_ERROR error while connecting to remote host. - * @bug It only tries connecting to one of the available AI's - * which is problematic for hosts having DNS fail-over. */ int ssh_socket_connect(ssh_socket s, const char *host, @@ -846,19 +865,19 @@ int ssh_socket_connect(ssh_socket s, const char *bind_addr) { socket_t fd; - + if (s->state != SSH_SOCKET_NONE) { ssh_set_error(s->session, SSH_FATAL, "ssh_socket_connect called on socket not unconnected"); return SSH_ERROR; } fd = ssh_connect_host_nonblocking(s->session, host, bind_addr, port); - SSH_LOG(SSH_LOG_PROTOCOL, "Nonblocking connection socket: %d", fd); + SSH_LOG(SSH_LOG_DEBUG, "Nonblocking connection socket: %d", fd); if (fd == SSH_INVALID_SOCKET) { return SSH_ERROR; } ssh_socket_set_fd(s,fd); - + return SSH_OK; } @@ -876,20 +895,29 @@ ssh_execute_command(const char *command, socket_t in, socket_t out) const char *shell = NULL; const char *args[] = {NULL/*shell*/, "-c", command, NULL}; int devnull; + int rc; /* Prepare /dev/null socket for the stderr redirection */ devnull = open("/dev/null", O_WRONLY); if (devnull == -1) { - SSH_LOG(SSH_LOG_WARNING, "Failed to open /dev/null"); + SSH_LOG(SSH_LOG_TRACE, "Failed to open /dev/null"); exit(1); } - /* By default, use the current users shell */ + /* + * By default, use the current users shell. This could fail with some + * shells like zsh or dash ... + */ shell = getenv("SHELL"); if (shell == NULL || shell[0] == '\0') { - /* Fall back to bash. There are issues with dash or - * whatever people tend to link to /bin/sh */ - shell = "/bin/bash"; + /* Fall back to the /bin/sh only if the bash is not available. But there are + * issues with dash or whatever people tend to link to /bin/sh */ + rc = access("/bin/bash", 0); + if (rc != 0) { + shell = "/bin/sh"; + } else { + shell = "/bin/bash"; + } } args[0] = shell; @@ -900,7 +928,13 @@ ssh_execute_command(const char *command, socket_t in, socket_t out) dup2(devnull, STDERR_FILENO); close(in); close(out); - execv(args[0], (char * const *)args); + rc = execv(args[0], (char * const *)args); + if (rc < 0) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + SSH_LOG(SSH_LOG_WARN, "Failed to execute command %s: %s", + command, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } exit(1); } @@ -918,6 +952,7 @@ int ssh_socket_connect_proxycommand(ssh_socket s, const char *command) { socket_t pair[2]; + ssh_poll_handle h = NULL; int pid; int rc; @@ -930,7 +965,7 @@ ssh_socket_connect_proxycommand(ssh_socket s, const char *command) return SSH_ERROR; } - SSH_LOG(SSH_LOG_PROTOCOL, "Executing proxycommand '%s'", command); + SSH_LOG(SSH_LOG_DEBUG, "Executing proxycommand '%s'", command); pid = fork(); if (pid == 0) { ssh_execute_command(command, pair[0], pair[0]); @@ -938,12 +973,14 @@ ssh_socket_connect_proxycommand(ssh_socket s, const char *command) } s->proxy_pid = pid; close(pair[0]); - SSH_LOG(SSH_LOG_PROTOCOL, "ProxyCommand connection pipe: [%d,%d]",pair[0],pair[1]); + SSH_LOG(SSH_LOG_DEBUG, "ProxyCommand connection pipe: [%d,%d]",pair[0],pair[1]); ssh_socket_set_fd(s, pair[1]); - s->state=SSH_SOCKET_CONNECTED; - s->fd_is_socket=0; - /* POLLOUT is the event to wait for in a nonblocking connect */ - ssh_poll_set_events(ssh_socket_get_poll_handle(s), POLLIN | POLLOUT); + s->fd_is_socket = 0; + h = ssh_socket_get_poll_handle(s); + if (h == NULL) { + return SSH_ERROR; + } + ssh_socket_set_connected(s, h); if (s->callbacks && s->callbacks->connected) { s->callbacks->connected(SSH_SOCKET_CONNECTED_OK, 0, s->callbacks->userdata); } diff --git a/src/string.c b/src/string.c index acd3cf48..44403487 100644 --- a/src/string.c +++ b/src/string.c @@ -103,7 +103,7 @@ int ssh_string_fill(struct ssh_string_struct *s, const void *data, size_t len) { * @return The newly allocated string, NULL on error with errno * set. * - * @note The nul byte is not copied nor counted in the ouput string. + * @note The null byte is not copied nor counted in the output string. */ struct ssh_string_struct *ssh_string_from_char(const char *what) { struct ssh_string_struct *ptr; @@ -129,7 +129,7 @@ struct ssh_string_struct *ssh_string_from_char(const char *what) { /** * @brief Return the size of a SSH string. * - * @param[in] s The the input SSH string. + * @param[in] s The input SSH string. * * @return The size of the content of the string, 0 on error. */ @@ -149,7 +149,7 @@ size_t ssh_string_len(struct ssh_string_struct *s) { } /** - * @brief Get the the string as a C nul-terminated string. + * @brief Get the string as a C null-terminated string. * * This is only available as long as the SSH string exists. * @@ -168,7 +168,7 @@ const char *ssh_string_get_char(struct ssh_string_struct *s) } /** - * @brief Convert a SSH string to a C nul-terminated string. + * @brief Convert a SSH string to a C null-terminated string. * * @param[in] s The SSH input string. * diff --git a/src/threads.c b/src/threads.c index 792b976d..7660b972 100644 --- a/src/threads.c +++ b/src/threads.c @@ -20,7 +20,7 @@ */ /** - * @defgroup libssh_threads The SSH threading functions. + * @defgroup libssh_threads The SSH threading functions * @ingroup libssh * * Threading with libssh diff --git a/src/threads/libcrypto.c b/src/threads/libcrypto.c index dac840d3..18951b6a 100644 --- a/src/threads/libcrypto.c +++ b/src/threads/libcrypto.c @@ -24,8 +24,6 @@ #include "libssh/threads.h" #include <libssh/callbacks.h> -#if (OPENSSL_VERSION_NUMBER >= 0x10100000) - int crypto_thread_init(struct ssh_threads_callbacks_struct *cb) { (void) cb; @@ -36,86 +34,3 @@ void crypto_thread_finalize(void) { return; } - -#else - -static struct ssh_threads_callbacks_struct *user_callbacks = NULL; - -static void **libcrypto_mutexes; - -void libcrypto_lock_callback(int mode, int i, const char *file, int line); - -void libcrypto_lock_callback(int mode, int i, const char *file, int line) -{ - (void)file; - (void)line; - - if (mode & CRYPTO_LOCK) { - user_callbacks->mutex_lock(&libcrypto_mutexes[i]); - } else { - user_callbacks->mutex_unlock(&libcrypto_mutexes[i]); - } -} - -static void libcrypto_THREADID_callback(CRYPTO_THREADID *id) -{ - unsigned long thread_id = (*user_callbacks->thread_id)(); - - CRYPTO_THREADID_set_numeric(id, thread_id); -} - -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]); - } - - CRYPTO_THREADID_set_callback(libcrypto_THREADID_callback); - 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; - } - - CRYPTO_THREADID_set_callback(NULL); - 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/token.c b/src/token.c index 0924d3bd..6ce2b64c 100644 --- a/src/token.c +++ b/src/token.c @@ -87,7 +87,7 @@ struct ssh_tokens_st *ssh_tokenize(const char *chain, char separator) return NULL; } - tokens->buffer= strdup(chain); + tokens->buffer = strdup(chain); if (tokens->buffer == NULL) { goto error; } @@ -376,6 +376,7 @@ char *ssh_append_without_duplicates(const char *list, { size_t concat_len = 0; char *ret = NULL, *concat = NULL; + int rc = 0; if (list != NULL) { concat_len = strlen(list); @@ -396,12 +397,144 @@ char *ssh_append_without_duplicates(const char *list, return NULL; } + rc = snprintf(concat, concat_len, "%s%s%s", + list == NULL ? "" : list, + list == NULL ? "" : ",", + appended_list == NULL ? "" : appended_list); + if (rc < 0) { + SAFE_FREE(concat); + return NULL; + } + + ret = ssh_remove_duplicates(concat); + + SAFE_FREE(concat); + + return ret; +} + +/** + * @internal + * + * @brief Given two strings containing lists of tokens, return a newly + * allocated string containing the elements of the first list without the + * elements of the second list. The order of the elements will be preserved. + * + * @param[in] list The first list + * @param[in] remove_list The list to be removed + * + * @return A newly allocated copy list containing elements of the + * list without the elements of remove_list; NULL in case of error. + */ +char *ssh_remove_all_matching(const char *list, + const char *remove_list) +{ + struct ssh_tokens_st *l_tok = NULL, *r_tok = NULL; + int i, j, cmp; + char *ret = NULL; + size_t len, pos = 0; + bool exclude; + + if (list == NULL) { + return NULL; + } + if (remove_list == NULL) { + return strdup (list); + } + + l_tok = ssh_tokenize(list, ','); + if (l_tok == NULL) { + goto out; + } + + r_tok = ssh_tokenize(remove_list, ','); + if (r_tok == NULL) { + goto out; + } + + ret = calloc(1, strlen(list) + 1); + if (ret == NULL) { + goto out; + } + + for (i = 0; l_tok->tokens[i]; i++) { + exclude = false; + for (j = 0; r_tok->tokens[j]; j++) { + cmp = strcmp(l_tok->tokens[i], r_tok->tokens[j]); + if (cmp == 0) { + exclude = true; + break; + } + } + if (exclude == false) { + if (pos != 0) { + ret[pos] = ','; + pos++; + } + + len = strlen(l_tok->tokens[i]); + memcpy(&ret[pos], l_tok->tokens[i], len); + pos += len; + } + } + + if (ret[0] == '\0') { + SAFE_FREE(ret); + } + +out: + ssh_tokens_free(l_tok); + ssh_tokens_free(r_tok); + return ret; +} + +/** + * @internal + * + * @brief Given two strings containing lists of tokens, return a newly + * allocated string containing all the elements of the first list prefixed at + * the beginning of the second list, without duplicates. + * + * @param[in] list The first list + * @param[in] prefixed_list The list to use as a prefix + * + * @return A newly allocated list containing all the elements + * of the list prefixed with the elements of the prefixed_list without + * duplicates; NULL in case of error. + */ +char *ssh_prefix_without_duplicates(const char *list, + const char *prefixed_list) +{ + size_t concat_len = 0; + char *ret = NULL, *concat = NULL; + int rc = 0; + if (list != NULL) { - strcpy(concat, list); - strncat(concat, ",", concat_len - strlen(concat) - 1); + concat_len = strlen(list); } - if (appended_list != NULL) { - strncat(concat, appended_list, concat_len - strlen(concat) - 1); + + if (prefixed_list != NULL) { + concat_len += strlen(prefixed_list); + } + + if (concat_len == 0) { + return NULL; + } + + /* Add room for ending '\0' and for middle ',' */ + concat_len += 2; + concat = calloc(concat_len, 1); + if (concat == NULL) { + return NULL; + } + + rc = snprintf(concat, concat_len, "%s%s%s", + prefixed_list == NULL ? "" : prefixed_list, + prefixed_list == NULL ? "" : ",", + list == NULL ? "" : list); + if (rc < 0) { + SAFE_FREE(concat); + return NULL; } ret = ssh_remove_duplicates(concat); diff --git a/src/ttyopts.c b/src/ttyopts.c new file mode 100644 index 00000000..f6557884 --- /dev/null +++ b/src/ttyopts.c @@ -0,0 +1,449 @@ +/* + * ttyopts.c - encoding of TTY modes. + * + * This file is part of the SSH Library + * + * Copyright (c) 2023 by Utimaco TS GmbH <oss_committee@utimaco.com> + * Author: Daniel Evers <daniel.evers@utimaco.com> + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include <stdint.h> +#include <stdio.h> + +#include <libssh/priv.h> +#include <string.h> + +#ifdef HAVE_TERMIOS_H +#include <termios.h> +#endif + +/** Terminal mode opcodes */ +enum { + TTY_OP_END = 0, + TTY_OP_VINTR = 1, + TTY_OP_VQUIT = 2, + TTY_OP_VERASE = 3, + TTY_OP_VKILL = 4, + TTY_OP_VEOF = 5, + TTY_OP_VEOL = 6, + TTY_OP_VEOL2 = 7, + TTY_OP_VSTART = 8, + TTY_OP_VSTOP = 9, + TTY_OP_VSUSP = 10, + TTY_OP_VDSUSP = 11, + TTY_OP_VREPRINT = 12, + TTY_OP_VWERASE = 13, + TTY_OP_VLNEXT = 14, + TTY_OP_VFLUSH = 15, + TTY_OP_VSWTC = 16, + TTY_OP_VSTATUS = 17, + TTY_OP_VDISCARD = 18, + TTY_OP_IGNPAR = 30, + TTY_OP_PARMRK = 31, + TTY_OP_INPCK = 32, + TTY_OP_ISTRIP = 33, + TTY_OP_INLCR = 34, + TTY_OP_IGNCR = 35, + TTY_OP_ICRNL = 36, + TTY_OP_IUCLC = 37, + TTY_OP_IXON = 38, + TTY_OP_IXANY = 39, + TTY_OP_IXOFF = 40, + TTY_OP_IMAXBEL = 41, + TTY_OP_IUTF8 = 42, + TTY_OP_ISIG = 50, + TTY_OP_ICANON = 51, + TTY_OP_XCASE = 52, + TTY_OP_ECHO = 53, + TTY_OP_ECHOE = 54, + TTY_OP_ECHOK = 55, + TTY_OP_ECHONL = 56, + TTY_OP_NOFLSH = 57, + TTY_OP_TOSTOP = 58, + TTY_OP_IEXTEN = 59, + TTY_OP_ECHOCTL = 60, + TTY_OP_ECHOKE = 61, + TTY_OP_PENDIN = 62, + TTY_OP_OPOST = 70, + TTY_OP_OLCUC = 71, + TTY_OP_ONLCR = 72, + TTY_OP_OCRNL = 73, + TTY_OP_ONOCR = 74, + TTY_OP_ONLRET = 75, + TTY_OP_CS7 = 90, + TTY_OP_CS8 = 91, + TTY_OP_PARENB = 92, + TTY_OP_PARODD = 93, + TTY_OP_ISPEED = 128, + TTY_OP_OSPEED = 129, +}; + +/** + * Encodes a single SSH terminal mode option into the buffer. + * + * @param[in] attr The mode's opcode value. + * + * @param[in] value The mode's value. + * + * @param[out] buf Destination buffer to encode into. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes written to the buffer on success, -1 on + * error. + */ +static int +encode_termios_opt(unsigned char opcode, + uint32_t value, + unsigned char *buf, + size_t buflen) +{ + int offset = 0; + + /* always need 5 bytes */ + if (buflen < 5) { + return -1; + } + + /* 1 byte opcode */ + buf[offset++] = opcode; + + /* 4 bytes value (big endian) */ + value = htonl(value); + memcpy(buf + offset, &value, sizeof(value)); + offset += sizeof(value); + + return offset; +} + +#ifdef HAVE_TERMIOS_H +/** Converts a baudrate constant (Bxxxx) to a numeric value. */ +static int +baud2speed(int baudrate) +{ + switch (baudrate) { + default: + case B0: + return 0; + case B50: + return 50; + case B75: + return 75; + case B110: + return 110; + case B134: + return 134; + case B150: + return 150; + case B200: + return 200; + case B300: + return 300; + case B600: + return 600; + case B1200: + return 1200; + case B1800: + return 1800; + case B2400: + return 2400; + case B4800: + return 4800; + case B9600: + return 9600; + case B19200: + return 19200; + case B38400: + return 38400; + case B57600: + return 57600; + case B115200: + return 115200; + case B230400: + return 230400; + } +} + +/** + * Encodes all terminal options from the given \c termios structure + * into the buffer. + * + * @param[in] attr The terminal options to encode. + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +static int +encode_termios_opts(struct termios *attr, unsigned char *buf, size_t buflen) +{ + unsigned int offset = 0; + int rc; + +#define SSH_ENCODE_OPT(code, value) \ + rc = encode_termios_opt(code, value, buf + offset, buflen - offset); \ + if (rc < 0) { \ + return rc; \ + } else { \ + offset += rc; \ + } + +#define SSH_ENCODE_INPUT_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_iflag & opt) ? 1 : 0) + SSH_ENCODE_INPUT_OPT(IGNPAR) + SSH_ENCODE_INPUT_OPT(PARMRK) + SSH_ENCODE_INPUT_OPT(INPCK) + SSH_ENCODE_INPUT_OPT(ISTRIP) + SSH_ENCODE_INPUT_OPT(INLCR) + SSH_ENCODE_INPUT_OPT(IGNCR) + SSH_ENCODE_INPUT_OPT(ICRNL) +#ifdef IUCLC + SSH_ENCODE_INPUT_OPT(IUCLC) +#endif + SSH_ENCODE_INPUT_OPT(IXON) + SSH_ENCODE_INPUT_OPT(IXANY) + SSH_ENCODE_INPUT_OPT(IXOFF) + SSH_ENCODE_INPUT_OPT(IMAXBEL) +#ifdef IUTF8 + SSH_ENCODE_INPUT_OPT(IUTF8) +#endif +#undef SSH_ENCODE_INPUT_OPT + +#define SSH_ENCODE_OUTPUT_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_oflag & opt) ? 1 : 0) + SSH_ENCODE_OUTPUT_OPT(OPOST) +#ifdef OLCUC + SSH_ENCODE_OUTPUT_OPT(OLCUC) +#endif + SSH_ENCODE_OUTPUT_OPT(ONLCR) + SSH_ENCODE_OUTPUT_OPT(OCRNL) + SSH_ENCODE_OUTPUT_OPT(ONOCR) + SSH_ENCODE_OUTPUT_OPT(ONLRET) +#undef SSH_ENCODE_OUTPUT_OPT + +#define SSH_ENCODE_CONTROL_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_cflag & opt) ? 1 : 0) + SSH_ENCODE_CONTROL_OPT(CS7) + SSH_ENCODE_CONTROL_OPT(CS8) + SSH_ENCODE_CONTROL_OPT(PARENB) + SSH_ENCODE_CONTROL_OPT(PARODD) +#undef SSH_ENCODE_CONTROL_OPT + +#define SSH_ENCODE_LOCAL_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_lflag & opt) ? 1 : 0) + SSH_ENCODE_LOCAL_OPT(ISIG) + SSH_ENCODE_LOCAL_OPT(ICANON) +#ifdef XCASE + SSH_ENCODE_LOCAL_OPT(XCASE) +#endif + SSH_ENCODE_LOCAL_OPT(ECHO) + SSH_ENCODE_LOCAL_OPT(ECHOE) + SSH_ENCODE_LOCAL_OPT(ECHOK) + SSH_ENCODE_LOCAL_OPT(ECHONL) + SSH_ENCODE_LOCAL_OPT(NOFLSH) + SSH_ENCODE_LOCAL_OPT(TOSTOP) + SSH_ENCODE_LOCAL_OPT(IEXTEN) + SSH_ENCODE_LOCAL_OPT(ECHOCTL) + SSH_ENCODE_LOCAL_OPT(ECHOKE) + SSH_ENCODE_LOCAL_OPT(PENDIN) +#undef SSH_ENCODE_LOCAL_OPT + +#define SSH_ENCODE_CC_OPT(opt) SSH_ENCODE_OPT(TTY_OP_##opt, attr->c_cc[opt]) + SSH_ENCODE_CC_OPT(VINTR) + SSH_ENCODE_CC_OPT(VQUIT) + SSH_ENCODE_CC_OPT(VERASE) + SSH_ENCODE_CC_OPT(VKILL) + SSH_ENCODE_CC_OPT(VEOF) + SSH_ENCODE_CC_OPT(VEOL) + SSH_ENCODE_CC_OPT(VEOL2) + SSH_ENCODE_CC_OPT(VSTART) + SSH_ENCODE_CC_OPT(VSTOP) + SSH_ENCODE_CC_OPT(VSUSP) +#ifdef VDSUSP + SSH_ENCODE_CC_OPT(VDSUSP) +#endif + SSH_ENCODE_CC_OPT(VREPRINT) + SSH_ENCODE_CC_OPT(VWERASE) + SSH_ENCODE_CC_OPT(VLNEXT) +#ifdef VFLUSH + SSH_ENCODE_CC_OPT(VFLUSH) +#endif +#ifdef VSWTC + SSH_ENCODE_CC_OPT(VSWTC) +#endif +#ifdef VSTATUS + SSH_ENCODE_CC_OPT(VSTATUS) +#endif + SSH_ENCODE_CC_OPT(VDISCARD) +#undef SSH_ENCODE_CC_OPT + + SSH_ENCODE_OPT(TTY_OP_ISPEED, baud2speed(cfgetispeed(attr))) + SSH_ENCODE_OPT(TTY_OP_OSPEED, baud2speed(cfgetospeed(attr))) +#undef SSH_ENCODE_OPT + + /* end of options */ + if (buflen > offset) { + buf[offset++] = TTY_OP_END; + } else { + return -1; + } + + return (int)offset; +} +#endif + +/** + * Encodes a set of default options to ensure "sane" PTY behavior. + * This function intentionally doesn't use the \c termios structure + * to allow it to work on Windows as well. + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +static int +encode_default_opts(unsigned char *buf, size_t buflen) +{ + unsigned int offset = 0; + int rc; + +#define SSH_ENCODE_OPT(code, value) \ + rc = encode_termios_opt(code, value, buf + offset, buflen - offset); \ + if (rc < 0) { \ + return rc; \ + } else { \ + offset += rc; \ + } + + SSH_ENCODE_OPT(TTY_OP_VINTR, 003) + SSH_ENCODE_OPT(TTY_OP_VQUIT, 034) + SSH_ENCODE_OPT(TTY_OP_VERASE, 0177) + SSH_ENCODE_OPT(TTY_OP_VKILL, 025) + SSH_ENCODE_OPT(TTY_OP_VEOF, 0) + SSH_ENCODE_OPT(TTY_OP_VEOL, 0) + SSH_ENCODE_OPT(TTY_OP_VEOL2, 0) + SSH_ENCODE_OPT(TTY_OP_VSTART, 021) + SSH_ENCODE_OPT(TTY_OP_VSTOP, 023) + SSH_ENCODE_OPT(TTY_OP_VSUSP, 032) + SSH_ENCODE_OPT(TTY_OP_VDSUSP, 031) + SSH_ENCODE_OPT(TTY_OP_VREPRINT, 022) + SSH_ENCODE_OPT(TTY_OP_VWERASE, 027) + SSH_ENCODE_OPT(TTY_OP_VLNEXT, 026) + SSH_ENCODE_OPT(TTY_OP_VDISCARD, 017) + SSH_ENCODE_OPT(TTY_OP_IGNPAR, 0) + SSH_ENCODE_OPT(TTY_OP_PARMRK, 0) + SSH_ENCODE_OPT(TTY_OP_INPCK, 0) + SSH_ENCODE_OPT(TTY_OP_ISTRIP, 0) + SSH_ENCODE_OPT(TTY_OP_INLCR, 0) + SSH_ENCODE_OPT(TTY_OP_IGNCR, 0) + SSH_ENCODE_OPT(TTY_OP_ICRNL, 0) + SSH_ENCODE_OPT(TTY_OP_IUCLC, 0) + SSH_ENCODE_OPT(TTY_OP_IXON, 1) + SSH_ENCODE_OPT(TTY_OP_IXANY, 0) + SSH_ENCODE_OPT(TTY_OP_IXOFF, 0) + SSH_ENCODE_OPT(TTY_OP_IMAXBEL, 0) + SSH_ENCODE_OPT(TTY_OP_IUTF8, 1) + SSH_ENCODE_OPT(TTY_OP_ISIG, 1) + SSH_ENCODE_OPT(TTY_OP_ICANON, 1) + SSH_ENCODE_OPT(TTY_OP_XCASE, 0) + SSH_ENCODE_OPT(TTY_OP_ECHO, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOE, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOK, 1) + SSH_ENCODE_OPT(TTY_OP_ECHONL, 0) + SSH_ENCODE_OPT(TTY_OP_NOFLSH, 0) + SSH_ENCODE_OPT(TTY_OP_TOSTOP, 0) + SSH_ENCODE_OPT(TTY_OP_IEXTEN, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOCTL, 0) + SSH_ENCODE_OPT(TTY_OP_ECHOKE, 1) + SSH_ENCODE_OPT(TTY_OP_PENDIN, 0) + SSH_ENCODE_OPT(TTY_OP_OPOST, 1) + SSH_ENCODE_OPT(TTY_OP_OLCUC, 0) + SSH_ENCODE_OPT(TTY_OP_ONLCR, 0) + SSH_ENCODE_OPT(TTY_OP_OCRNL, 0) + SSH_ENCODE_OPT(TTY_OP_ONOCR, 0) + SSH_ENCODE_OPT(TTY_OP_ONLRET, 0) + SSH_ENCODE_OPT(TTY_OP_CS7, 1) + SSH_ENCODE_OPT(TTY_OP_CS8, 1) + SSH_ENCODE_OPT(TTY_OP_PARENB, 0) + SSH_ENCODE_OPT(TTY_OP_PARODD, 0) + SSH_ENCODE_OPT(TTY_OP_ISPEED, 38400); + SSH_ENCODE_OPT(TTY_OP_OSPEED, 38400); + +#undef SSH_ENCODE_OPT + + /* end of options */ + if (buflen > offset) { + buf[offset++] = TTY_OP_END; + } else { + return -1; + } + + return (int)offset; +} + +/** + * @ingroup libssh_misc + * + * @brief Encode the current TTY options as SSH modes. + * + * Call this function to determine the settings of the process' TTY and + * encode them as SSH Terminal Modes according to RFC 4254 section 8. + * + * If STDIN isn't connected to a TTY, this function fills the buffer with + * "sane" default modes. + * + * The encoded modes can be passed to \c ssh_channel_request_pty_size_modes . + * + * @code + * unsigned char modes_buf[SSH_TTY_MODES_MAX_BUFSIZE]; + * encode_current_tty_opts(modes_buf, sizeof(modes_buf)); + * @endcode + * + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +int +encode_current_tty_opts(unsigned char *buf, size_t buflen) +{ +#ifdef HAVE_TERMIOS_H + struct termios attr; + ZERO_STRUCT(attr); + + if (isatty(STDIN_FILENO)) { + /* get local terminal attributes */ + if (tcgetattr(STDIN_FILENO, &attr) < 0) { + perror("tcgetattr"); + return -1; + } + return encode_termios_opts(&attr, buf, buflen); + } +#endif + + /* use "sane" default attributes */ + return encode_default_opts(buf, buflen); +} diff --git a/src/wrapper.c b/src/wrapper.c index bbd4e4b2..bf949ea9 100644 --- a/src/wrapper.c +++ b/src/wrapper.c @@ -1,5 +1,5 @@ /* - * wrapper.c - wrapper for crytpo functions + * wrapper.c - wrapper for crypto functions * * This file is part of the SSH Library * @@ -25,7 +25,7 @@ * Why a wrapper? * * Let's say you want to port libssh from libcrypto of openssl to libfoo - * you are going to spend hours to remove every references to SHA1_Update() + * you are going to spend hours removing every reference to SHA1_Update() * to libfoo_sha1_update after the work is finished, you're going to have * only this file to modify it's not needed to say that your modifications * are welcome. @@ -108,7 +108,7 @@ const char *ssh_hmac_type_to_string(enum ssh_hmac_e hmac_type, bool etm) } /* it allocates a new cipher structure based on its offset into the global table */ -static struct ssh_cipher_struct *cipher_new(int offset) { +static struct ssh_cipher_struct *cipher_new(uint8_t offset) { struct ssh_cipher_struct *cipher = NULL; cipher = malloc(sizeof(struct ssh_cipher_struct)); @@ -150,15 +150,16 @@ static void cipher_free(struct ssh_cipher_struct *cipher) { SAFE_FREE(cipher); } -struct ssh_crypto_struct *crypto_new(void) { - struct ssh_crypto_struct *crypto; +struct ssh_crypto_struct *crypto_new(void) +{ + struct ssh_crypto_struct *crypto; - crypto = malloc(sizeof(struct ssh_crypto_struct)); - if (crypto == NULL) { - return NULL; - } - ZERO_STRUCTP(crypto); - return crypto; + crypto = malloc(sizeof(struct ssh_crypto_struct)); + if (crypto == NULL) { + return NULL; + } + ZERO_STRUCTP(crypto); + return crypto; } void crypto_free(struct ssh_crypto_struct *crypto) @@ -176,9 +177,13 @@ void crypto_free(struct ssh_crypto_struct *crypto) #ifdef HAVE_ECDH SAFE_FREE(crypto->ecdh_client_pubkey); SAFE_FREE(crypto->ecdh_server_pubkey); - if(crypto->ecdh_privkey != NULL){ + if (crypto->ecdh_privkey != NULL) { #ifdef HAVE_OPENSSL_ECC +#if OPENSSL_VERSION_NUMBER < 0x30000000L EC_KEY_free(crypto->ecdh_privkey); +#else + EVP_PKEY_free(crypto->ecdh_privkey); +#endif /* OPENSSL_VERSION_NUMBER */ #elif defined HAVE_GCRYPT_ECC gcry_sexp_release(crypto->ecdh_privkey); #endif @@ -187,7 +192,7 @@ void crypto_free(struct ssh_crypto_struct *crypto) #endif SAFE_FREE(crypto->dh_server_signature); if (crypto->session_id != NULL) { - explicit_bzero(crypto->session_id, crypto->digest_len); + explicit_bzero(crypto->session_id, crypto->session_id_len); SAFE_FREE(crypto->session_id); } if (crypto->secret_hash != NULL) { @@ -234,12 +239,35 @@ void crypto_free(struct ssh_crypto_struct *crypto) SAFE_FREE(crypto); } +static void +compression_enable(ssh_session session, + enum ssh_crypto_direction_e direction, + bool delayed) +{ + /* The delayed compression is turned on AFTER authentication. This means + * that we need to turn it on immediately in case of rekeying */ + if (delayed && !(session->flags & SSH_SESSION_FLAG_AUTHENTICATED)) { + if (direction == SSH_DIRECTION_IN) { + session->next_crypto->delayed_compress_in = 1; + } else { /* SSH_DIRECTION_OUT */ + session->next_crypto->delayed_compress_out = 1; + } + } else { + if (direction == SSH_DIRECTION_IN) { + session->next_crypto->do_compress_in = 1; + } else { /* SSH_DIRECTION_OUT */ + session->next_crypto->do_compress_out = 1; + } + } +} + static int crypt_set_algorithms2(ssh_session session) { const char *wanted = NULL; + const char *method = NULL; struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab(); struct ssh_hmac_struct *ssh_hmactab=ssh_get_hmactab(); - size_t i = 0; + uint8_t i = 0; int cmp; /* @@ -359,22 +387,29 @@ static int crypt_set_algorithms2(ssh_session session) session->next_crypto->in_hmac = ssh_hmactab[i].hmac_type; session->next_crypto->in_hmac_etm = ssh_hmactab[i].etm; - /* compression */ - cmp = strcmp(session->next_crypto->kex_methods[SSH_COMP_C_S], "zlib"); + /* compression: client */ + method = session->next_crypto->kex_methods[SSH_COMP_C_S]; + cmp = strcmp(method, "zlib"); if (cmp == 0) { - session->next_crypto->do_compress_out = 1; + SSH_LOG(SSH_LOG_PACKET, "enabling C->S compression"); + compression_enable(session, SSH_DIRECTION_OUT, false); } - cmp = strcmp(session->next_crypto->kex_methods[SSH_COMP_S_C], "zlib"); + cmp = strcmp(method, "zlib@openssh.com"); if (cmp == 0) { - session->next_crypto->do_compress_in = 1; + SSH_LOG(SSH_LOG_PACKET, "enabling C->S delayed compression"); + compression_enable(session, SSH_DIRECTION_OUT, true); } - cmp = strcmp(session->next_crypto->kex_methods[SSH_COMP_C_S], "zlib@openssh.com"); + + method = session->next_crypto->kex_methods[SSH_COMP_S_C]; + cmp = strcmp(method, "zlib"); if (cmp == 0) { - session->next_crypto->delayed_compress_out = 1; + SSH_LOG(SSH_LOG_PACKET, "enabling S->C compression"); + compression_enable(session, SSH_DIRECTION_IN, false); } - cmp = strcmp(session->next_crypto->kex_methods[SSH_COMP_S_C], "zlib@openssh.com"); + cmp = strcmp(method, "zlib@openssh.com"); if (cmp == 0) { - session->next_crypto->delayed_compress_in = 1; + SSH_LOG(SSH_LOG_PACKET, "enabling S->C delayed compression"); + compression_enable(session, SSH_DIRECTION_IN, true); } return SSH_OK; @@ -388,12 +423,11 @@ int crypt_set_algorithms_client(ssh_session session) #ifdef WITH_SERVER int crypt_set_algorithms_server(ssh_session session){ const char *method = NULL; - size_t i = 0; + uint8_t i = 0; struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab(); struct ssh_hmac_struct *ssh_hmactab=ssh_get_hmactab(); int cmp; - if (session == NULL) { return SSH_ERROR; } @@ -512,33 +546,27 @@ int crypt_set_algorithms_server(ssh_session session){ /* compression */ method = session->next_crypto->kex_methods[SSH_COMP_C_S]; - if(strcmp(method,"zlib") == 0){ - SSH_LOG(SSH_LOG_PACKET,"enabling C->S compression"); - session->next_crypto->do_compress_in=1; + cmp = strcmp(method, "zlib"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling C->S compression"); + compression_enable(session, SSH_DIRECTION_IN, false); } - if(strcmp(method,"zlib@openssh.com") == 0){ - SSH_LOG(SSH_LOG_PACKET,"enabling C->S delayed compression"); - - if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) { - session->next_crypto->do_compress_in = 1; - } else { - session->next_crypto->delayed_compress_in = 1; - } + cmp = strcmp(method, "zlib@openssh.com"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling C->S delayed compression"); + compression_enable(session, SSH_DIRECTION_IN, true); } method = session->next_crypto->kex_methods[SSH_COMP_S_C]; - if(strcmp(method,"zlib") == 0){ + cmp = strcmp(method, "zlib"); + if (cmp == 0) { SSH_LOG(SSH_LOG_PACKET, "enabling S->C compression"); - session->next_crypto->do_compress_out=1; + compression_enable(session, SSH_DIRECTION_OUT, false); } - if(strcmp(method,"zlib@openssh.com") == 0){ - SSH_LOG(SSH_LOG_PACKET,"enabling S->C delayed compression"); - - if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) { - session->next_crypto->do_compress_out = 1; - } else { - session->next_crypto->delayed_compress_out = 1; - } + cmp = strcmp(method, "zlib@openssh.com"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling S->C delayed compression"); + compression_enable(session, SSH_DIRECTION_OUT, true); } method = session->next_crypto->kex_methods[SSH_HOSTKEYS]; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 44d4f201..46c19ff7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,9 +6,7 @@ endif (BSD OR SOLARIS OR OSX) set(TORTURE_LIBRARY torture) -include_directories(${OPENSSL_INCLUDE_DIR} - ${CMOCKA_INCLUDE_DIR} - ${ZLIB_INCLUDE_DIR} +include_directories(${CMOCKA_INCLUDE_DIR} ${libssh_BINARY_DIR}/include ${libssh_BINARY_DIR} ${libssh_SOURCE_DIR}/src @@ -28,10 +26,13 @@ add_library(${TORTURE_LIBRARY} torture_key.c torture_pki.c torture_cmocka.c) -target_link_libraries(${TORTURE_LIBRARY} ${TORTURE_LINK_LIBRARIES}) +target_link_libraries(${TORTURE_LIBRARY} PRIVATE ${TORTURE_LINK_LIBRARIES}) target_compile_options(${TORTURE_LIBRARY} PRIVATE -DSSH_PING_EXECUTABLE="${CMAKE_CURRENT_BINARY_DIR}/ssh_ping" ) +if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(${TORTURE_LIBRARY}) +endif (WITH_COVERAGE) # The shared version of the library is only useful when client testing is # enabled @@ -39,10 +40,6 @@ if (CLIENT_TESTING) # create shared test library set(TORTURE_SHARED_LIBRARY torture_shared) - if (MINGW) - set(USE_ATTRIBUTE_WEAK "-DUSE_ATTRIBUTE_WEAK") - endif () - # Create a list of symbols that should be wrapped for override test set(WRAP_SYMBOLS "") list(APPEND WRAP_SYMBOLS @@ -66,20 +63,23 @@ if (CLIENT_TESTING) torture_pki.c torture_cmocka.c ) - target_link_libraries(${TORTURE_SHARED_LIBRARY} + target_link_libraries(${TORTURE_SHARED_LIBRARY} PUBLIC ${CMOCKA_LIBRARY} ssh::static ${WRAP_SYMBOLS} ) target_compile_options(${TORTURE_SHARED_LIBRARY} PRIVATE -DSSH_PING_EXECUTABLE="${CMAKE_CURRENT_BINARY_DIR}/ssh_ping" - ${USE_ATTRIBUTE_WEAK} + -DTORTURE_SHARED ) + if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(${TORTURE_SHARED_LIBRARY}) + endif (WITH_COVERAGE) endif () -if (ARGP_LIBRARY) +if (ARGP_LIBRARIES) target_link_libraries(${TORTURE_LIBRARY} - ${ARGP_LIBRARY} + PUBLIC ${ARGP_LIBRARIES} ) endif() @@ -103,7 +103,7 @@ if (SSH_EXECUTABLE) set(OPENSSH_CIPHERS "aes128-ctr\naes192-ctr\naes256-ctr\narcfour256\narcfour128\naes128-gcm@openssh.com\naes256-gcm@openssh.com\naes128-cbc\n3des-cbc\nblowfish-cbc\ncast128-cbc\naes192-cbc\naes256-cbc\narcfour\nrijndael-cbc@lysator.liu.se\n") set(OPENSSH_MACS "hmac-md5-etm@openssh.com\nhmac-sha1-etm@openssh.com\numac-64-etm@openssh.com\numac-128-etm@openssh.com\nhmac-sha2-256-etm@openssh.com\nhmac-sha2-512-etm@openssh.com\nhmac-ripemd160-etm@openssh.com\nhmac-sha1-96-etm@openssh.com\nhmac-md5-96-etm@openssh.com\nhmac-md5\nhmac-sha1\numac-64@openssh.com\numac-128@openssh.com\nhmac-sha2-256\nhmac-sha2-512\nhmac-ripemd160\nhmac-ripemd160@openssh.com\nhmac-sha1-96\nhmac-md5-96\n") set(OPENSSH_KEX "ecdh-sha2-nistp256\necdh-sha2-nistp384\necdh-sha2-nistp521\ndiffie-hellman-group-exchange-sha256\ndiffie-hellman-group-exchange-sha1\ndiffie-hellman-group14-sha1\ndiffie-hellman-group1-sha1\n") - set(OPENSSH_KEYS "ssh-rsa\nssh-dss\necdsa-sha2-nistp256\n") + set(OPENSSH_KEYS "ssh-rsa\necdsa-sha2-nistp256\n") else() execute_process(COMMAND ${SSH_EXECUTABLE} -Q cipher OUTPUT_VARIABLE OPENSSH_CIPHERS) execute_process(COMMAND ${SSH_EXECUTABLE} -Q mac OUTPUT_VARIABLE OPENSSH_MACS) @@ -130,9 +130,9 @@ if (SSH_EXECUTABLE) diffie-hellman-group16-sha512 diffie-hellman-group18-sha512 diffie-hellman-group-exchange-sha1 diffie-hellman-group-exchange-sha256 ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521 curve25519-sha256 curve25519-sha256@libssh.org - ssh-ed25519 ssh-ed25519-cert-v01@openssh.com ssh-rsa ssh-dss + ssh-ed25519 ssh-ed25519-cert-v01@openssh.com ssh-rsa ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 - ssh-rsa-cert-v01@openssh.com ssh-dss-cert-v01@openssh.com + ssh-rsa-cert-v01@openssh.com ecdsa-sha2-nistp256-cert-v01@openssh.com ecdsa-sha2-nistp384-cert-v01@openssh.com ecdsa-sha2-nistp521-cert-v01@openssh.com ) @@ -169,13 +169,14 @@ if (CLIENT_TESTING OR SERVER_TESTING) 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_package(priv_wrapper 1.0.0) if (NOT SSHD_EXECUTABLE) message(SEND_ERROR "Could not find sshd which is required for client testing") endif() - find_program(NC_EXECUTABLE + find_program(NCAT_EXECUTABLE NAME - nc + ncat PATHS /bin /usr/bin @@ -186,6 +187,20 @@ if (CLIENT_TESTING OR SERVER_TESTING) if (NOT SOFTHSM_FOUND) message(SEND_ERROR "Could not find softhsm module!") endif (NOT SOFTHSM_FOUND) + find_library(PKCS11SPY + NAMES + pkcs11-spy.so + ) + if (WITH_PKCS11_PROVIDER) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(P11_KIT p11-kit-1) + if (P11_KIT_FOUND) + pkg_get_variable(P11_MODULE_PATH p11-kit-1 p11_module_path) + set(P11_KIT_CLIENT ${P11_MODULE_PATH}/p11-kit-client.so) + endif (P11_KIT_FOUND) + endif (PKG_CONFIG_FOUND) + endif (WITH_PKCS11_PROVIDER) endif (WITH_PKCS11_URI) find_program(SSH_EXECUTABLE NAMES ssh) @@ -216,13 +231,28 @@ if (CLIENT_TESTING OR SERVER_TESTING) set(WITH_TIMEOUT "1") 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}) + # For chroot() use priv_wrapper package if found, or internal chroot_wrapper + if (priv_wrapper_FOUND) + set(CHROOT_WRAPPER "${PRIV_WRAPPER_LIBRARY}") + else() + 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 + ) + set(CHROOT_WRAPPER "${CHROOT_WRAPPER_LIBRARY}") + endif() + + # chown wrapper + add_library(chown_wrapper SHARED chown_wrapper.c) + set(CHOWN_WRAPPER_LIBRARY + ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}chown_wrapper${CMAKE_SHARED_LIBRARY_SUFFIX}) set(TEST_TARGET_LIBRARIES ${TEST_TARGET_LIBRARIES} - chroot_wrapper + chown_wrapper ) + set(CHOWN_WRAPPER "${CHOWN_WRAPPER_LIBRARY}") # ssh_ping add_executable(ssh_ping ssh_ping.c) @@ -243,12 +273,19 @@ if (CLIENT_TESTING OR SERVER_TESTING) 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}") + set(TORTURE_ENVIRONMENT + "LD_PRELOAD=${SOCKET_WRAPPER_LIBRARY}:${NSS_WRAPPER_LIBRARY}:${UID_WRAPPER_LIBRARY}:${PAM_WRAPPER_LIBRARY}:${CHROOT_WRAPPER}:${CHOWN_WRAPPER}") + if (priv_wrapper_FOUND) + list(APPEND TORTURE_ENVIRONMENT PRIV_WRAPPER=1 PRIV_WRAPPER_CHROOT_DISABLE=1) + list(APPEND TORTURE_ENVIRONMENT PRIV_WRAPPER_PRCTL_DISABLE="ALL" PRIV_WRAPPER_SETRLIMIT_DISABLE="ALL") + endif() 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) + list(APPEND TORTURE_ENVIRONMENT LSAN_OPTIONS=suppressions=${CMAKE_CURRENT_SOURCE_DIR}/suppressions/lsan.supp) + list(APPEND TORTURE_ENVIRONMENT OPENSSL_ENABLE_SHA1_SIGNATURES=1) # Give bob some keys file(COPY keys/id_rsa DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) @@ -285,16 +322,21 @@ if (CLIENT_TESTING OR SERVER_TESTING) file(READ keys/pkcs11/id_pkcs11_ecdsa_521_openssh.pub CONTENTS) file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.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) + # Copy the signed key to an doe's homedir. + file(COPY keys/certauth/id_rsa DESTINATION + ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/certauth/id_rsa.pub DESTINATION + ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/certauth/id_rsa-cert.pub DESTINATION + ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) +endif () +if (WITH_PKCS11_URI) #Copy the script to setup PKCS11 tokens file(COPY pkcs11/setup-softhsm-tokens.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/pkcs11 FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) +endif (WITH_PKCS11_URI) - message(STATUS "TORTURE_ENVIRONMENT=${TORTURE_ENVIRONMENT}") -endif () +message(STATUS "TORTURE_ENVIRONMENT=${TORTURE_ENVIRONMENT}") configure_file(tests_config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/tests_config.h) @@ -317,3 +359,12 @@ endif () if (FUZZ_TESTING) add_subdirectory(fuzz) endif() + +add_custom_target(test_memcheck + # FIXME: The threads_pki_rsa test is skipped under valgrind as it times out + # Passing suppression file is also stupid so lets go with override here: + # https://stackoverflow.com/a/56116311 + COMMAND ${CMAKE_CTEST_COMMAND} -E torture_threads_pki_rsa -E pkd_hello_i1 + --output-on-failure --force-new-ctest-process --test-action memcheck + --overwrite MemoryCheckSuppressionFile=${CMAKE_SOURCE_DIR}/tests/valgrind.supp + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") diff --git a/tests/benchmarks/CMakeLists.txt b/tests/benchmarks/CMakeLists.txt index dbaa4d51..4074b8f4 100644 --- a/tests/benchmarks/CMakeLists.txt +++ b/tests/benchmarks/CMakeLists.txt @@ -1,11 +1,17 @@ project(libssh-benchmarks C) set(benchmarks_SRCS - bench_scp.c bench_sftp bench_raw.c benchmarks.c latency.c + bench_scp.c bench_raw.c benchmarks.c latency.c ) +if (WITH_SFTP) + set(benchmarks_SRCS + ${benchmarks_SRCS} + bench_sftp.c + ) +endif (WITH_SFTP) include_directories(${libssh_BINARY_DIR}) add_executable(benchmarks ${benchmarks_SRCS}) -target_link_libraries(benchmarks ssh::ssh) +target_link_libraries(benchmarks ssh::static) diff --git a/tests/benchmarks/bench1.sh b/tests/benchmarks/bench1.sh index 4458e9a7..6b3b20f3 100755 --- a/tests/benchmarks/bench1.sh +++ b/tests/benchmarks/bench1.sh @@ -1,13 +1,14 @@ +#!/bin/bash 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: `ssh -V 2>&1`" +echo "local machine: $(uname -a)" +echo "Cipher : $CIPHER ; Destination : $DEST ($(ssh $DEST uname -a))" +echo "Local ssh version: $(ssh -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 ssh $DEST 'id > /dev/null') 2>&1`" +echo "Destination $DEST SSHD version : $(echo | nc $DEST 22 | head -n1)" +echo "ssh login latency :$( (command 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 index 01d67777..cf240fda 100755 --- a/tests/benchmarks/bench2.sh +++ b/tests/benchmarks/bench2.sh @@ -1,13 +1,14 @@ +#!/bin/bash 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 "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`" +echo "Destination $DEST SSHD version : $(echo | nc $DEST 22 | head -n1)" +echo "ssh login latency :$( (command 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 index db1a057c..0fd1446d 100644 --- a/tests/benchmarks/bench_raw.c +++ b/tests/benchmarks/bench_raw.c @@ -49,6 +49,9 @@ static char *get_python_eater(unsigned long bytes){ char *ptr; char buf[12]; + if (eater == NULL) { + return NULL; + } memcpy(eater,python_eater,sizeof(python_eater)); ptr=strstr(eater,"XXXXXXXXXX"); if(!ptr){ @@ -116,7 +119,10 @@ int benchmarks_raw_up (ssh_session session, struct argument_s *args, unsigned long total=0; bytes = args->datasize * 1024 * 1024; - script =get_python_eater(bytes); + script = get_python_eater(bytes); + if (script == NULL) { + return -1; + } err=upload_script(session,"/tmp/eater.py",script); free(script); if(err<0) @@ -205,6 +211,9 @@ static char *get_python_giver(unsigned long bytes){ char *ptr; char buf[12]; + if (giver == NULL) { + return NULL; + } memcpy(giver,python_giver,sizeof(python_giver)); ptr=strstr(giver,"XXXXXXXXXX"); if(!ptr){ @@ -236,7 +245,10 @@ int benchmarks_raw_down (ssh_session session, struct argument_s *args, unsigned long total=0; bytes = args->datasize * 1024 * 1024; - script =get_python_giver(bytes); + script = get_python_giver(bytes); + if (script == NULL) { + return -1; + } err=upload_script(session,"/tmp/giver.py",script); free(script); if(err<0) diff --git a/tests/benchmarks/bench_sftp.c b/tests/benchmarks/bench_sftp.c index 601ecec0..e6c68248 100644 --- a/tests/benchmarks/bench_sftp.c +++ b/tests/benchmarks/bench_sftp.c @@ -23,6 +23,7 @@ #include "benchmarks.h" #include <libssh/libssh.h> #include <libssh/sftp.h> +#include <libssh/misc.h> #include <stdio.h> #include <fcntl.h> #include <stdlib.h> @@ -180,6 +181,9 @@ int benchmarks_async_sftp_down (ssh_session session, struct argument_s *args, if(!file) goto error; ids = malloc(concurrent_downloads * sizeof(int)); + if (ids == NULL) { + return -1; + } if(args->verbose>0) fprintf(stdout,"Starting download of %lu bytes now, using %d concurrent downloads\n",bytes, concurrent_downloads); @@ -237,3 +241,396 @@ error: free(ids); return -1; } + +int benchmarks_async_sftp_aio_down(ssh_session session, + struct argument_s *args, + float *bps) +{ + sftp_session sftp = NULL; + sftp_limits_t li = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + + struct ssh_list *aio_queue = NULL; + + int concurrent_downloads = args->concurrent_requests; + size_t chunksize; + struct timestamp_struct ts = {0}; + float ms = 0.0f; + + size_t total_bytes = args->datasize * 1024 * 1024; + size_t total_bytes_requested = 0, total_bytes_read = 0; + size_t bufsize = args->chunksize; + size_t to_read; + ssize_t bytes_read, bytes_requested; + int warned = 0, i, rc; + + sftp = sftp_new(session); + if (sftp == NULL) { + fprintf(stderr, "Error during sftp aio download: %s\n", + ssh_get_error(session)); + return -1; + } + + /* + * Errors which are logged in the ssh session are reported after + * jumping to the goto label, errors which aren't logged inside the + * ssh session are reported before jumping to that label + */ + + rc = sftp_init(sftp); + if (rc == SSH_ERROR) { + goto error; + } + + li = sftp_limits(sftp); + if (li == NULL) { + goto error; + } + + if (args->chunksize > li->max_read_length) { + chunksize = li->max_read_length; + if (args->verbose > 0) { + fprintf(stdout, + "Using the chunk size %zu (not the set size %u), " + "to respect the max data limit for read packet\n", + chunksize, args->chunksize); + } + } else { + chunksize = args->chunksize; + } + + file = sftp_open(sftp, SFTPDIR SFTPFILE, O_RDONLY, 0); + if (file == NULL) { + goto error; + } + + aio_queue = ssh_list_new(); + if (aio_queue == NULL) { + fprintf(stderr, + "Error during sftp aio download: Insufficient memory\n"); + goto error; + } + + if (args->verbose > 0) { + fprintf(stdout, + "Starting download of %zu bytes now, " + "using %d concurrent downloads.\n", + total_bytes, concurrent_downloads); + } + + timestamp_init(&ts); + + for (i = 0; + i < concurrent_downloads && total_bytes_requested < total_bytes; + ++i) { + to_read = total_bytes - total_bytes_requested; + if (to_read > chunksize) { + to_read = chunksize; + } + + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { + goto error; + } + + if ((size_t)bytes_requested != to_read) { + fprintf(stderr, + "Error during sftp aio download: sftp_aio_begin_read() " + "requesting less bytes even when the number of bytes " + "asked to read are within the max limit"); + sftp_aio_free(aio); + goto error; + } + + total_bytes_requested += (size_t)bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + if (rc == SSH_ERROR) { + fprintf(stderr, + "Error during sftp aio download: Insufficient memory"); + sftp_aio_free(aio); + goto error; + } + } + + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + bytes_read = sftp_aio_wait_read(&aio, buffer, bufsize); + if (bytes_read == -1) { + goto error; + } + + total_bytes_read += (size_t)bytes_read; + if (bytes_read == 0) { + fprintf(stdout , + "File smaller than expected: %zu bytes (expected %zu).\n", + total_bytes_read, total_bytes); + break; + } + + if (total_bytes_read != total_bytes && + (size_t)bytes_read != chunksize && + warned != 1) { + fprintf(stderr, + "async_sftp_aio_download: Receiving short reads " + "(%zu, expected %zu) before encountering eof, " + "the received file will be corrupted and shorted. " + "Adapt chunksize to %zu.\n", + bytes_read, chunksize, bytes_read); + warned = 1; + } + + if (total_bytes_requested == total_bytes) { + /* No need to issue more requests */ + continue; + } + + /* else issue a request */ + to_read = total_bytes - total_bytes_requested; + if (to_read > chunksize) { + to_read = chunksize; + } + + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { + goto error; + } + + if ((size_t)bytes_requested != to_read) { + fprintf(stderr, + "Error during sftp aio download: sftp_aio_begin_read() " + "requesting less bytes even when the number of bytes " + "asked to read are within the max limit"); + sftp_aio_free(aio); + goto error; + } + + total_bytes_requested += (size_t)bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + if (rc == SSH_ERROR) { + fprintf(stderr, + "Error during sftp aio download: Insufficient memory\n"); + sftp_aio_free(aio); + goto error; + } + } + + sftp_close(file); + ms = elapsed_time(&ts); + *bps = (float)(8000 * total_bytes_read) / ms; + if (args->verbose > 0) { + fprintf(stdout, "Download took %f ms for %zu bytes at %f bps.\n", + ms, total_bytes_read, *bps); + } + + ssh_list_free(aio_queue); + sftp_limits_free(li); + sftp_free(sftp); + return 0; + +error: + rc = ssh_get_error_code(session); + if (rc != SSH_NO_ERROR) { + fprintf(stderr, "Error during sftp aio download: %s\n", + ssh_get_error(session)); + } + + /* Release aio structures corresponding to outstanding requests */ + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + sftp_aio_free(aio); + } + + ssh_list_free(aio_queue); + sftp_close(file); + sftp_limits_free(li); + sftp_free(sftp); + return -1; +} + +int benchmarks_async_sftp_aio_up(ssh_session session, + struct argument_s *args, + float *bps) +{ + sftp_session sftp = NULL; + sftp_limits_t li = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + struct ssh_list *aio_queue = NULL; + + int concurrent_uploads = args->concurrent_requests; + size_t chunksize; + struct timestamp_struct ts = {0}; + float ms = 0.0f; + + size_t total_bytes = args->datasize * 1024 * 1024; + size_t to_write, total_bytes_requested = 0; + ssize_t bytes_written, bytes_requested; + int i, rc; + + sftp = sftp_new(session); + if (sftp == NULL) { + fprintf(stderr, "Error during sftp aio upload: %s\n", + ssh_get_error(session)); + return -1; + } + + /* + * Errors which are logged in the ssh session are reported after + * jumping to the goto label, errors which aren't logged inside the + * ssh session are reported before jumping to that label + */ + + rc = sftp_init(sftp); + if (rc == SSH_ERROR) { + goto error; + } + + li = sftp_limits(sftp); + if (li == NULL) { + goto error; + } + + if (args->chunksize > li->max_write_length) { + chunksize = li->max_write_length; + if (args->verbose > 0) { + fprintf(stdout, + "Using the chunk size %zu (not the set size %u), " + "to respect the max data limit for write packet\n", + chunksize, args->chunksize); + } + } else { + chunksize = args->chunksize; + } + + file = sftp_open(sftp, SFTPDIR SFTPFILE, + O_WRONLY | O_CREAT | O_TRUNC, 0777); + if (file == NULL) { + goto error; + } + + aio_queue = ssh_list_new(); + if (aio_queue == NULL) { + fprintf(stderr, "Error during sftp aio upload: Insufficient memory\n"); + goto error; + } + + if (args->verbose > 0) { + fprintf(stdout, + "Starting upload of %zu bytes now, " + "using %d concurrent uploads.\n", + total_bytes, concurrent_uploads); + } + + timestamp_init(&ts); + + for (i = 0; + i < concurrent_uploads && total_bytes_requested < total_bytes; + ++i) { + to_write = total_bytes - total_bytes_requested; + if (to_write > chunksize) { + to_write = chunksize; + } + + bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (bytes_requested == SSH_ERROR) { + goto error; + } + + if ((size_t)bytes_requested != to_write) { + fprintf(stderr, + "Error during sftp aio upload: sftp_aio_begin_write() " + "requesting less bytes even when the number of bytes " + "asked to write are within the max write limit"); + sftp_aio_free(aio); + goto error; + } + + total_bytes_requested += (size_t)bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + if (rc == SSH_ERROR) { + fprintf(stderr, + "Error during sftp aio upload: Insufficient memory\n"); + sftp_aio_free(aio); + goto error; + } + } + + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + bytes_written = sftp_aio_wait_write(&aio); + if (bytes_written == SSH_ERROR) { + goto error; + } + + if (total_bytes_requested == total_bytes) { + /* No need to issue more requests */ + continue; + } + + /* else issue a request */ + to_write = total_bytes - total_bytes_requested; + if (to_write > chunksize) { + to_write = chunksize; + } + + bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (bytes_requested == SSH_ERROR) { + goto error; + } + + if ((size_t)bytes_requested != to_write) { + fprintf(stderr, + "Error during sftp aio upload: sftp_aio_begin_write() " + "requesting less bytes even when the number of bytes " + "asked to write are within the max write limit"); + sftp_aio_free(aio); + goto error; + } + + total_bytes_requested += bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + if (rc == SSH_ERROR) { + fprintf(stderr, + "Error during sftp aio upload: Insufficient memory\n"); + sftp_aio_free(aio); + goto error; + } + } + + sftp_close(file); + ms = elapsed_time(&ts); + *bps = (float)(8000 * total_bytes) / ms; + if (args->verbose > 0) { + fprintf(stdout, "Upload took %f ms for %zu bytes at %f bps.\n", + ms, total_bytes, *bps); + } + + ssh_list_free(aio_queue); + sftp_limits_free(li); + sftp_free(sftp); + return 0; + +error: + rc = ssh_get_error_code(session); + if (rc != SSH_NO_ERROR) { + fprintf(stderr, "Error during sftp aio upload: %s\n", + ssh_get_error(session)); + } + + /* Release aio structures corresponding to outstanding requests */ + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + sftp_aio_free(aio); + } + + ssh_list_free(aio_queue); + sftp_close(file); + sftp_limits_free(li); + sftp_free(sftp); + return -1; +} diff --git a/tests/benchmarks/benchmarks.c b/tests/benchmarks/benchmarks.c index 5e33dd4b..4229d5a5 100644 --- a/tests/benchmarks/benchmarks.c +++ b/tests/benchmarks/benchmarks.c @@ -19,6 +19,8 @@ * MA 02111-1307, USA. */ +#define LIBSSH_STATIC + #include "config.h" #include "benchmarks.h" #include <libssh/libssh.h> @@ -27,42 +29,54 @@ #include <stdlib.h> #include <stdio.h> -struct benchmark benchmarks[]= { +struct benchmark benchmarks[] = { { - .name="benchmark_raw_upload", - .fct=benchmarks_raw_up, - .enabled=0 + .name = "benchmark_raw_upload", + .fct = benchmarks_raw_up, + .enabled = 0 }, { - .name="benchmark_raw_download", - .fct=benchmarks_raw_down, - .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_upload", + .fct = benchmarks_scp_up, + .enabled = 0 }, { - .name="benchmark_scp_download", - .fct=benchmarks_scp_down, - .enabled=0 + .name = "benchmark_scp_download", + .fct = benchmarks_scp_down, + .enabled = 0 }, +#ifdef WITH_SFTP { - .name="benchmark_sync_sftp_upload", - .fct=benchmarks_sync_sftp_up, - .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_sync_sftp_download", + .fct = benchmarks_sync_sftp_down, + .enabled = 0 }, { .name="benchmark_async_sftp_download", .fct=benchmarks_async_sftp_down, .enabled=0 + }, + { + .name = "benchmark_async_sftp_aio_download", + .fct = benchmarks_async_sftp_aio_down, + .enabled = 0 + }, + { + .name = "benchmark_async_sftp_aio_upload", + .fct = benchmarks_async_sftp_aio_up, + .enabled = 0 } +#endif /* WITH_SFTP */ }; #ifdef HAVE_ARGP_H @@ -71,7 +85,7 @@ struct benchmark benchmarks[]= { const char *argp_program_version = "libssh benchmarks 2011-08-28"; const char *argp_program_bug_address = "Aris Adamantiadis <aris@0xbadc0de.be>"; -static char **cmdline; +static char **cmdline = NULL; /* Program documentation. */ static char doc[] = "libssh benchmarks"; @@ -79,128 +93,142 @@ 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} + { + .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 = "async-sftp-aio-download", + .key = '8', + .arg = NULL, + .flags = 0, + .doc = "Download data using asynchronous SFTP AIO api (fast)", + .group = 0 + }, + { + .name = "async-sftp-aio-upload", + .key = '9', + .arg = NULL, + .flags = 0, + .doc = "Upload data using asynchronous SFTP AIO api (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; +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; + /* arg is currently not used */ + (void) arg; - switch (key) { + switch (key) { case '1': case '2': case '3': @@ -208,42 +236,45 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) { case '5': case '6': case '7': - benchmarks[key - '1'].enabled = 1; - arguments->ntests ++; - break; + case '8': + case '9': + benchmarks[key - '1'].enabled = 1; + arguments->ntests++; + break; case 'v': - arguments->verbose++; - break; + arguments->verbose++; + break; case 's': - arguments->datasize = atoi(arg); - break; + arguments->datasize = atoi(arg); + break; case 'p': - arguments->concurrent_requests = atoi(arg); - break; + arguments->concurrent_requests = atoi(arg); + break; case 'c': - arguments->chunksize = atoi(arg); - break; + arguments->chunksize = atoi(arg); + break; case 'C': - arguments->cipher = arg; - break; + 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; + 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; + /* End processing here. */ + cmdline = &state->argv [state->next - 1]; + state->next = state->argc; + break; default: - return ARGP_ERR_UNKNOWN; - } + return ARGP_ERR_UNKNOWN; + } - return 0; + return 0; } /* Our argp parser. */ @@ -251,150 +282,213 @@ 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. - */ +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); + argp_parse(&argp, argc, argv, 0, 0, arguments); #else /* HAVE_ARGP_H */ - (void) argc; - (void) argv; - arguments->hosts[0]="localhost"; - arguments->nhosts=1; + (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 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; +static ssh_session connect_host(const char *host, int verbose, char *cipher) +{ + ssh_session session = NULL; + int rc; + + session = ssh_new(); + if (session == NULL) { + fprintf(stderr, "Error connecting to \"%s\": %s\n", + host, "Unable to create a new ssh session"); + return NULL; } - } - 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; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, host); + if (rc < 0) + goto error; + + rc = ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose); + if (rc < 0) + goto error; + + if (cipher != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher); + if (rc < 0) + goto error; + + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher); + if (rc < 0) + goto error; + } + + rc = ssh_options_parse_config(session, NULL); + if (rc < 0) + goto error; + + rc = ssh_connect(session); + if (rc == SSH_ERROR) + goto error; + + rc = ssh_userauth_autopubkey(session, NULL); + if (rc != 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; + 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 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 ; i<BENCHMARK_NUMBER ; ++i){ - b = &benchmarks[i]; - if(b->enabled){ - err=b->fct(session,arguments,&bps); - if(err==0){ - fprintf(stdout, "%s : %s : %s\n",hostname, b->name, network_speed(bps)); - } + const char *hostname) +{ + float ping_rtt = 0.0; + float ssh_rtt = 0.0; + float bps = 0.0; + int i; + int err; + struct benchmark *b = NULL; + + 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; i < BENCHMARK_NUMBER; ++i){ + b = &benchmarks[i]; + if (b->enabled) { + 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; +char *buffer = NULL; + +int main(int argc, char **argv) +{ + struct argument_s arguments; + ssh_session session = NULL; + int i, r; + + 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; } - 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<arguments.nhosts;++i){ - fprintf(stdout,"\"%s\" ", arguments.hosts[i]); + + if (arguments.ntests == 0) { + for (i=0; i < BENCHMARK_NUMBER; ++i) { + benchmarks[i].enabled = 1; + } + arguments.ntests = BENCHMARK_NUMBER; } - fprintf(stdout,"with benchmarks "); - for(i=0;i<BENCHMARK_NUMBER;++i){ - if(benchmarks[i].enabled) - fprintf(stdout,"\"%s\" ",benchmarks[i].name); + + buffer = malloc(arguments.chunksize > 1024 ? arguments.chunksize : 1024); + if (buffer == NULL) { + fprintf(stderr, "Allocation of chunk buffer failed\n"); + return EXIT_FAILURE; } - fprintf(stdout,"\n"); - } - - for(i=0; i<arguments.nhosts;++i){ - if(arguments.verbose > 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; + + if (arguments.verbose > 0) { + fprintf(stdout, "Will try hosts "); + for (i=0; i < arguments.nhosts; ++i) { + fprintf(stdout, "\"%s\" ", arguments.hosts[i]); + } + + fprintf(stdout, "with benchmarks "); + for (i = 0; i < BENCHMARK_NUMBER; ++i) { + if (benchmarks[i].enabled) + fprintf(stdout, "\"%s\" ", benchmarks[i].name); + } + + fprintf(stdout,"\n"); } - do_benchmarks(session, &arguments, arguments.hosts[i]); - ssh_disconnect(session); - ssh_free(session); - } - return EXIT_SUCCESS; + + r = ssh_init(); + if (r == SSH_ERROR) { + fprintf(stderr, "Failed to initialize libssh\n"); + return EXIT_FAILURE; + } + + for (i = 0; i < arguments.nhosts; ++i) { + if (arguments.verbose > 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); + } + + r = ssh_finalize(); + if (r == SSH_ERROR) { + fprintf(stderr, "Failed to finalize libssh\n"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/tests/benchmarks/benchmarks.h b/tests/benchmarks/benchmarks.h index 26da09bb..5f54a950 100644 --- a/tests/benchmarks/benchmarks.h +++ b/tests/benchmarks/benchmarks.h @@ -37,6 +37,8 @@ enum libssh_benchmarks { BENCHMARK_SYNC_SFTP_UPLOAD, BENCHMARK_SYNC_SFTP_DOWNLOAD, BENCHMARK_ASYNC_SFTP_DOWNLOAD, + BENCHMARK_ASYNC_SFTP_AIO_DOWNLOAD, + BENCHMARK_ASYNC_SFTP_AIO_UPLOAD, BENCHMARK_NUMBER }; @@ -96,4 +98,8 @@ 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); +int benchmarks_async_sftp_aio_down(ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_async_sftp_aio_up(ssh_session session, struct argument_s *args, + float *bps); #endif /* BENCHMARKS_H_ */ diff --git a/tests/chown_wrapper.c b/tests/chown_wrapper.c new file mode 100644 index 00000000..ee6910ed --- /dev/null +++ b/tests/chown_wrapper.c @@ -0,0 +1,21 @@ +#define _GNU_SOURCE +#include <unistd.h> +#include <string.h> +#include <dlfcn.h> + +typedef int (*__libc_chown)(const char *pathname, uid_t owner, gid_t group); + +/* silent gcc */ +int chown(const char *pathname, uid_t owner, gid_t group); + +int chown(const char *pathname, uid_t owner, gid_t group) +{ + __libc_chown original_chown; + if (strlen(pathname) > 7 && strncmp(pathname, "/dev/pt", 7) == 0) { + /* fake it! */ + return 0; + } + + original_chown = (__libc_chown)dlsym(RTLD_NEXT, "chown"); + return (*original_chown)(pathname, owner, group); +} diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt index 71e5182e..c99c94ef 100644..100755 --- a/tests/client/CMakeLists.txt +++ b/tests/client/CMakeLists.txt @@ -4,10 +4,12 @@ find_package(socket_wrapper) set(LIBSSH_CLIENT_TESTS torture_algorithms + torture_client_callbacks torture_client_config torture_connect torture_hostkey torture_auth + torture_auth_cert torture_rekey torture_forward torture_knownhosts @@ -15,6 +17,7 @@ set(LIBSSH_CLIENT_TESTS torture_proxycommand torture_session torture_request_env + torture_request_pty_modes torture_client_global_requests) find_program(SCP_EXECUTABLE NAMES scp) @@ -49,14 +52,22 @@ if (WITH_SFTP) torture_sftp_dir torture_sftp_read torture_sftp_fsync + torture_sftp_hardlink + torture_sftp_limits + torture_sftp_rename + torture_sftp_expand_path + torture_sftp_aio ${SFTP_BENCHMARK_TESTS}) endif (WITH_SFTP) +set(TORTURE_CLIENT_ENVIRONMENT ${TORTURE_ENVIRONMENT}) +list(APPEND TORTURE_CLIENT_ENVIRONMENT NSS_WRAPPER_HOSTS=${CMAKE_BINARY_DIR}/tests/etc/hosts) + 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} + LINK_LIBRARIES ${TORTURE_LIBRARY} util ) if (OSX) @@ -70,6 +81,6 @@ foreach(_CLI_TEST ${LIBSSH_CLIENT_TESTS}) TEST ${_CLI_TEST} PROPERTY - ENVIRONMENT ${TORTURE_ENVIRONMENT}) + ENVIRONMENT ${TORTURE_CLIENT_ENVIRONMENT}) endif() endforeach() diff --git a/tests/client/torture_algorithms.c b/tests/client/torture_algorithms.c index ea3b647b..60354f9b 100644 --- a/tests/client/torture_algorithms.c +++ b/tests/client/torture_algorithms.c @@ -594,24 +594,34 @@ static void torture_algorithms_zlib(void **state) { rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_C_S, "zlib"); #ifdef WITH_ZLIB - assert_int_equal(rc, SSH_OK); + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + } else { + 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); + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + } else { + 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; + if (!ssh_fips_mode()) { + if (ssh_get_openssh_version(session)) { + assert_false(rc == SSH_OK); + ssh_disconnect(session); + return; + } } #endif assert_int_equal(rc, SSH_OK); diff --git a/tests/client/torture_auth.c b/tests/client/torture_auth.c index 29f6f5a5..77b7bd1f 100644 --- a/tests/client/torture_auth.c +++ b/tests/client/torture_auth.c @@ -32,8 +32,7 @@ #include <sys/types.h> #include <pwd.h> -/* agent_is_running */ -#include "agent.c" +#include "torture_auth_common.c" static int sshd_setup(void **state) { @@ -112,7 +111,7 @@ static int agent_setup(void **state) char ssh_agent_cmd[4096]; char ssh_agent_sock[1024]; char ssh_agent_pidfile[1024]; - char bob_ssh_key[1024]; + char ssh_key_add[1024]; struct passwd *pwd; int rc; @@ -149,38 +148,12 @@ static int agent_setup(void **state) 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), + snprintf(ssh_key_add, + sizeof(ssh_key_add), "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); + rc = system(ssh_key_add); assert_return_code(rc, errno); return 0; @@ -257,6 +230,143 @@ static void torture_auth_none_nonblocking(void **state) { } +/* Setting MaxAuthTries 0 makes libssh hang. The option is not practical, + * but simulates setting low value and requiring multiple authentication + * methods to succeed (T233) + */ +static void torture_auth_none_max_tries(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + const char *sshd_config = "MaxAuthTries 0"; + + torture_update_sshd_config(state, sshd_config); + + 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); + } + + /* Reset config back to defaults */ + torture_update_sshd_config(state, ""); +} + + +static void torture_auth_pubkey(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 = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + /* 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_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + /* negative tests */ + rc = ssh_userauth_try_publickey(NULL, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_ERROR); + rc = ssh_userauth_try_publickey(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_ERROR); + + rc = ssh_userauth_try_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* negative tests */ + rc = ssh_userauth_publickey(NULL, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_ERROR); + rc = ssh_userauth_publickey(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_ERROR); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +static void torture_auth_pubkey_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 = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + /* 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); + + 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); + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + do { + rc = ssh_userauth_try_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + static void torture_auth_autopubkey(void **state) { struct torture_state *s = *state; ssh_session session = s->ssh.session; @@ -401,7 +511,11 @@ static void torture_auth_autopubkey_nonblocking(void **state) { assert_int_equal(rc, SSH_AUTH_SUCCESS); } -static void torture_auth_kbdint(void **state) { +static void +torture_auth_kbdint(void **state, + const char *password, + enum ssh_auth_e res) +{ struct torture_state *s = *state; ssh_session session = s->ssh.session; int rc; @@ -424,19 +538,35 @@ static void torture_auth_kbdint(void **state) { 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); + rc = ssh_userauth_kbdint_setanswer(session, 0, 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) { + 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); + assert_int_equal(rc, res); +} + +static void +torture_auth_kbdint_good(void **state) +{ + torture_auth_kbdint(state, TORTURE_SSH_USER_BOB_PASSWORD, SSH_AUTH_SUCCESS); +} + +static void +torture_auth_kbdint_bad(void **state) +{ + torture_auth_kbdint(state, "bad password stample", SSH_AUTH_DENIED); } -static void torture_auth_kbdint_nonblocking(void **state) { +static void +torture_auth_kbdint_nonblocking(void **state, + const char *password, + enum ssh_auth_e res) +{ struct torture_state *s = *state; ssh_session session = s->ssh.session; int rc; @@ -447,9 +577,9 @@ static void torture_auth_kbdint_nonblocking(void **state) { rc = ssh_connect(session); assert_int_equal(rc, SSH_OK); - ssh_set_blocking(session,0); + ssh_set_blocking(session, 0); do { - rc = ssh_userauth_none(session, NULL); + rc = ssh_userauth_none(session, NULL); } while (rc == SSH_AUTH_AGAIN); /* This request should return a SSH_REQUEST_DENIED error */ @@ -464,23 +594,41 @@ static void torture_auth_kbdint_nonblocking(void **state) { } 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); + rc = ssh_userauth_kbdint_setanswer(session, 0, 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) { + 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); + assert_int_equal(rc, res); +} + +static void +torture_auth_kbdint_nonblocking_good(void **state) +{ + torture_auth_kbdint_nonblocking(state, + TORTURE_SSH_USER_BOB_PASSWORD, + SSH_AUTH_SUCCESS); +} + +static void +torture_auth_kbdint_nonblocking_bad(void **state) +{ + torture_auth_kbdint_nonblocking(state, + "bad password stample", + SSH_AUTH_DENIED); } -static void torture_auth_password(void **state) { +static void +torture_auth_password(void **state, const char *password, enum ssh_auth_e res) +{ struct torture_state *s = *state; ssh_session session = s->ssh.session; int rc; @@ -499,11 +647,29 @@ static void torture_auth_password(void **state) { 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); + rc = ssh_userauth_password(session, NULL, password); + assert_int_equal(rc, res); +} + +static void +torture_auth_password_good(void **state) +{ + torture_auth_password(state, + TORTURE_SSH_USER_BOB_PASSWORD, + SSH_AUTH_SUCCESS); } -static void torture_auth_password_nonblocking(void **state) { +static void +torture_auth_password_bad(void **state) +{ + torture_auth_password(state, "bad password stample", SSH_AUTH_DENIED); +} + +static void +torture_auth_password_nonblocking(void **state, + const char *password, + enum ssh_auth_e res) +{ struct torture_state *s = *state; ssh_session session = s->ssh.session; int rc; @@ -516,7 +682,7 @@ static void torture_auth_password_nonblocking(void **state) { ssh_set_blocking(session,0); do { - rc = ssh_userauth_none(session, NULL); + rc = ssh_userauth_none(session, NULL); } while (rc == SSH_AUTH_AGAIN); /* This request should return a SSH_REQUEST_DENIED error */ @@ -528,16 +694,51 @@ static void torture_auth_password_nonblocking(void **state) { assert_true(rc & SSH_AUTH_METHOD_PASSWORD); do { - rc = ssh_userauth_password(session, NULL, TORTURE_SSH_USER_BOB_PASSWORD); - } while(rc==SSH_AUTH_AGAIN); + rc = ssh_userauth_password(session, NULL, password); + } while (rc == SSH_AUTH_AGAIN); - assert_int_equal(rc, SSH_AUTH_SUCCESS); + assert_int_equal(rc, res); +} + +static void +torture_auth_password_nonblocking_good(void **state) +{ + torture_auth_password_nonblocking(state, + TORTURE_SSH_USER_BOB_PASSWORD, + SSH_AUTH_SUCCESS); } -static void torture_auth_agent(void **state) { +static void +torture_auth_password_nonblocking_bad(void **state) +{ + torture_auth_password_nonblocking(state, + "bad password stample", + SSH_AUTH_DENIED); +} + +/* TODO cover the case: + * * when there is accompanying certificate (identities only + agent) + * * export private key to public key during _auto() authentication. + * this needs to be a encrypted private key in PEM format without + * accompanying public key. + */ +static void torture_auth_agent_identities_only(void **state) +{ struct torture_state *s = *state; ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + struct passwd *pwd = NULL; int rc; + int identities_only = 1; + char *id = NULL; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); if (!ssh_agent_is_running(session)){ print_message("*** Agent not running. Test ignored\n"); @@ -546,37 +747,18 @@ static void torture_auth_agent(void **state) { rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); assert_int_equal(rc, SSH_OK); - rc = ssh_connect(session); + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); 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; + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); } - 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); + 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); @@ -584,111 +766,74 @@ static void torture_auth_agent_nonblocking(void **state) { rc = ssh_userauth_list(session, NULL); assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); - ssh_set_blocking(session,0); + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); - do { - rc = ssh_userauth_agent(session, NULL); - } while (rc == SSH_AUTH_AGAIN); - assert_int_equal(rc, SSH_AUTH_SUCCESS); + /* Re-add a key */ + rc = ssh_list_append(session->opts.identity, strdup(bob_ssh_key)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); } -static void torture_auth_cert(void **state) { +static void torture_auth_agent_identities_only_protected(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; + int identities_only = 1; + char *id = NULL; pwd = getpwnam("bob"); assert_non_null(pwd); snprintf(bob_ssh_key, sizeof(bob_ssh_key), - "%s/.ssh_cert/id_rsa", + "%s/.ssh/id_rsa_protected", 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 */ + 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_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); assert_int_equal(rc, SSH_OK); - rc = ssh_pki_import_cert_file(bob_ssh_cert, &cert); - assert_int_equal(rc, SSH_OK); + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); + } - rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + rc = ssh_connect(session); 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); + 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); - /* 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; + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); - /* 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); - } + /* Re-add a key */ + rc = ssh_list_append(session->opts.identity, strdup(bob_ssh_key)); + assert_int_equal(rc, SSH_OK); - torture_auth_agent_nonblocking(state); + /* Should succeed as key now in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); } static void torture_auth_pubkey_types(void **state) @@ -748,7 +893,7 @@ static void torture_auth_pubkey_types_ecdsa(void **state) rc = ssh_userauth_list(session, NULL); assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); - /* We have only the 256b key -- whitelisting only larger should fail */ + /* We have only the 256b key -- allowlisting only larger should fail */ rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, "ecdsa-sha2-nistp384"); assert_ssh_return_code(session, rc); @@ -885,7 +1030,7 @@ static void torture_auth_pubkey_types_ecdsa_nonblocking(void **state) ssh_set_blocking(session, 0); do { - rc = ssh_userauth_none(session, NULL); + rc = ssh_userauth_none(session, NULL); } while (rc == SSH_AUTH_AGAIN); /* This request should return a SSH_REQUEST_DENIED error */ @@ -896,7 +1041,7 @@ static void torture_auth_pubkey_types_ecdsa_nonblocking(void **state) rc = ssh_userauth_list(session, NULL); assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); - /* We have only the 256b key -- whitelisting only larger should fail */ + /* We have only the 256b key -- allowlisting only larger should fail */ rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, "ecdsa-sha2-nistp384"); assert_ssh_return_code(session, rc); @@ -982,6 +1127,124 @@ static void torture_auth_pubkey_types_ed25519_nonblocking(void **state) } while (rc == SSH_AUTH_AGAIN); assert_int_equal(rc, SSH_AUTH_SUCCESS); + SSH_KEY_FREE(privkey); +} + +static void torture_auth_pubkey_rsa_key_size(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; + unsigned int limit = 4096; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + 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); + + /* set unreasonable large minimum key size to trigger the condition */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); /* larger than the test key */ + assert_ssh_return_code(session, rc); + + /* Import the RSA private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* revert to default values which should work also in FIPS mode */ + limit = 0; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); + 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_rsa_key_size_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; + unsigned int limit = 4096; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + 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); + + /* set unreasonable large minimum key size to trigger the condition */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); /* larger than the test key */ + assert_ssh_return_code(session, rc); + + /* Import the RSA private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* revert to default values which should work also in FIPS mode */ + limit = 0; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); + 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); + + SSH_KEY_FREE(privkey); } int torture_run_tests(void) { @@ -993,18 +1256,39 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_auth_none_nonblocking, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_auth_password, + cmocka_unit_test_setup_teardown(torture_auth_none_max_tries, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_password_good, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_password_nonblocking_good, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_password_bad, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_password_nonblocking_bad, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_auth_password_nonblocking, + cmocka_unit_test_setup_teardown(torture_auth_kbdint_good, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_auth_kbdint, + cmocka_unit_test_setup_teardown(torture_auth_kbdint_nonblocking_good, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_auth_kbdint_nonblocking, + cmocka_unit_test_setup_teardown(torture_auth_kbdint_bad, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_kbdint_nonblocking_bad, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_nonblocking, + pubkey_setup, + session_teardown), cmocka_unit_test_setup_teardown(torture_auth_autopubkey, pubkey_setup, session_teardown), @@ -1020,14 +1304,11 @@ int torture_run_tests(void) { 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, + cmocka_unit_test_setup_teardown(torture_auth_agent_identities_only, + agent_setup, agent_teardown), - cmocka_unit_test_setup_teardown(torture_auth_agent_cert_nonblocking, - agent_cert_setup, + cmocka_unit_test_setup_teardown(torture_auth_agent_identities_only_protected, + agent_setup, agent_teardown), cmocka_unit_test_setup_teardown(torture_auth_pubkey_types, pubkey_setup, @@ -1047,6 +1328,12 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ed25519_nonblocking, pubkey_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_rsa_key_size, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_rsa_key_size_nonblocking, + pubkey_setup, + session_teardown), }; ssh_init(); diff --git a/tests/client/torture_auth_cert.c b/tests/client/torture_auth_cert.c new file mode 100644 index 00000000..18766b93 --- /dev/null +++ b/tests/client/torture_auth_cert.c @@ -0,0 +1,1072 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * Copyright (c) 2023 by 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/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> +#include <pwd.h> + +#include "torture_auth_common.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(); + const char *all_keytypes = NULL; + struct passwd *pwd = NULL; + bool b = false; + int rc; + + pwd = getpwnam("doe"); + 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); + + /* Enable all hostkeys */ + all_keytypes = ssh_kex_get_supported_method(SSH_HOSTKEYS); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, all_keytypes); + assert_ssh_return_code(s->ssh.session, rc); + + /* certs have been signed for login as alice */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + /* Make sure we do not interfere with another ssh-agent */ + unsetenv("SSH_AUTH_SOCK"); + unsetenv("SSH_AGENT_PID"); + + return 0; +} + +/* This sets up the ssh session in the directory without the default + * certificates that are used for authentication, requiring them to be provided + * as configuration options or from agent explicitly. */ +static int session_setup_ssh_dir(void **state) +{ + struct torture_state *s = *state; + const char *no_home = "~/.no_ssh"; + int rc; + + session_setup(state); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_SSH_DIR, &no_home); + 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 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 ssh_key_add[1024]; + struct passwd *pwd; + int rc; + + rc = session_setup(state); + if (rc != 0) { + return rc; + } + + pwd = getpwnam("doe"); + 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(ssh_key_add, + sizeof(ssh_key_add), + "ssh-add %s/.ssh/id_rsa", + pwd->pw_dir); + + rc = system(ssh_key_add); + assert_return_code(rc, errno); + + return 0; +} + +static int agent_cert_setup(void **state) +{ + char doe_alt_ssh_key[1024]; + struct passwd *pwd; + int rc; + + rc = agent_setup(state); + if (rc != 0) { + return rc; + } + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + /* remove all keys, load alternative key + cert */ + snprintf(doe_alt_ssh_key, + sizeof(doe_alt_ssh_key), + "ssh-add -D && ssh-add %s/.ssh/id_rsa", + pwd->pw_dir); + + rc = system(doe_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 */ + rc = torture_terminate_process(ssh_agent_pidfile); + assert_return_code(rc, errno); + + unlink(ssh_agent_pidfile); + + unsetenv("TORTURE_SSH_AGENT_PIDFILE"); + unsetenv("SSH_AUTH_SOCK"); + + return 0; +} + +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 doe_ssh_key[1024]; + char doe_ssh_cert[2048]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s-cert.pub", + doe_ssh_key); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_import_privkey_file(doe_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_import_cert_file(doe_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_ssh_return_code(session, rc); + + 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_cert_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_key privkey = NULL; + ssh_key cert = NULL; + char doe_ssh_key[1024]; + char doe_ssh_cert[2048]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s-cert.pub", + doe_ssh_key); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + rc = ssh_pki_import_privkey_file(doe_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_import_cert_file(doe_ssh_cert, &cert); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_OK); + + do { + rc = ssh_userauth_try_publickey(session, NULL, cert); + } while (rc == SSH_AUTH_AGAIN); + 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); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(cert); +} + +/* Same as torture_auth_cert, but without explicitly loading certificate to the + * private key file, keeping libssh to use default cert path when done with + * _auto(). */ +static void torture_auth_cert_default_non_explicit(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + /* the cert is in the default location (~/.ssh/id_rsa-cert.pub) */ + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Same as torture_auth_cert_nonblocking, but without explicitly loading + * certificate to the private key file, keeping libssh to use default cert path + * when done with _auto(). + * Non-blocking version */ +static void torture_auth_cert_default_non_explicit_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + /* the cert is in the default location (~/.ssh/id_rsa-cert.pub) */ + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Sanity test that there are no default identities available and the automatic + * pubkey authentication fails without any explicit identities */ +static void torture_auth_auto_fail(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); +} + +/* Sanity test that there are no default identities available and the automatic + * pubkey authentication fails without any explicit identities + * Non-blocking version */ +static void torture_auth_auto_fail_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); +} + +/* Same as torture_auth_cert, but the home SSH dir does not have any default + * identities and they are loaded through the options, only through the private + * key path. */ +static void torture_auth_cert_options_private(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + /* the cert has default naming relative to the private key (*-cert.pub) */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, doe_ssh_key); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Same as torture_auth_cert, but the home SSH dir does not have any default + * identities and they are loaded through the options, only through the private + * key path. + * Non-blocking version */ +static void torture_auth_cert_options_private_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + /* the cert has default naming relative to the private key (*-cert.pub) */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, doe_ssh_key); + 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_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Same as torture_auth_cert, but the home SSH dir does not have any default + * identities and they are loaded through the options, also the certificate file + */ +static void torture_auth_cert_options_cert(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + char doe_ssh_cert[2048]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s-cert.pub", + doe_ssh_key); + + /* Explicit private key and cert */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, doe_ssh_key); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, doe_ssh_cert); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Same as torture_auth_cert, but the home SSH dir does not have any default + * identities and they are loaded through the options, only through the private + * key path. + * Non-blocking version */ +static void torture_auth_cert_options_cert_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + char doe_ssh_cert[2048]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s-cert.pub", + doe_ssh_key); + + /* Explicit private key and cert */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, doe_ssh_key); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, doe_ssh_cert); + 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_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void workaround_old_openssh_bug(void **state) +{ +#if OPENSSH_VERSION_MAJOR < 8 || (OPENSSH_VERSION_MAJOR == 8 && OPENSSH_VERSION_MINOR == 0) + 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); + } +#else + (void)state; +#endif /* OPENSSH_VERSION_MAJOR < 8.1 */ +} + +static void torture_auth_agent_cert(void **state) +{ + workaround_old_openssh_bug(state); + + /* Setup loads a different key, tests are exactly the same. */ + torture_auth_agent(state); +} + +static void torture_auth_agent_cert_nonblocking(void **state) +{ + workaround_old_openssh_bug(state); + + torture_auth_agent_nonblocking(state); +} + +static void +torture_auth_agent_cert_identities_only(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + struct passwd *pwd = NULL; + int identities_only = 1; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + return; + } + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); + } + + 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); + + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key -- the cert in default location should be loaded + * automatically */ + rc = ssh_list_append(session->opts.identity, strdup(doe_ssh_key)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +static void +torture_auth_agent_cert_identities_only_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + struct passwd *pwd = NULL; + int identities_only = 1; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + return; + } + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); + } + + 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); + + /* Should fail as key is not in config */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key -- the cert in default location should be loaded + * automatically */ + rc = ssh_list_append(session->opts.identity, strdup(doe_ssh_key)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); +} + +static int agent_cert_setup_explicit(void **state) +{ + char orig_doe_ssh_key[1024]; + char doe_ssh_key[1024]; + char keydata[2048]; + struct passwd *pwd = NULL; + int fd ; + int rc; + + agent_cert_setup(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(orig_doe_ssh_key, + sizeof(orig_doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/my_rsa", + pwd->pw_dir); + + /* move the private key away from the default location the certificate can + * not be loaded automatically */ + fd = open(orig_doe_ssh_key, O_RDONLY); + assert_true(fd > 0); + rc = read(fd, keydata, sizeof(keydata)); + assert_true(rc > 0); + keydata[rc] = '\0'; + close(fd); + torture_write_file(doe_ssh_key, keydata); + + return 0; +} + +static void +torture_auth_agent_cert_identities_only_explicit(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + char doe_ssh_cert[1024]; + struct passwd *pwd = NULL; + int identities_only = 1; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/my_rsa", + pwd->pw_dir); + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s/.ssh/id_rsa-cert.pub", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + skip(); + } + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); + } + + 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); + + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key and cert */ + rc = ssh_list_append(session->opts.identity, strdup(doe_ssh_key)); + assert_int_equal(rc, SSH_OK); + rc = ssh_list_append(session->opts.certificate, strdup(doe_ssh_cert)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +static void +torture_auth_agent_cert_identities_only_nonblocking_explicit(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + char doe_ssh_cert[1024]; + struct passwd *pwd = NULL; + int identities_only = 1; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/my_rsa", + pwd->pw_dir); + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s/.ssh/id_rsa-cert.pub", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + skip(); + } + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); + } + + 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); + + /* Should fail as key is not in config */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key and cert */ + rc = ssh_list_append(session->opts.identity, strdup(doe_ssh_key)); + assert_int_equal(rc, SSH_OK); + rc = ssh_list_append(session->opts.certificate, strdup(doe_ssh_cert)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); +} + +static void +torture_auth_agent_cert_only_identities_only(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_cert[1024]; + struct passwd *pwd = NULL; + int identities_only = 1; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s/.ssh/id_rsa-cert.pub", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + skip(); + } + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); + } + + 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); + + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a cert: key is in the agent */ + rc = ssh_list_append(session->opts.certificate, strdup(doe_ssh_cert)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +static void +torture_auth_agent_cert_only_identities_only_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_cert[1024]; + struct passwd *pwd = NULL; + int identities_only = 1; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s/.ssh/id_rsa-cert.pub", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + skip(); + } + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); + } + + 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); + + /* Should fail as key is not in config */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a cert: key is in the agent */ + rc = ssh_list_append(session->opts.certificate, strdup(doe_ssh_cert)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_auth_cert, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_default_non_explicit, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_default_non_explicit_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_auto_fail, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_auto_fail_nonblocking, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_options_private, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_options_private_nonblocking, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_options_cert, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_options_cert_nonblocking, + session_setup_ssh_dir, + 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_agent_cert_identities_only, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert_identities_only_nonblocking, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert_identities_only_explicit, + agent_cert_setup_explicit, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert_identities_only_nonblocking_explicit, + agent_cert_setup_explicit, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert_only_identities_only, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert_only_identities_only_nonblocking, + agent_cert_setup, + agent_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_auth_common.c b/tests/client/torture_auth_common.c new file mode 100644 index 00000000..8a4f2854 --- /dev/null +++ b/tests/client/torture_auth_common.c @@ -0,0 +1,94 @@ +/* + * 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 "torture.h" +#include "libssh/libssh.h" + +/* agent_is_running */ +#include "agent.c" + +void torture_auth_agent(void **state); +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); + + /* negative test case */ + rc = ssh_userauth_agent(NULL, NULL); + assert_int_equal(rc, SSH_AUTH_ERROR); + + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +void torture_auth_agent_nonblocking(void **state); +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_ssh_return_code(session, rc); +} diff --git a/tests/client/torture_auth_pkcs11.c b/tests/client/torture_auth_pkcs11.c index ee97bff4..2537d2d8 100644 --- a/tests/client/torture_auth_pkcs11.c +++ b/tests/client/torture_auth_pkcs11.c @@ -39,9 +39,8 @@ #define LIBSSH_ECDSA_256_TESTKEY "id_pkcs11_ecdsa_256" #define LIBSSH_ECDSA_384_TESTKEY "id_pkcs11_ecdsa_384" #define LIBSSH_ECDSA_521_TESTKEY "id_pkcs11_ecdsa_521" -#define SOFTHSM_CONF "softhsm.conf" -const char template[] = "temp_dir_XXXXXX"; +const char template[] = "/tmp/temp_dir_XXXXXX"; struct pki_st { char *temp_dir; @@ -109,7 +108,6 @@ static int setup_session(void **state) struct torture_state *s = *state; struct pki_st *test_state = NULL; int rc; - char conf_path[1024] = {0}; char keys_dir[1024] = {0}; char *temp_dir; @@ -134,9 +132,6 @@ static int setup_session(void **state) test_state->keys_dir = strdup(keys_dir); - snprintf(conf_path, sizeof(conf_path), "%s/softhsm.conf", test_state->temp_dir); - setenv("SOFTHSM2_CONF", conf_path, 1); - setup_tokens(state, LIBSSH_RSA_TESTKEY, "rsa"); setup_tokens(state, LIBSSH_ECDSA_256_TESTKEY, "ecdsa256"); setup_tokens(state, LIBSSH_ECDSA_384_TESTKEY, "ecdsa384"); @@ -160,7 +155,7 @@ static int sshd_teardown(void **state) { struct pki_st *test_state = s->private_data; int rc; - unsetenv("SOFTHSM2_CONF"); + torture_cleanup_tokens(test_state->temp_dir); rc = torture_change_dir(test_state->orig_dir); assert_int_equal(rc, 0); @@ -196,7 +191,7 @@ static void torture_auth_autopubkey(void **state, const char *obj_name, const ch rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, priv_uri); assert_int_equal(rc, SSH_OK); - assert_string_equal(session->opts.identity->root->data, priv_uri); + assert_string_equal(session->opts.identity_non_exp->root->data, priv_uri); rc = ssh_connect(session); assert_int_equal(rc, SSH_OK); diff --git a/tests/client/torture_client_callbacks.c b/tests/client/torture_client_callbacks.c new file mode 100644 index 00000000..498783e3 --- /dev/null +++ b/tests/client/torture_client_callbacks.c @@ -0,0 +1,261 @@ +/* + * 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 "libssh/callbacks.h" + +#include <sys/types.h> +#include <pwd.h> +#include <errno.h> + +#define STATE_SUCCESS (1) +#define STATE_FAILURE (2) + +struct callback_state +{ + int open_response; + int request_response; + ssh_session expected_session; + ssh_channel expected_channel; + struct ssh_channel_callbacks_struct *callback; +}; + +static void on_open_response(ssh_session session, ssh_channel channel, bool is_success, void *userdata) +{ + struct callback_state *state = (struct callback_state*)userdata; + assert_ptr_equal(state->expected_session, session); + assert_ptr_equal(state->expected_channel, channel); + state->open_response = is_success ? STATE_SUCCESS : STATE_FAILURE; +} + +static void on_request_response(ssh_session session, ssh_channel channel, void *userdata) +{ + struct callback_state *state = (struct callback_state*)userdata; + assert_ptr_equal(state->expected_session, session); + assert_ptr_equal(state->expected_channel, channel); + state->request_response = STATE_SUCCESS; +} + +static struct callback_state *set_callbacks(ssh_session session, ssh_channel channel) +{ + int rc; + struct ssh_channel_callbacks_struct *cb; + struct callback_state *cb_state = NULL; + + cb_state = (struct callback_state *)calloc(1, + sizeof(struct callback_state)); + assert_non_null(cb_state); + cb_state->expected_session = session; + cb_state->expected_channel = channel; + + cb = (struct ssh_channel_callbacks_struct *)calloc(1, + sizeof(struct ssh_channel_callbacks_struct)); + assert_non_null(cb); + ssh_callbacks_init(cb); + cb->userdata = cb_state; + cb->channel_open_response_function = on_open_response; + cb->channel_request_response_function = on_request_response; + rc = ssh_set_channel_callbacks(channel, cb); + assert_ssh_return_code(session, rc); + + /* Keep the reference so it can be cleaned up later */ + cb_state->callback = cb; + return cb_state; +} + +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_open_success(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + struct callback_state *cb_state = NULL; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + cb_state = set_callbacks(session, channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + assert_int_equal(STATE_SUCCESS, cb_state->open_response); + + ssh_channel_free(channel); + free(cb_state->callback); + free(cb_state); +} + +static void torture_open_failure(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + struct callback_state *cb_state = NULL; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + cb_state = set_callbacks(session, channel); + + rc = ssh_channel_open_forward(channel, "0.0.0.0", 0, "0.0.0.0", 0); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + assert_int_equal(STATE_FAILURE, cb_state->open_response); + + ssh_channel_free(channel); + free(cb_state->callback); + free(cb_state); +} + +static void torture_request_success(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + struct callback_state *cb_state = NULL; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + cb_state = set_callbacks(session, channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(channel, "echo -n ABCD"); + assert_ssh_return_code(session, rc); + + assert_int_equal(STATE_SUCCESS, cb_state->request_response); + + ssh_channel_free(channel); + free(cb_state->callback); + free(cb_state); +} + +static void torture_request_failure(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + struct callback_state *cb_state = NULL; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + cb_state = set_callbacks(session, channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_env(channel, "NOT_ACCEPTED", "VALUE"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + assert_int_equal(STATE_SUCCESS, cb_state->request_response); + + ssh_channel_free(channel); + free(cb_state->callback); + free(cb_state); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_open_success, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_open_failure, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_request_success, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_request_failure, + 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_client_config.c b/tests/client/torture_client_config.c index c413619a..5ac22a4c 100644 --- a/tests/client/torture_client_config.c +++ b/tests/client/torture_client_config.c @@ -130,7 +130,7 @@ static void torture_client_config_system(void **state) * 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 + * system-wide configuration file so we try to emulate the behavior by parsing * the files separately in the same order. */ static void torture_client_config_emulate(void **state) diff --git a/tests/client/torture_connect.c b/tests/client/torture_connect.c index f31f521f..f086488d 100644 --- a/tests/client/torture_connect.c +++ b/tests/client/torture_connect.c @@ -83,6 +83,26 @@ static int session_teardown(void **state) return 0; } +static void torture_connect_peer_discon_msg(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); + + rc = ssh_service_request(session, "wrong-service"); + assert_int_not_equal(rc, SSH_OK); + + ssh_disconnect(session); + assert_non_null(session->peer_discon_msg); + assert_non_null(ssh_get_disconnect_message(session)); +} + static void torture_connect_nonblocking(void **state) { struct torture_state *s = *state; ssh_session session = s->ssh.session; @@ -100,6 +120,29 @@ static void torture_connect_nonblocking(void **state) { assert_ssh_return_code(session, rc); } +static void torture_connect_ipv6(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "testing"); + assert_ssh_return_code(session, rc); + /* set non-blocking mode */ + ssh_set_blocking(session, 0); + + do { + rc = ssh_connect(session); + } while (rc == SSH_AGAIN); + + assert_ssh_return_code(session, rc); + + /* should work for blocking mode too */ + ssh_disconnect(session); + ssh_set_blocking(session, 1); + rc = ssh_connect(session); + 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; @@ -189,7 +232,7 @@ static void torture_connect_uninitialized(UNUSED_PARAM(void **state)) ssh_session session; struct passwd *pwd; - /* Make sure the library is unitialized */ + /* Make sure the library is uninitialized */ while (is_ssh_initialized()) { rc = ssh_finalize(); assert_return_code(rc, errno); @@ -218,7 +261,9 @@ static void torture_connect_uninitialized(UNUSED_PARAM(void **state)) int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_connect_peer_discon_msg, session_setup, session_teardown), cmocka_unit_test_setup_teardown(torture_connect_nonblocking, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_ipv6, 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 diff --git a/tests/client/torture_forward.c b/tests/client/torture_forward.c index dcbdcbdb..18b8c85f 100644 --- a/tests/client/torture_forward.c +++ b/tests/client/torture_forward.c @@ -82,6 +82,8 @@ static void torture_ssh_forward(void **state) ssh_channel c; int dport; int bound_port; + char *originator_host = NULL; + int originator_port; int rc; int verbosity = SSH_LOG_TRACE; @@ -90,7 +92,7 @@ static void torture_ssh_forward(void **state) 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); + c = ssh_channel_open_forward_port(session, 10, &dport, &originator_host, &originator_port); /* We do not get a listener and run into the timeout here */ assert_null(c); diff --git a/tests/client/torture_hostkey.c b/tests/client/torture_hostkey.c index 574a8400..88b657b6 100644 --- a/tests/client/torture_hostkey.c +++ b/tests/client/torture_hostkey.c @@ -127,30 +127,6 @@ static void torture_hostkey_ed25519(void **state) { 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; @@ -221,10 +197,6 @@ int torture_run_tests(void) { 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), diff --git a/tests/client/torture_proxycommand.c b/tests/client/torture_proxycommand.c index c04ff2ab..1bad4ccc 100644 --- a/tests/client/torture_proxycommand.c +++ b/tests/client/torture_proxycommand.c @@ -59,7 +59,7 @@ static int session_teardown(void **state) return 0; } -#ifdef NC_EXECUTABLE +#ifdef NCAT_EXECUTABLE static void torture_options_set_proxycommand(void **state) { struct torture_state *s = *state; @@ -71,13 +71,18 @@ static void torture_options_set_proxycommand(void **state) int rc; socket_t fd; - rc = stat(NC_EXECUTABLE, &sb); + rc = stat(NCAT_EXECUTABLE, &sb); if (rc != 0 || (sb.st_mode & S_IXOTH) == 0) { - SSH_LOG(SSH_LOG_WARNING, "Could not find " NC_EXECUTABLE ": Skipping the test"); + SSH_LOG(SSH_LOG_WARNING, + "Could not find " NCAT_EXECUTABLE ": Skipping the test"); skip(); } - rc = snprintf(command, sizeof(command), NC_EXECUTABLE " %s %d", address, port); + rc = snprintf(command, + sizeof(command), + NCAT_EXECUTABLE " %s %d", + address, + port); assert_true((size_t)rc < sizeof(command)); rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, command); @@ -90,7 +95,7 @@ static void torture_options_set_proxycommand(void **state) assert_int_equal(rc & O_RDWR, O_RDWR); } -#else /* NC_EXECUTABLE */ +#else /* NCAT_EXECUTABLE */ static void torture_options_set_proxycommand(void **state) { @@ -98,7 +103,7 @@ static void torture_options_set_proxycommand(void **state) skip(); } -#endif /* NC_EXECUTABLE */ +#endif /* NCAT_EXECUTABLE */ static void torture_options_set_proxycommand_notexist(void **state) { struct torture_state *s = *state; @@ -122,7 +127,7 @@ static void torture_options_set_proxycommand_ssh(void **state) socket_t fd; rc = snprintf(command, sizeof(command), - "ssh -oStrictHostKeyChecking=no -W [%%h]:%%p alice@%s", + "ssh -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -W [%%h]:%%p alice@%s", address); assert_true((size_t)rc < sizeof(command)); @@ -147,7 +152,7 @@ static void torture_options_set_proxycommand_ssh_stderr(void **state) /* 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", + "ssh -vvv -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -W [%%h]:%%p alice@%s", address); assert_true((size_t)rc < sizeof(command)); @@ -161,6 +166,56 @@ static void torture_options_set_proxycommand_ssh_stderr(void **state) assert_int_equal(rc & O_RDWR, O_RDWR); } +static void torture_options_proxycommand_injection(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + const char *malicious_host = "`echo foo > mfile`"; + const char *command = "nc %h %p"; + char *current_dir = NULL; + char *malicious_file_path = NULL; + int mfp_len; + int verbosity = torture_libssh_verbosity(); + struct stat sb; + 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); + // if we would be checking the rc, this should fail + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, malicious_host); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROXYCOMMAND, command); + assert_int_equal(rc, 0); + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code_equal(s->ssh.session, rc, SSH_ERROR); + + current_dir = torture_get_current_working_dir(); + assert_non_null(current_dir); + mfp_len = strlen(current_dir) + 6; + malicious_file_path = malloc(mfp_len); + assert_non_null(malicious_file_path); + rc = snprintf(malicious_file_path, mfp_len, + "%s/mfile", current_dir); + assert_int_equal(rc, mfp_len); + free(current_dir); + rc = stat(malicious_file_path, &sb); + assert_int_not_equal(rc, 0); + + // cleanup + remove(malicious_file_path); + free(malicious_file_path); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -176,6 +231,9 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_options_set_proxycommand_ssh_stderr, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_options_proxycommand_injection, + NULL, + session_teardown), }; diff --git a/tests/client/torture_rekey.c b/tests/client/torture_rekey.c index c599332d..f6a633fc 100644 --- a/tests/client/torture_rekey.c +++ b/tests/client/torture_rekey.c @@ -38,6 +38,10 @@ #include <fcntl.h> #include <pwd.h> +#define KEX_RETRY 32 + +static uint64_t bytes = 2048; /* 2KB (more than the authentication phase) */ + static int sshd_setup(void **state) { torture_setup_sshd_server(state, false); @@ -144,6 +148,29 @@ static void torture_rekey_default(void **state) ssh_disconnect(s->ssh.session); } +static void sanity_check_session(void **state) +{ + struct torture_state *s = *state; + struct ssh_crypto_struct *c = NULL; + + c = s->ssh.session->current_crypto; + assert_non_null(c); + 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); + /* when strict kex is used, the newkeys reset the sequence number */ + if ((s->ssh.session->flags & SSH_SESSION_FLAG_KEX_STRICT) != 0) { + assert_int_equal(c->out_cipher->packets, s->ssh.session->send_seq); + assert_int_equal(c->in_cipher->packets, s->ssh.session->recv_seq); + } else { + /* Otherwise we have less encrypted packets than transferred + * (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); + } +} + /* We lower the rekey limits manually and check that the rekey * really happens when sending data */ @@ -153,7 +180,6 @@ static void torture_rekey_send(void **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; @@ -163,16 +189,10 @@ static void torture_rekey_send(void **state) 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); + sanity_check_session(state); /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + assert_non_null(c); secret_hash = malloc(c->digest_len); assert_non_null(secret_hash); memcpy(secret_hash, c->secret_hash, c->digest_len); @@ -189,10 +209,11 @@ static void torture_rekey_send(void **state) 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 */ + /* send ignore packets of up to 1KB to trigger rekey. Send little bit more + * to make sure it completes with all different ciphers */ memset(data, 0, sizeof(data)); memset(data, 'A', 128); - for (i = 0; i < 16; i++) { + for (i = 0; i < KEX_RETRY; i++) { ssh_send_ignore(s->ssh.session, data); ssh_handle_packets(s->ssh.session, 50); } @@ -234,8 +255,6 @@ static void session_setup_sftp(void **state) assert_non_null(s->ssh.tsftp); } -uint64_t bytes = 2048; /* 2KB */ - static int session_setup_sftp_client(void **state) { struct torture_state *s = *state; @@ -271,14 +290,10 @@ static void torture_rekey_recv(void **state) mode_t mask; int 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); + sanity_check_session(state); /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + assert_non_null(c); secret_hash = malloc(c->digest_len); assert_non_null(secret_hash); memcpy(secret_hash, c->secret_hash, c->digest_len); @@ -442,6 +457,160 @@ static void torture_rekey_server_send(void **state) ssh_disconnect(s->ssh.session); } +static void torture_rekey_different_kex(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; + size_t secret_hash_len = 0; + const char *kex1 = "diffie-hellman-group14-sha256,curve25519-sha256,ecdh-sha2-nistp256"; + const char *kex2 = "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512,ecdh-sha2-nistp521"; + + /* Use short digest for initial key exchange */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_KEY_EXCHANGE, kex1); + assert_ssh_return_code(s->ssh.session, rc); + + 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 */ + sanity_check_session(state); + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + assert_non_null(c); + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + secret_hash_len = c->digest_len; + assert_int_equal(secret_hash_len, 32); /* SHA256 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); + + /* Now try to change preference of key exchange algorithm to something with larger digest */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_KEY_EXCHANGE, kex2); + assert_ssh_return_code(s->ssh.session, rc); + + /* send ignore packets of up to 1KB to trigger rekey. Send little bit more + * to make sure the rekey it completes with all different ciphers (paddings */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 128); + for (i = 0; i < KEX_RETRY; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 1000); + + c = s->ssh.session->current_crypto; + /* SHA256 len */ + if (c->digest_len != 32) { + break; + } + } + + /* 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_int_equal(c->digest_len, 64); /* SHA512 len */ + assert_memory_not_equal(secret_hash, c->secret_hash, secret_hash_len); + /* Session ID stays same after one rekey */ + assert_memory_equal(secret_hash, c->session_id, secret_hash_len); + free(secret_hash); + + assert_int_equal(ssh_is_connected(s->ssh.session), 1); + assert_int_equal(s->ssh.session->session_state, SSH_SESSION_STATE_AUTHENTICATED); + + ssh_disconnect(s->ssh.session); +} + +static void torture_rekey_server_different_kex(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; + size_t secret_hash_len = 0; + const char *sshd_config = "RekeyLimit 2K none"; + const char *kex1 = "diffie-hellman-group14-sha256,curve25519-sha256,ecdh-sha2-nistp256"; + const char *kex2 = "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512"; + + /* Use short digest for initial key exchange */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_KEY_EXCHANGE, kex1); + assert_ssh_return_code(s->ssh.session, rc); + + 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); + secret_hash_len = c->digest_len; + assert_int_equal(secret_hash_len, 32); /* SHA256 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); + + /* Now try to change preference of key exchange algorithm to something with larger digest */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_KEY_EXCHANGE, kex2); + assert_ssh_return_code(s->ssh.session, rc); + + /* send ignore packets of up to 1KB to trigger rekey. Send little bit more + * to make sure the rekey it completes with all different ciphers (paddings */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 128); + for (i = 0; i < KEX_RETRY; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 1000); + + c = s->ssh.session->current_crypto; + /* SHA256 len */ + if (c->digest_len != 32) { + break; + } + } + + /* Check that the secret hash is different than initially */ + c = s->ssh.session->current_crypto; + assert_int_equal(c->digest_len, 64); /* SHA512 len */ + assert_memory_not_equal(secret_hash, c->secret_hash, secret_hash_len); + /* Session ID stays same after one rekey */ + assert_memory_equal(secret_hash, c->session_id, secret_hash_len); + free(secret_hash); + + ssh_disconnect(s->ssh.session); +} + + #ifdef WITH_SFTP static int session_setup_sftp_server(void **state) { @@ -510,6 +679,164 @@ static void torture_rekey_server_recv(void **state) } #endif /* WITH_SFTP */ +#ifdef WITH_ZLIB +/* This is disabled by OpenSSH since OpenSSH 7.4p1 */ +#if (OPENSSH_VERSION_MAJOR == 7 && OPENSSH_VERSION_MINOR < 4) || OPENSSH_VERSION_MAJOR < 7 +/* Compression can be funky to get right after rekey + */ +static void torture_rekey_send_compression(void **state) +{ + struct torture_state *s = *state; + const char *comp = "zlib"; + int rc; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_C_S, comp); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_S_C, comp); + assert_ssh_return_code(s->ssh.session, rc); + + torture_rekey_send(state); +} + +#ifdef WITH_SFTP +static void torture_rekey_recv_compression(void **state) +{ + struct torture_state *s = *state; + const char *comp = "zlib"; + int rc; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_C_S, comp); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_S_C, comp); + assert_ssh_return_code(s->ssh.session, rc); + + torture_rekey_recv(state); +} +#endif /* WITH_SFTP */ +#endif + +/* Especially the delayed compression by openssh. + */ +static void torture_rekey_send_compression_delayed(void **state) +{ + struct torture_state *s = *state; + const char *comp = "zlib@openssh.com"; + int rc; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_C_S, comp); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_S_C, comp); + assert_ssh_return_code(s->ssh.session, rc); + + torture_rekey_send(state); +} + +#ifdef WITH_SFTP +static void torture_rekey_recv_compression_delayed(void **state) +{ + struct torture_state *s = *state; + const char *comp = "zlib@openssh.com"; + int rc; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_C_S, comp); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_S_C, comp); + assert_ssh_return_code(s->ssh.session, rc); + + torture_rekey_recv(state); +} +#endif /* WITH_SFTP */ +#endif /* WITH_ZLIB */ + +static void setup_server_for_good_guess(void *state) +{ + const char *default_sshd_config = "KexAlgorithms curve25519-sha256"; + const char *fips_sshd_config = "KexAlgorithms ecdh-sha2-nistp256"; + const char *sshd_config = default_sshd_config; + + if (ssh_fips_mode()) { + sshd_config = fips_sshd_config; + } + /* This sets an only supported kex algorithm that we do not have as a first + * option */ + torture_update_sshd_config(state, sshd_config); +} + +static void torture_rekey_guess_send(void **state) +{ + struct torture_state *s = *state; + + setup_server_for_good_guess(state); + + /* Make the client send the first_kex_packet_follows flag during key + * exchange as well as during the rekey */ + s->ssh.session->send_first_kex_follows = true; + + torture_rekey_send(state); +} + +static void torture_rekey_guess_wrong_send(void **state) +{ + struct torture_state *s = *state; + const char *sshd_config = "KexAlgorithms diffie-hellman-group14-sha256"; + + /* This sets an only supported kex algorithm that we do not have as a first + * option */ + torture_update_sshd_config(state, sshd_config); + + /* Make the client send the first_kex_packet_follows flag during key + * exchange as well as during the rekey */ + s->ssh.session->send_first_kex_follows = true; + + torture_rekey_send(state); +} + +#ifdef WITH_SFTP +static void torture_rekey_guess_recv(void **state) +{ + struct torture_state *s = *state; + int rc; + + setup_server_for_good_guess(state); + + /* Make the client send the first_kex_packet_follows flag during key + * exchange as well as during the rekey */ + s->ssh.session->send_first_kex_follows = true; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); + assert_ssh_return_code(s->ssh.session, rc); + + session_setup_sftp(state); + + torture_rekey_recv(state); +} + +static void torture_rekey_guess_wrong_recv(void **state) +{ + struct torture_state *s = *state; + const char *sshd_config = "KexAlgorithms diffie-hellman-group14-sha256"; + int rc; + + /* This sets an only supported kex algorithm that we do not have as a first + * option */ + torture_update_sshd_config(state, sshd_config); + + /* Make the client send the first_kex_packet_follows flag during key + * exchange as well as during the rekey */ + s->ssh.session->send_first_kex_follows = true; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); + assert_ssh_return_code(s->ssh.session, rc); + + session_setup_sftp(state); + + torture_rekey_recv(state); +} +#endif /* WITH_SFTP */ int torture_run_tests(void) { int rc; @@ -528,16 +855,57 @@ int torture_run_tests(void) { 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_different_kex, + session_setup, + session_teardown), +#ifdef WITH_ZLIB +#if (OPENSSH_VERSION_MAJOR == 7 && OPENSSH_VERSION_MINOR < 4) || OPENSSH_VERSION_MAJOR < 7 + cmocka_unit_test_setup_teardown(torture_rekey_send_compression, + session_setup, + session_teardown), +#ifdef WITH_SFTP + cmocka_unit_test_setup_teardown(torture_rekey_recv_compression, + session_setup_sftp_client, + session_teardown), +#endif /* WITH_SFTP */ +#endif + cmocka_unit_test_setup_teardown(torture_rekey_send_compression_delayed, + session_setup, + session_teardown), +#ifdef WITH_SFTP + cmocka_unit_test_setup_teardown(torture_rekey_recv_compression_delayed, + session_setup_sftp_client, + session_teardown), +#endif /* WITH_SFTP */ +#endif /* WITH_ZLIB */ + /* TODO verify the two rekey are possible and the states are not broken after rekey */ + + cmocka_unit_test_setup_teardown(torture_rekey_server_different_kex, + session_setup, + session_teardown), + /* Note, that these tests modify the sshd_config so follow-up tests + * might get unexpected behavior if they do not update the server with + * torture_update_sshd_config() too */ cmocka_unit_test_setup_teardown(torture_rekey_server_send, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_rekey_guess_send, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_rekey_guess_wrong_send, + session_setup, + session_teardown), #ifdef WITH_SFTP cmocka_unit_test_setup_teardown(torture_rekey_server_recv, session_setup_sftp_server, session_teardown), + cmocka_unit_test_setup_teardown(torture_rekey_guess_recv, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_rekey_guess_wrong_recv, + session_setup, + session_teardown), #endif /* WITH_SFTP */ - /* TODO verify the two rekey are possible and the states are not broken after rekey */ }; ssh_init(); diff --git a/tests/client/torture_request_pty_modes.c b/tests/client/torture_request_pty_modes.c new file mode 100755 index 00000000..0e8cdc0d --- /dev/null +++ b/tests/client/torture_request_pty_modes.c @@ -0,0 +1,258 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Andreas Schneider <asn@cryptomilk.org> + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include <libssh/libssh.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <pwd.h> +#include <errno.h> +#include <termios.h> +#include <unistd.h> +#include <fcntl.h> +#include <pty.h> + +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; +} + +/* reads from the channel, expecting the given output */ +static int check_channel_output(ssh_channel c, const char *expected) +{ + char buffer[4096] = {0}; + int nbytes; + + nbytes = ssh_channel_read(c, buffer, sizeof(buffer) - 1, 0); + while (nbytes > 0) { + buffer[nbytes]='\0'; + ssh_log_hexdump("Read bytes:", (unsigned char *)buffer, nbytes); + if (strstr(buffer, expected) != NULL) + { + return 1; + } + + nbytes = ssh_channel_read(c, buffer, sizeof(buffer), 0); + } + return 0; +} + +/* set explicit TTY modes and validate that the server uses them */ +static void torture_request_pty_modes_translate_ocrnl(void **state) +{ + const unsigned char modes[] = { + /* enable OCRNL */ + 73, 0, 0, 0, 1, + /* disable all other CR/NL handling */ + 34, 0, 0, 0, 0, + 35, 0, 0, 0, 0, + 36, 0, 0, 0, 0, + 72, 0, 0, 0, 0, + 74, 0, 0, 0, 0, + 75, 0, 0, 0, 0, + 0, /* TTY_OP_END */ + }; + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + int rc; + int string_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_pty_size_modes(c, "xterm", 80, 25, modes, sizeof(modes)); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(c, "/bin/echo -e '>TEST\\r\\n<'"); + assert_ssh_return_code(session, rc); + + /* expect 2 newline characters */ + string_found = check_channel_output(c, ">TEST\n\n<"); + assert_int_equal(string_found, 1); + + ssh_channel_close(c); +} + +/* if stdin is a TTY, its modes are passed to the server */ +static void torture_request_pty_modes_use_stdin_modes(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + int rc; + int string_found = 0; + struct termios modes; + int stdin_backup_fd = -1; + int master_fd, slave_fd; + + c = ssh_channel_new(session); + assert_non_null(c); + + rc = ssh_channel_open_session(c); + assert_ssh_return_code(session, rc); + + /* stdin must be a TTY, so open one and replace the FD */ + stdin_backup_fd = dup(STDIN_FILENO); + rc = openpty(&master_fd, &slave_fd, NULL, NULL, NULL); + assert_int_equal(rc, 0); + dup2(master_fd, STDIN_FILENO); + assert_true(isatty(STDIN_FILENO)); + /* translate NL to CRNL on output to see a noticeable effect */ + memset(&modes, 0, sizeof(modes)); + tcgetattr(STDIN_FILENO, &modes); + modes.c_oflag |= ONLCR; + modes.c_iflag &= ~(ICRNL | INLCR | IGNCR); + tcsetattr(STDIN_FILENO, TCSANOW, &modes); + + rc = ssh_channel_request_pty_size(c, "xterm", 80, 25); + + /* revert the changes to STDIN first! */ + dup2(stdin_backup_fd, STDIN_FILENO); + close(stdin_backup_fd); + close(master_fd); + close(slave_fd); + + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(c, "/bin/echo -e '>TEST\\r\\n<'"); + assert_ssh_return_code(session, rc); + + /* expect 2 carriage return characters + newline */ + string_found = check_channel_output(c, ">TEST\r\r\n<"); + assert_int_equal(string_found, 1); + + ssh_channel_close(c); +} + +/* if stdin is NOT a TTY, default modes are passed to the server */ +static void torture_request_pty_modes_use_default_modes(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + int rc; + int string_found = 0; + int stdin_backup_fd = -1; + + c = ssh_channel_new(session); + assert_non_null(c); + + rc = ssh_channel_open_session(c); + assert_ssh_return_code(session, rc); + + /* stdin must not a TTY - change the FD to something else */ + stdin_backup_fd = dup(STDIN_FILENO); + close(STDIN_FILENO); + rc = open("/dev/null", O_RDONLY); // reuses FD 0 now + assert_int_equal(rc, STDIN_FILENO); + assert_false(isatty(STDIN_FILENO)); + + rc = ssh_channel_request_pty_size(c, "xterm", 80, 25); + + /* revert the changes to STDIN first! */ + dup2(stdin_backup_fd, STDIN_FILENO); + close(stdin_backup_fd); + + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(c, "/bin/echo -e '>TEST\\r\\n<'"); + assert_ssh_return_code(session, rc); + + /* expect the input unmodified */ + string_found = check_channel_output(c, ">TEST\r\n<"); + assert_int_equal(string_found, 1); + + ssh_channel_close(c); +} + +int torture_run_tests(void) { + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_request_pty_modes_translate_ocrnl, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_request_pty_modes_use_stdin_modes, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_request_pty_modes_use_default_modes, + 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 index 59a00bae..fe3f239b 100644 --- a/tests/client/torture_scp.c +++ b/tests/client/torture_scp.c @@ -39,6 +39,9 @@ #define TEMPLATE BINARYDIR "/tests/home/alice/temp_dir_XXXXXX" #define ALICE_HOME BINARYDIR "/tests/home/alice" +/* store the original umask */ +mode_t old; + struct scp_st { struct torture_state *s; char *tmp_dir; @@ -99,6 +102,9 @@ static int session_setup(void **state) s = ts->s; + /* store the original umask and set a new one */ + old = umask(0022); + /* Create temporary directory for alice */ tmp_dir = torture_make_temp_dir(TEMPLATE); assert_non_null(tmp_dir); @@ -135,6 +141,9 @@ static int session_teardown(void **state) assert_non_null(ts->s); s = ts->s; + /* restore the umask */ + umask(old); + ssh_disconnect(s->ssh.session); ssh_free(s->ssh.session); diff --git a/tests/client/torture_session.c b/tests/client/torture_session.c index 2fa2ae33..8a43e586 100644 --- a/tests/client/torture_session.c +++ b/tests/client/torture_session.c @@ -218,6 +218,39 @@ static void torture_max_sessions(void **state) #undef MAX_CHANNELS } +static void torture_no_more_sessions(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channels[2]; + int rc; + + /* Open a channel session */ + channels[0] = ssh_channel_new(session); + assert_non_null(channels[0]); + + rc = ssh_channel_open_session(channels[0]); + assert_ssh_return_code(session, rc); + + /* Send no-more-sessions@openssh.com global request */ + rc = ssh_request_no_more_sessions(session); + assert_ssh_return_code(session, rc); + + /* Try to open an extra session and expect failure */ + channels[1] = ssh_channel_new(session); + assert_non_null(channels[1]); + + rc = ssh_channel_open_session(channels[1]); + assert_int_equal(rc, SSH_ERROR); + + /* Free the unused channel */ + ssh_channel_free(channels[1]); + + /* Close and free open channel */ + ssh_channel_close(channels[0]); + ssh_channel_free(channels[0]); +} + static void torture_channel_delayed_close(void **state) { struct torture_state *s = *state; @@ -258,6 +291,225 @@ static void torture_channel_delayed_close(void **state) } +/* Ensure that calling 'ssh_channel_poll' on a freed channel does not lead to + * segmentation faults. */ +static void torture_freed_channel_poll(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + + char request[256]; + int rc; + + snprintf(request, 256, + "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + + ssh_channel_free(channel); + + rc = ssh_channel_poll(channel, 0); + assert_int_equal(rc, SSH_ERROR); +} + +/* Ensure that calling 'ssh_channel_poll_timeout' on a freed channel does not + * lead to segmentation faults. */ +static void torture_freed_channel_poll_timeout(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + bool channel_freed = false; + char request[256]; + char buff[256] = {0}; + int rc; + + snprintf(request, 256, + "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_channel_read(channel, buff, 256, 0); + } while(rc > 0); + assert_ssh_return_code(session, rc); + + /* when either of these conditions is met the call to ssh_channel_free will + * actually free the channel so calling poll on that channel will be + * use-after-free */ + if ((channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) || + (channel->flags & SSH_CHANNEL_FLAG_NOT_BOUND)) { + channel_freed = true; + } + ssh_channel_free(channel); + + if (!channel_freed) { + rc = ssh_channel_poll_timeout(channel, 500, 0); + assert_int_equal(rc, SSH_ERROR); + } +} + +/* Ensure that calling 'ssh_channel_read_nonblocking' on a freed channel does + * not lead to segmentation faults. */ +static void torture_freed_channel_read_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + + char request[256]; + char buff[256] = {0}; + int rc; + + snprintf(request, 256, + "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + + ssh_channel_free(channel); + + rc = ssh_channel_read_nonblocking(channel, buff, 256, 0); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); +} + +/* Ensure that calling 'ssh_channel_get_exit_status' on a freed channel does not + * lead to segmentation faults. */ +static void torture_freed_channel_get_exit_status(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + bool channel_freed = false; + char request[256]; + char buff[256] = {0}; + int rc; + + snprintf(request, 256, + "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_channel_read(channel, buff, 256, 0); + } while(rc > 0); + assert_ssh_return_code(session, rc); + + /* when either of these conditions is met the call to ssh_channel_free will + * actually free the channel so calling poll on that channel will be + * use-after-free */ + if ((channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) || + (channel->flags & SSH_CHANNEL_FLAG_NOT_BOUND)) { + channel_freed = true; + } + ssh_channel_free(channel); + + if (!channel_freed) { + rc = ssh_channel_get_exit_status(channel); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + } +} + +static void +torture_channel_read_stderr(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* This writes to standard error "pipe" */ + rc = ssh_channel_request_exec(channel, "echo -n ABCD >&2"); + assert_ssh_return_code(session, rc); + + /* No data in stdout */ + rc = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + assert_int_equal(rc, 0); + + /* poll should say how much we can read */ + rc = ssh_channel_poll(channel, 1); + assert_int_equal(rc, strlen("ABCD")); + + /* Everything in stderr */ + rc = ssh_channel_read(channel, buffer, sizeof(buffer), 1); + assert_int_equal(rc, strlen("ABCD")); + + buffer[rc] = '\0'; + assert_string_equal("ABCD", buffer); + + ssh_channel_free(channel); +} + +static void torture_pubkey_hash(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char *hash = NULL; + char *hexa = NULL; + int rc = 0; + + /* bad arguments */ + rc = ssh_get_pubkey_hash(session, NULL); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_get_pubkey_hash(NULL, (unsigned char **)&hash); + assert_int_equal(rc, SSH_ERROR); + + /* deprecated, but should be covered by tests! */ + rc = ssh_get_pubkey_hash(session, (unsigned char **)&hash); + if (ssh_fips_mode()) { + /* When in FIPS mode, expect the call to fail */ + assert_int_equal(rc, SSH_ERROR); + } else { + assert_int_equal(rc, MD5_DIGEST_LEN); + + hexa = ssh_get_hexa((unsigned char *)hash, rc); + SSH_STRING_FREE_CHAR(hash); + assert_string_equal(hexa, + "ee:80:7f:61:f9:d5:be:f1:96:86:cc:96:7a:db:7a:7b"); + + SSH_STRING_FREE_CHAR(hexa); + } +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -273,9 +525,30 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_max_sessions, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_no_more_sessions, + session_setup, + session_teardown), cmocka_unit_test_setup_teardown(torture_channel_delayed_close, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_freed_channel_poll, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_freed_channel_poll_timeout, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_freed_channel_read_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_freed_channel_get_exit_status, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_channel_read_stderr, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_pubkey_hash, + session_setup, + session_teardown), }; ssh_init(); diff --git a/tests/client/torture_sftp_aio.c b/tests/client/torture_sftp_aio.c new file mode 100644 index 00000000..4cbb5793 --- /dev/null +++ b/tests/client/torture_sftp_aio.c @@ -0,0 +1,534 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include <sys/types.h> +#include <pwd.h> +#include <errno.h> + +#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 = NULL; + 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_aio_read_file(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + struct { + char *buf; + ssize_t bytes_read; + } a = {0}, b = {0}; + + sftp_file file = NULL; + sftp_attributes file_attr = NULL; + int fd; + + size_t chunk_size; + int in_flight_requests = 20; + + sftp_aio aio = NULL; + struct ssh_list *aio_queue = NULL; + sftp_limits_t li = NULL; + + size_t file_size; + size_t total_bytes_requested; + size_t to_read, total_bytes_read; + ssize_t bytes_requested; + + int i, rc; + + /* Get the max limit for reading, use it as the chunk size */ + li = sftp_limits(t->sftp); + assert_non_null(li); + chunk_size = li->max_read_length; + + a.buf = calloc(chunk_size, 1); + assert_non_null(a.buf); + + b.buf = calloc(chunk_size, 1); + assert_non_null(b.buf); + + aio_queue = ssh_list_new(); + assert_non_null(aio_queue); + + file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + fd = open(SSH_EXECUTABLE, O_RDONLY, 0); + assert_int_not_equal(fd, -1); + + /* Get the file size */ + file_attr = sftp_stat(t->sftp, SSH_EXECUTABLE); + assert_non_null(file_attr); + file_size = file_attr->size; + + total_bytes_requested = 0; + for (i = 0; + i < in_flight_requests && total_bytes_requested < file_size; + ++i) { + to_read = file_size - total_bytes_requested; + if (to_read > chunk_size) { + to_read = chunk_size; + } + + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + assert_int_equal(bytes_requested, to_read); + total_bytes_requested += bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + assert_int_equal(rc, SSH_OK); + } + + total_bytes_read = 0; + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + a.bytes_read = sftp_aio_wait_read(&aio, a.buf, chunk_size); + assert_int_not_equal(a.bytes_read, SSH_ERROR); + + total_bytes_read += (size_t)a.bytes_read; + if (total_bytes_read != file_size) { + assert_int_equal((size_t)a.bytes_read, chunk_size); + /* + * Failure of this assertion means that a short + * read is encountered but we have not reached + * the end of file yet. A short read before reaching + * the end of file should not occur for our test where + * the chunk size respects the max limit for reading. + */ + } + + /* + * Check whether the bytes read above are bytes + * present in the file or some garbage was stored + * in the buffer supplied to sftp_aio_wait_read(). + */ + b.bytes_read = read(fd, b.buf, a.bytes_read); + assert_int_equal(a.bytes_read, b.bytes_read); + + rc = memcmp(a.buf, b.buf, (size_t)a.bytes_read); + assert_int_equal(rc, 0); + + /* Issue more read requests if needed */ + if (total_bytes_requested == file_size) { + continue; + } + + /* else issue more requests */ + to_read = file_size - total_bytes_requested; + if (to_read > chunk_size) { + to_read = chunk_size; + } + + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + assert_int_equal(bytes_requested, to_read); + total_bytes_requested += bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + assert_int_equal(rc, SSH_OK); + } + + /* + * Check whether sftp server responds with an + * eof for more requests. + */ + bytes_requested = sftp_aio_begin_read(file, chunk_size, &aio); + assert_int_equal(bytes_requested, chunk_size); + + a.bytes_read = sftp_aio_wait_read(&aio, a.buf, chunk_size); + assert_int_equal(a.bytes_read, 0); + + /* Clean up */ + sftp_attributes_free(file_attr); + close(fd); + sftp_close(file); + ssh_list_free(aio_queue); + free(b.buf); + free(a.buf); + sftp_limits_free(li); +} + +static void torture_sftp_aio_read_more_than_cap(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + sftp_limits_t li = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + + char *buf = NULL; + ssize_t bytes; + + /* Get the max limit for reading */ + li = sftp_limits(t->sftp); + assert_non_null(li); + + file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + /* Try reading more than the max limit */ + bytes = sftp_aio_begin_read(file, + li->max_read_length * 2, + &aio); + assert_int_equal(bytes, li->max_read_length); + + buf = calloc(li->max_read_length, 1); + assert_non_null(buf); + + bytes = sftp_aio_wait_read(&aio, buf, li->max_read_length); + assert_int_not_equal(bytes, SSH_ERROR); + + free(buf); + sftp_close(file); + sftp_limits_free(li); +} + +static void torture_sftp_aio_write_file(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char file_path[128] = {0}; + sftp_file file = NULL; + int fd; + + struct { + char *buf; + ssize_t bytes; + } wr = {0}, rd = {0}; + + size_t chunk_size; + ssize_t bytes_requested; + int in_flight_requests = 2; + + sftp_limits_t li = NULL; + sftp_aio *aio_queue = NULL; + int rc, i; + + /* Get the max limit for writing, use it as the chunk size */ + li = sftp_limits(t->sftp); + assert_non_null(li); + chunk_size = li->max_write_length; + + rd.buf = calloc(chunk_size, 1); + assert_non_null(rd.buf); + + wr.buf = calloc(chunk_size, 1); + assert_non_null(wr.buf); + + aio_queue = malloc(sizeof(sftp_aio) * in_flight_requests); + assert_non_null(aio_queue); + + snprintf(file_path, sizeof(file_path), + "%s/libssh_sftp_aio_write_test", t->testdir); + file = sftp_open(t->sftp, file_path, O_CREAT | O_WRONLY, 0777); + assert_non_null(file); + + fd = open(file_path, O_RDONLY, 0); + assert_int_not_equal(fd, -1); + + for (i = 0; i < in_flight_requests; ++i) { + bytes_requested = sftp_aio_begin_write(file, + wr.buf, + chunk_size, + &aio_queue[i]); + assert_int_equal(bytes_requested, chunk_size); + } + + for (i = 0; i < in_flight_requests; ++i) { + wr.bytes = sftp_aio_wait_write(&aio_queue[i]); + assert_int_equal(wr.bytes, chunk_size); + + /* + * Check whether the bytes written to the file + * by SFTP AIO write api were the bytes present + * in the buffer to write or some garbage was + * written to the file. + */ + rd.bytes = read(fd, rd.buf, wr.bytes); + assert_int_equal(rd.bytes, wr.bytes); + + rc = memcmp(rd.buf, wr.buf, wr.bytes); + assert_int_equal(rc, 0); + } + + /* Clean up */ + close(fd); + sftp_close(file); + free(aio_queue); + + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(wr.buf); + free(rd.buf); + sftp_limits_free(li); +} + +static void torture_sftp_aio_write_more_than_cap(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + sftp_limits_t li = NULL; + char *buf = NULL; + size_t buf_size; + + char file_path[128] = {0}; + sftp_file file = NULL; + + sftp_aio aio = NULL; + ssize_t bytes; + int rc; + + li = sftp_limits(t->sftp); + assert_non_null(li); + + buf_size = li->max_write_length * 2; + buf = calloc(buf_size, 1); + assert_non_null(buf); + + snprintf(file_path, sizeof(file_path), + "%s/libssh_sftp_aio_write_test_cap", t->testdir); + file = sftp_open(t->sftp, file_path, O_CREAT | O_WRONLY, 0777); + assert_non_null(file); + + /* Try writing more than the max limit for writing */ + bytes = sftp_aio_begin_write(file, buf, buf_size, &aio); + assert_int_equal(bytes, li->max_write_length); + + bytes = sftp_aio_wait_write(&aio); + assert_int_equal(bytes, li->max_write_length); + + /* Clean up */ + sftp_close(file); + + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(buf); + sftp_limits_free(li); +} + +static void torture_sftp_aio_read_negative(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char *buf = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + sftp_limits_t li = NULL; + + size_t chunk_size; + ssize_t bytes; + int rc; + + li = sftp_limits(t->sftp); + assert_non_null(li); + chunk_size = li->max_read_length; + + buf = calloc(chunk_size, 1); + assert_non_null(buf); + + /* Open a file for reading */ + file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + /* Passing NULL as the sftp file handle */ + bytes = sftp_aio_begin_read(NULL, chunk_size, &aio); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing 0 as the number of bytes to read */ + bytes = sftp_aio_begin_read(file, 0, &aio); + assert_int_equal(bytes, SSH_ERROR); + + /* + * Passing NULL instead of a pointer to a location to + * store an aio handle. + */ + bytes = sftp_aio_begin_read(file, chunk_size, NULL); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing NULL instead of a pointer to an aio handle */ + bytes = sftp_aio_wait_read(NULL, buf, sizeof(buf)); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing NULL as the buffer's address */ + bytes = sftp_aio_begin_read(file, chunk_size, &aio); + assert_int_equal(bytes, chunk_size); + + bytes = sftp_aio_wait_read(&aio, NULL, sizeof(buf)); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing 0 as the buffer size */ + bytes = sftp_aio_begin_read(file, chunk_size, &aio); + assert_int_equal(bytes, chunk_size); + + bytes = sftp_aio_wait_read(&aio, buf, 0); + assert_int_equal(bytes, SSH_ERROR); + + /* + * Test for the scenario when the number + * of bytes read exceed the buffer size. + */ + rc = sftp_seek(file, 0); /* Seek to the start of file */ + assert_int_equal(rc, 0); + + bytes = sftp_aio_begin_read(file, 2, &aio); + assert_int_equal(bytes, 2); + + bytes = sftp_aio_wait_read(&aio, buf, 1); + assert_int_equal(bytes, SSH_ERROR); + + sftp_close(file); + free(buf); + sftp_limits_free(li); +} + +static void torture_sftp_aio_write_negative(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char *buf = NULL; + + char file_path[128] = {0}; + sftp_file file = NULL; + sftp_aio aio = NULL; + sftp_limits_t li = NULL; + + size_t chunk_size; + ssize_t bytes; + int rc; + + li = sftp_limits(t->sftp); + assert_non_null(li); + chunk_size = li->max_write_length; + + buf = calloc(chunk_size, 1); + assert_non_null(buf); + + /* Open a file for writing */ + snprintf(file_path, sizeof(file_path), + "%s/libssh_sftp_aio_write_test_negative", t->testdir); + file = sftp_open(t->sftp, file_path, O_CREAT | O_WRONLY, 0777); + assert_non_null(file); + + /* Passing NULL as the sftp file handle */ + bytes = sftp_aio_begin_write(NULL, buf, chunk_size, &aio); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing NULL as the buffer's address */ + bytes = sftp_aio_begin_write(file, NULL, chunk_size, &aio); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing 0 as the size of buffer */ + bytes = sftp_aio_begin_write(file, buf, 0, &aio); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing NULL instead of a pointer to a location to store an aio handle */ + bytes = sftp_aio_begin_write(file, buf, chunk_size, NULL); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing NULL instead of a pointer to an aio handle */ + bytes = sftp_aio_wait_write(NULL); + assert_int_equal(bytes, SSH_ERROR); + + sftp_close(file); + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(buf); + sftp_limits_free(li); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_aio_read_file, + session_setup, + session_teardown), + + cmocka_unit_test_setup_teardown(torture_sftp_aio_read_more_than_cap, + session_setup, + session_teardown), + + cmocka_unit_test_setup_teardown(torture_sftp_aio_write_file, + session_setup, + session_teardown), + + cmocka_unit_test_setup_teardown(torture_sftp_aio_write_more_than_cap, + session_setup, + session_teardown), + + cmocka_unit_test_setup_teardown(torture_sftp_aio_read_negative, + session_setup, + session_teardown), + + cmocka_unit_test_setup_teardown(torture_sftp_aio_write_negative, + 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_expand_path.c b/tests/client/torture_sftp_expand_path.c new file mode 100644 index 00000000..85ef0108 --- /dev/null +++ b/tests/client/torture_sftp_expand_path.c @@ -0,0 +1,125 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "sftp.c" + +#include <sys/types.h> +#include <pwd.h> +#include <errno.h> + +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 = NULL; + 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_expand_path(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *pwd = NULL; + char *expanded_path = NULL; + int rc; + + rc = sftp_extension_supported(t->sftp, "expand-path@openssh.com", "1"); + if (rc == 0) { + skip(); + } + + pwd = getpwnam(TORTURE_SSH_USER_ALICE); + assert_non_null(pwd); + + /* testing for a absolute path */ + expanded_path = sftp_expand_path(t->sftp, "~/."); + assert_non_null(expanded_path); + + assert_string_equal(expanded_path, pwd->pw_dir); + + SSH_STRING_FREE_CHAR(expanded_path); + + /* testing for a relative path */ + expanded_path = sftp_expand_path(t->sftp, "."); + assert_non_null(expanded_path); + + assert_string_equal(expanded_path, pwd->pw_dir); + + SSH_STRING_FREE_CHAR(expanded_path); + + /* passing a NULL sftp session */ + expanded_path = sftp_expand_path(NULL, "~/."); + assert_null(expanded_path); + + /* passing an invalid path */ + expanded_path = sftp_expand_path(t->sftp, "/...//"); + assert_null(expanded_path); + + /* passing null path */ + expanded_path = sftp_expand_path(t->sftp, NULL); + assert_null(expanded_path); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_expand_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_hardlink.c b/tests/client/torture_sftp_hardlink.c new file mode 100644 index 00000000..37963636 --- /dev/null +++ b/tests/client/torture_sftp_hardlink.c @@ -0,0 +1,114 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include <sys/types.h> +#include <pwd.h> +#include <errno.h> + +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 = NULL; + 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_hardlink(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char link_1[128] = {0}; + char link_2[128] = {0}; + int fd; + int rc; + + snprintf(link_1, sizeof(link_1), + "%s/libssh_sftp_hardlink_test_1", t->testdir); + snprintf(link_2, sizeof(link_2), + "%s/libssh_sftp_hardlink_test_2", t->testdir); + + fd = open(link_1, O_CREAT, S_IRWXU); + assert_return_code(fd, errno); + close(fd); + + rc = sftp_hardlink(t->sftp, link_1, link_2); + assert_int_equal(rc, SSH_OK); + + /* check whether the file got associated with link_2 */ + rc = access(link_2, F_OK); + assert_int_equal(rc, 0); + + unlink(link_1); + unlink(link_2); + + /* + * try to create a hardlink for a file that does not + * exist, this should fail + */ + rc = sftp_hardlink(t->sftp, link_1, link_2); + assert_int_not_equal(rc, 0); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_hardlink, + 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_limits.c b/tests/client/torture_sftp_limits.c new file mode 100644 index 00000000..07ef9928 --- /dev/null +++ b/tests/client/torture_sftp_limits.c @@ -0,0 +1,177 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include <sys/types.h> +#include <sys/resource.h> +#include <pwd.h> +#include <errno.h> + +#if HAVE_VALGRIND_VALGRIND_H + #include <valgrind/valgrind.h> +#endif + +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 = NULL; + 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_limits(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_limits_t li = NULL; + int rc; + + li = sftp_limits(t->sftp); + assert_non_null(li); + + rc = sftp_extension_supported(t->sftp, "limits@openssh.com", "1"); + if (rc == 1) { + /* + * Tests are run against the OpenSSH server, hence we check for the + * specific limits used by OpenSSH. + */ + uint64_t openssh_max_packet_length = 256 * 1024; + uint64_t openssh_max_read_length = openssh_max_packet_length - 1024; + uint64_t openssh_max_write_length = openssh_max_packet_length - 1024; + size_t vg = 0; + + assert_int_equal(li->max_packet_length, openssh_max_packet_length); + assert_int_equal(li->max_read_length, openssh_max_read_length); + assert_int_equal(li->max_write_length, openssh_max_write_length); + + /* + * fds - File descriptors, w.r.to - With respect to + * + * Valgrind reserves some fds for itself and changes the rlimits + * w.r.to fds for the process its inspecting. Due to this reservation + * the rlimits w.r.to fds for our test may not be the same as the + * rlimits w.r.to fds seen by OpenSSH server (which Valgrind isn't + * inspecting). + * + * Valgrind changes the limits in such a way that after seeing the + * changed limits, the test cannot predict the original unchanged + * limits (which OpenSSH would be using). Hence, the test cannot + * determine the correct value of "max_open_handles" that the OpenSSH + * server should've sent. + * + * So if Valgrind is running our test, we don't provide any kind of + * check for max_open_handles. Check for >= 0 is also not provided in + * this case since that's always true for an uint64_t (an unsigned type) + */ +#if HAVE_VALGRIND_VALGRIND_H + vg = RUNNING_ON_VALGRIND; +#endif + + if (vg == 0) { + struct rlimit rlim = {0}; + uint64_t openssh_max_open_handles = 0; + + /* + * Get the resource limit for max file descriptors that a process + * can open. Since the client and the server run on the same machine + * in case of tests, this limit should be same for both (except the + * case when Valgrind runs the test) + */ + rc = getrlimit(RLIMIT_NOFILE, &rlim); + assert_int_equal(rc, 0); + if (rlim.rlim_cur > 5) { + /* + * Leaving file handles for stdout, stdin, stderr, syslog and + * a spare file handle, OpenSSH server allows the client to open + * at max (rlim.rlim_cur - 5) handles. + */ + openssh_max_open_handles = rlim.rlim_cur - 5; + } + + assert_int_equal(li->max_open_handles, openssh_max_open_handles); + } + } else { + /* Check for the default limits */ + assert_int_equal(li->max_packet_length, 34000); + assert_int_equal(li->max_read_length, 32768); + assert_int_equal(li->max_write_length, 32768); + assert_int_equal(li->max_open_handles, 0); + } + + sftp_limits_free(li); +} + +static void torture_sftp_limits_negative(void **state) +{ + sftp_limits_t li = NULL; + + (void)state; + li = sftp_limits(NULL); + assert_null(li); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_limits, + session_setup, + session_teardown), + + cmocka_unit_test_setup_teardown(torture_sftp_limits_negative, + 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 index 687153eb..c6ec4b91 100644 --- a/tests/client/torture_sftp_read.c +++ b/tests/client/torture_sftp_read.c @@ -101,7 +101,7 @@ int torture_run_tests(void) { 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 + * See: https://gitlab.com/libssh/libssh-mirror/-/issues/23 */ cmocka_unit_test_setup_teardown(torture_sftp_read_blocking, session_setup, diff --git a/tests/client/torture_sftp_rename.c b/tests/client/torture_sftp_rename.c new file mode 100644 index 00000000..c2d11a31 --- /dev/null +++ b/tests/client/torture_sftp_rename.c @@ -0,0 +1,120 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include <sys/types.h> +#include <pwd.h> +#include <errno.h> + +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 = NULL; + 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_rename(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char name_1[128] = {0}; + char name_2[128] = {0}; + + int fd; + int rc; + + snprintf(name_1, sizeof(name_1), + "%s/libssh_sftp_rename_test_1", t->testdir); + snprintf(name_2, sizeof(name_2), + "%s/libssh_sftp_rename_test_2", t->testdir); + + fd = open(name_1, O_CREAT, S_IRWXU); + assert_return_code(fd, errno); + close(fd); + + /* try to rename an existing file */ + rc = sftp_rename(t->sftp, name_1, name_2); + assert_int_equal(rc, SSH_OK); + + /* check whether any file with name_1 exists, it shouldn't */ + rc = access(name_1, F_OK); + assert_int_not_equal(rc, 0); + + /* check whether file with name_2 exists, it should */ + rc = access(name_2, F_OK); + assert_int_equal(rc, 0); + + unlink(name_2); + + /* + * try to rename a file that does not exist, + * this should fail (-ve test case) + */ + rc = sftp_rename(t->sftp, name_1, name_2); + assert_int_not_equal(rc, 0); +} + + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_rename, + 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/ctest-default.cmake b/tests/ctest-default.cmake index dbf53e30..51bbebab 100644 --- a/tests/ctest-default.cmake +++ b/tests/ctest-default.cmake @@ -17,16 +17,16 @@ set(CTEST_BUILD_OPTIONS "-DUNIT_TESTING=ON -WITH_SFTP=ON -DWITH_SERVER=ON -DWITH #set(CTEST_CUSTOM_MEMCHECK_IGNORE torture_rand) -## The Model to set: Nightly, Continous, Experimental +## The Model to set: Nightly, Continuous, Experimental set(CTEST_MODEL "Experimental") ## The branch #set(CTEST_GIT_BRANCH "--branch v0-5") -## Wether to enable memory checking. +## Whether to enable memory checking. set(WITH_MEMCHECK FALSE) -## Wether to enable code coverage. +## Whether to enable code coverage. set(WITH_COVERAGE FALSE) ####################################################################### diff --git a/tests/etc/hosts.in b/tests/etc/hosts.in index 2ab69799..8891125d 100644 --- a/tests/etc/hosts.in +++ b/tests/etc/hosts.in @@ -1,2 +1,5 @@ 127.0.0.10 server.libssh.site 127.0.0.21 client.libssh.site + +123.0.0.11 testing +fd00::5357:5f0a testing diff --git a/tests/etc/pam_matrix_passdb.in b/tests/etc/pam_matrix_passdb.in index c0aa54e5..9404bc0e 100644 --- a/tests/etc/pam_matrix_passdb.in +++ b/tests/etc/pam_matrix_passdb.in @@ -1,3 +1,4 @@ bob:secret:sshd alice:secret:sshd charlie:secret:sshd +doe:secret:sshd diff --git a/tests/etc/passwd.in b/tests/etc/passwd.in index 85e20c6d..cae364b7 100644 --- a/tests/etc/passwd.in +++ b/tests/etc/passwd.in @@ -1,6 +1,7 @@ bob:x:5000:9000:bob gecos:@HOMEDIR@/bob:/bin/sh alice:x:5001:9000:alice gecos:@HOMEDIR@/alice:/bin/sh charlie:x:5002:9000:charlie gecos:@HOMEDIR@/charlie:/bin/sh +doe:x:5003:9000:doe gecos:@HOMEDIR@/doe:/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 diff --git a/tests/etc/shadow.in b/tests/etc/shadow.in index a0b2b9d6..0f03b149 100644 --- a/tests/etc/shadow.in +++ b/tests/etc/shadow.in @@ -1,3 +1,4 @@ alice:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: bob:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: charlie:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: +doe:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: diff --git a/tests/external_override/CMakeLists.txt b/tests/external_override/CMakeLists.txt index a0d584e3..365a1083 100644 --- a/tests/external_override/CMakeLists.txt +++ b/tests/external_override/CMakeLists.txt @@ -22,26 +22,58 @@ add_library(poly1305_override SHARED set(POLY1305_OVERRIDE_LIBRARY ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}poly1305_override${CMAKE_SHARED_LIBRARY_SUFFIX}) +if (WITH_GCRYPT) + set (override_src + ${libssh_SOURCE_DIR}/src/getrandom_gcrypt.c + ${libssh_SOURCE_DIR}/src/md_gcrypt.c + ) + set(override_libs + ${GCRYPT_LIBRARIES} + ) +elseif (WITH_MBEDTLS) + set (override_src + ${libssh_SOURCE_DIR}/src/getrandom_mbedcrypto.c + ${libssh_SOURCE_DIR}/src/md_mbedcrypto.c + ) + set(override_libs + ${MBEDTLS_CRYPTO_LIBRARY} + ) +else () + set (override_src + ${libssh_SOURCE_DIR}/src/getrandom_crypto.c + ${libssh_SOURCE_DIR}/src/md_crypto.c + ) + set(override_libs + OpenSSL::Crypto + ) +endif (WITH_GCRYPT) + # ed25519_override add_library(ed25519_override SHARED - ed25519_override.c - ${libssh_SOURCE_DIR}/src/external/fe25519.c - ${libssh_SOURCE_DIR}/src/external/ge25519.c - ${libssh_SOURCE_DIR}/src/external/sc25519.c - ${libssh_SOURCE_DIR}/src/external/ed25519.c - ) + ed25519_override.c + ${libssh_SOURCE_DIR}/src/external/fe25519.c + ${libssh_SOURCE_DIR}/src/external/ge25519.c + ${libssh_SOURCE_DIR}/src/external/sc25519.c + ${libssh_SOURCE_DIR}/src/external/ed25519.c + ${override_src} + ) +target_link_libraries(ed25519_override + PRIVATE ${override_libs}) set(ED25519_OVERRIDE_LIBRARY ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}ed25519_override${CMAKE_SHARED_LIBRARY_SUFFIX}) # curve25519_override add_library(curve25519_override SHARED - curve25519_override.c - ${libssh_SOURCE_DIR}/src/external/curve25519_ref.c - ${libssh_SOURCE_DIR}/src/external/fe25519.c - ${libssh_SOURCE_DIR}/src/external/ge25519.c - ${libssh_SOURCE_DIR}/src/external/sc25519.c - ${libssh_SOURCE_DIR}/src/external/ed25519.c - ) + curve25519_override.c + ${libssh_SOURCE_DIR}/src/external/curve25519_ref.c + ${libssh_SOURCE_DIR}/src/external/fe25519.c + ${libssh_SOURCE_DIR}/src/external/ge25519.c + ${libssh_SOURCE_DIR}/src/external/sc25519.c + ${libssh_SOURCE_DIR}/src/external/ed25519.c + ${override_src} +) +target_link_libraries(curve25519_override + PRIVATE ${override_libs}) set(CURVE25519_OVERRIDE_LIBRARY ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}curve25519_override${CMAKE_SHARED_LIBRARY_SUFFIX}) @@ -66,23 +98,13 @@ elseif (WITH_GCRYPT) list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_ED25519=1") list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=1") else () - if (HAVE_OPENSSL_EVP_CHACHA20 AND HAVE_OPENSSL_EVP_POLY1305) + if (HAVE_OPENSSL_EVP_CHACHA20) list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=0") else () list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=1") endif () - - if (HAVE_OPENSSL_ED25519) - list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_ED25519=0") - else () - list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_ED25519=1") - endif () - - if (HAVE_OPENSSL_X25519) - list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=0") - else () - list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=1") - endif () + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=0") + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_ED25519=0") endif () if (NOT OSX) diff --git a/tests/external_override/torture_override.c b/tests/external_override/torture_override.c index f351d52e..8024574b 100644 --- a/tests/external_override/torture_override.c +++ b/tests/external_override/torture_override.c @@ -56,7 +56,7 @@ static int sshd_setup(void **state) s = *((struct torture_state **)state); s->private_data = test_state; - test_state->orig_dir = strdup(torture_get_current_working_dir()); + test_state->orig_dir = torture_get_current_working_dir(); assert_non_null(test_state->orig_dir); temp_dir = torture_make_temp_dir(template); @@ -137,7 +137,7 @@ static void test_algorithm(ssh_session session, const char *cipher, const char *hostkey) { - char data[256]; + char data[256] = {0}; int rc; if (kex != NULL) { @@ -161,7 +161,7 @@ static void test_algorithm(ssh_session session, assert_ssh_return_code(session, rc); /* send ignore packets of all sizes */ - memset(data, 'A', sizeof(data)); + memset(data, 'A', sizeof(data) - 1); ssh_send_ignore(session, data); ssh_handle_packets(session, 50); @@ -195,8 +195,8 @@ static void torture_override_chacha20_poly1305(void **state) internal_poly1305_called = internal_poly1305_function_called(); #if SHOULD_CALL_INTERNAL_CHACHAPOLY - assert_true(internal_chacha20_called || - internal_poly1305_called); + assert_true(internal_chacha20_called); + assert_true(internal_poly1305_called); #else assert_false(internal_chacha20_called || internal_poly1305_called); diff --git a/tests/fuzz/CMakeLists.txt b/tests/fuzz/CMakeLists.txt index 5982e81c..40a6eb13 100644 --- a/tests/fuzz/CMakeLists.txt +++ b/tests/fuzz/CMakeLists.txt @@ -1,7 +1,7 @@ project(fuzzing CXX) macro(fuzzer name) - add_executable(${name} ${name}.cpp) + add_executable(${name} ${name}.c) target_link_libraries(${name} PRIVATE ssh::static) @@ -15,9 +15,22 @@ macro(fuzzer name) else() target_sources(${name} PRIVATE fuzzer.c) # Run the fuzzer to make sure it works - # add_test(${name} ${CMAKE_CURRENT_BINARY_DIR}/${name} EXAMPLE) + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${name}_corpus") + file(GLOB files "${CMAKE_CURRENT_SOURCE_DIR}/${name}_corpus/*") + set(i 0) + foreach(file ${files}) + add_test(${name}_${i} + ${CMAKE_CURRENT_BINARY_DIR}/${name} ${file}) + math(EXPR i "${i} + 1") + endforeach() + endif() endif() endmacro() fuzzer(ssh_client_fuzzer) fuzzer(ssh_server_fuzzer) +fuzzer(ssh_client_config_fuzzer) +fuzzer(ssh_bind_config_fuzzer) +fuzzer(ssh_known_hosts_fuzzer) +fuzzer(ssh_privkey_fuzzer) +fuzzer(ssh_pubkey_fuzzer) diff --git a/tests/fuzz/README.md b/tests/fuzz/README.md index c68ef3d1..088ce27d 100644 --- a/tests/fuzz/README.md +++ b/tests/fuzz/README.md @@ -95,7 +95,7 @@ You can either pick up my branch or workaround them locally: ### Reproduce locally Clone the above repository from https://github.com/google/oss-fuzz/, apply -changes from previous secion if needed, setup local clone of libssh repository +changes from previous section if needed, setup local clone of libssh repository and build the fuzzers locally (where `~/devel/libssh` is path to local libssh checkout): @@ -111,7 +111,7 @@ This should give you the same error/leak/crash as you see on the testcase detail in oss-fuzz.com. I find it very useful to run libssh in debug mode, to see what happened and -what exit path was taken to get to the error. Fortunatelly, we can simply +what exit path was taken to get to the error. Fortunately, we can simply pass environment variables to the container: python infra/helper.py reproduce -eLIBSSH_VERBOSITY=9 libssh ssh_client_fuzzer ~/Downloads/clusterfuzz-testcase-ssh_client_fuzzer-4637376441483264 diff --git a/tests/fuzz/ssh_bind_config_fuzzer.c b/tests/fuzz/ssh_bind_config_fuzzer.c new file mode 100644 index 00000000..3d0d8be8 --- /dev/null +++ b/tests/fuzz/ssh_bind_config_fuzzer.c @@ -0,0 +1,52 @@ +/* + * Copyright 2021 Jakub Jelen <jjelen@redhat.com> + * + * 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 <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "libssh/server.h" +#include "libssh/bind_config.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_bind bind = NULL; + char *input = NULL; + + input = (char *)malloc(size + 1); + if (!input) { + return 1; + } + strncpy(input, (const char *)data, size); + input[size] = '\0'; + + ssh_init(); + + bind = ssh_bind_new(); + assert(bind != NULL); + + ssh_bind_config_parse_string(bind, input); + + ssh_bind_free(bind); + ssh_finalize(); + + free(input); + + return 0; +} diff --git a/tests/fuzz/ssh_client_config_fuzzer.c b/tests/fuzz/ssh_client_config_fuzzer.c new file mode 100644 index 00000000..62eae93c --- /dev/null +++ b/tests/fuzz/ssh_client_config_fuzzer.c @@ -0,0 +1,55 @@ +/* + * Copyright 2021 Stanislav Zidek <szidek@redhat.com> + * + * 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 <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "libssh/options.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_session session = NULL; + char *input = NULL; + + input = (char *)malloc(size+1); + if (!input) { + return 1; + } + strncpy(input, (const char *)data, size); + input[size] = '\0'; + + ssh_init(); + + session = ssh_new(); + assert(session != NULL); + + /* Make sure we have default options set */ + ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL); + ssh_options_set(session, SSH_OPTIONS_HOST, "example.com"); + + ssh_config_parse_string(session, input); + + ssh_free(session); + ssh_finalize(); + + free(input); + + return 0; +} diff --git a/tests/fuzz/ssh_client_fuzzer.cpp b/tests/fuzz/ssh_client_fuzzer.c index 8480223c..e69bf385 100644 --- a/tests/fuzz/ssh_client_fuzzer.cpp +++ b/tests/fuzz/ssh_client_fuzzer.c @@ -18,6 +18,7 @@ #include <stdio.h> #include <stdlib.h> #include <signal.h> +#include <stdbool.h> #define LIBSSH_STATIC 1 #include <libssh/libssh.h> @@ -83,7 +84,7 @@ static void select_loop(ssh_session session, ssh_channel channel) ssh_event_free(event); } -extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { ssh_session session = NULL; ssh_channel channel = NULL; @@ -92,6 +93,15 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) ssize_t nwritten; bool no = false; int rc; + long timeout = 1; /* use short timeout to avoid timeouts during fuzzing */ + + /* This is the maximum that can be handled by the socket buffer before the + * other side will read some data. Other option would be feeding the socket + * from different thread which would not mind if it would be blocked, but I + * believe all the important inputs should fit into this size */ + if (size > 219264) { + return -1; + } /* Set up the socket to send data */ rc = socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds); @@ -128,6 +138,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) assert(rc == 0); rc = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &no); assert(rc == 0); + rc = ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &timeout); + assert(rc == 0); ssh_callbacks_init(&cb); ssh_set_callbacks(session, &cb); @@ -143,7 +155,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) } channel = ssh_channel_new(session); - assert(channel != NULL); + if (channel == NULL) { + goto out; + } rc = ssh_channel_open_session(channel); if (rc != SSH_OK) { @@ -151,7 +165,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) } rc = ssh_channel_request_exec(channel, "ls"); - assert(rc == SSH_OK); + if (rc != SSH_OK) { + goto out; + } select_loop(session, channel); diff --git a/tests/fuzz/ssh_known_hosts_fuzzer.c b/tests/fuzz/ssh_known_hosts_fuzzer.c new file mode 100644 index 00000000..fabdc28f --- /dev/null +++ b/tests/fuzz/ssh_known_hosts_fuzzer.c @@ -0,0 +1,82 @@ +/* + * Copyright 2022 Jakub Jelen <jjelen@redhat.com> + * + * 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 <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "knownhosts.c" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + char *hostname = NULL; + const uint8_t *hostname_end = NULL; + size_t hostname_len = 0; + char filename[256]; + struct ssh_list *entries = NULL; + struct ssh_iterator *it = NULL; + FILE *fp = NULL; + + /* Interpret the first part of the string (until the first NULL byte) + * as a hostname we are searching for in the file */ + hostname_end = memchr(data, '\0', size); + if (hostname_end == NULL) { + return 1; + } + hostname_len = hostname_end - data + 1; + if (hostname_len > 253) { + /* This is the maximum valid length of a hostname */ + return 1; + } + hostname = malloc(hostname_len); + if (hostname == NULL) { + return 1; + } + memcpy(hostname, data, hostname_len); + + snprintf(filename, sizeof(filename), "/tmp/libfuzzer.%d", getpid()); + fp = fopen(filename, "wb"); + if (!fp) { + free(hostname); + return 1; + } + fwrite(data + hostname_len, size - hostname_len, 1, fp); + fclose(fp); + + ssh_init(); + + ssh_known_hosts_read_entries(hostname, filename, &entries); + for (it = ssh_list_get_iterator(entries); + it != NULL; + it = ssh_list_get_iterator(entries)) { + struct ssh_knownhosts_entry *entry = NULL; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entries, it); + } + ssh_list_free(entries); + + ssh_finalize(); + + free(hostname); + unlink(filename); + + return 0; +} diff --git a/tests/fuzz/ssh_known_hosts_fuzzer_corpus/d7c0eade3f3b70d94b1a7090e09eb8607da0ace4 b/tests/fuzz/ssh_known_hosts_fuzzer_corpus/d7c0eade3f3b70d94b1a7090e09eb8607da0ace4 Binary files differnew file mode 100644 index 00000000..18e779e0 --- /dev/null +++ b/tests/fuzz/ssh_known_hosts_fuzzer_corpus/d7c0eade3f3b70d94b1a7090e09eb8607da0ace4 diff --git a/tests/fuzz/ssh_privkey_fuzzer.c b/tests/fuzz/ssh_privkey_fuzzer.c new file mode 100644 index 00000000..b65d680d --- /dev/null +++ b/tests/fuzz/ssh_privkey_fuzzer.c @@ -0,0 +1,52 @@ +/* + * Copyright 2023 Jakub Jelen <jjelen@redhat.com> + * + * 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 "config.h" + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "libssh/priv.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_key pkey = NULL; + uint8_t *input = NULL; + int rc; + + input = bin_to_base64(data, size); + if (input == NULL) { + return 1; + } + + ssh_init(); + + rc = ssh_pki_import_privkey_base64((char *)input, NULL, NULL, NULL, &pkey); + free(input); + if (rc != SSH_OK) { + return 1; + } + ssh_key_free(pkey); + + ssh_finalize(); + + return 0; +} + diff --git a/tests/fuzz/ssh_privkey_fuzzer_corpus/855ce609b52aec530bf631a78da7038bed99040a b/tests/fuzz/ssh_privkey_fuzzer_corpus/855ce609b52aec530bf631a78da7038bed99040a new file mode 100644 index 00000000..2759f43e --- /dev/null +++ b/tests/fuzz/ssh_privkey_fuzzer_corpus/855ce609b52aec530bf631a78da7038bed99040a @@ -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/fuzz/ssh_pubkey_fuzzer.c b/tests/fuzz/ssh_pubkey_fuzzer.c new file mode 100644 index 00000000..70c94948 --- /dev/null +++ b/tests/fuzz/ssh_pubkey_fuzzer.c @@ -0,0 +1,68 @@ +/* + * Copyright 2023 Jakub Jelen <jjelen@redhat.com> + * + * 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 "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "libssh/misc.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_key pkey = NULL; + char *filename = NULL; + int fd; + int rc; + ssize_t sz; + + ssh_init(); + + filename = strdup("/tmp/libssh_pubkey_XXXXXX"); + if (filename == NULL) { + return -1; + } + fd = mkstemp(filename); + if (fd == -1) { + free(filename); + close(fd); + return -1; + } + sz = ssh_writen(fd, data, size); + close(fd); + if (sz == SSH_ERROR) { + unlink(filename); + free(filename); + return -1; + } + + rc = ssh_pki_import_pubkey_file(filename, &pkey); + if (rc != SSH_OK) { + unlink(filename); + free(filename); + return 1; + } + ssh_key_free(pkey); + unlink(filename); + free(filename); + + ssh_finalize(); + + return 0; +} + diff --git a/tests/fuzz/ssh_pubkey_fuzzer_corpus/b2c9f01394a2835b2cd7c520395a4977143e8d23 b/tests/fuzz/ssh_pubkey_fuzzer_corpus/b2c9f01394a2835b2cd7c520395a4977143e8d23 new file mode 100644 index 00000000..accd5b65 --- /dev/null +++ b/tests/fuzz/ssh_pubkey_fuzzer_corpus/b2c9f01394a2835b2cd7c520395a4977143e8d23 @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIujq/HWVfplmh7TmVZOS7CslRk3RPyER6vlQX0o+E5W jjelen@t470s.jjelen.redhat.com diff --git a/tests/fuzz/ssh_server_fuzzer.cpp b/tests/fuzz/ssh_server_fuzzer.c index 26498df6..aa84b8d2 100644 --- a/tests/fuzz/ssh_server_fuzzer.cpp +++ b/tests/fuzz/ssh_server_fuzzer.c @@ -24,6 +24,7 @@ #include <stdio.h> #include <sys/socket.h> #include <unistd.h> +#include <stdbool.h> #define LIBSSH_STATIC 1 #include <libssh/libssh.h> @@ -117,7 +118,7 @@ static int write_rsa_hostkey(const char *rsakey_path) return 0; } -extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { int socket_fds[2] = {-1, -1}; ssize_t nwritten; @@ -138,6 +139,14 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) .channel_open_request_session_function = channel_open, }; + /* This is the maximum that can be handled by the socket buffer before the + * other side will read some data. Other option would be feeding the socket + * from different thread which would not mind if it would be blocked, but I + * believe all the important inputs should fit into this size */ + if (size > 219264) { + return -1; + } + /* Write SSH RSA host key to disk */ rc = write_rsa_hostkey("/tmp/libssh_fuzzer_private_key"); assert(rc == 0); @@ -168,8 +177,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) assert(rc == 0); } rc = ssh_bind_options_set(sshbind, - SSH_BIND_OPTIONS_RSAKEY, - "/tmp/libssh_fuzzer_private_key"); + SSH_BIND_OPTIONS_HOSTKEY, + "/tmp/libssh_fuzzer_private_key"); assert(rc == 0); rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_CIPHERS_C_S, "none"); assert(rc == 0); @@ -185,6 +194,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) ssh_set_auth_methods(session, SSH_AUTH_METHOD_NONE); ssh_callbacks_init(&server_cb); + ssh_set_server_callbacks(session, &server_cb); rc = ssh_bind_accept_fd(sshbind, session, socket_fds[0]); assert(rc == SSH_OK); @@ -196,7 +206,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) ssh_event_add_session(event, session); size_t n = 0; - while(sdata.authenticated == false || sdata.channel == NULL) { + while (sdata.authenticated == false || sdata.channel == NULL) { if (sdata.auth_attempts >= 3 || n >= 100) { break; } diff --git a/tests/keys/certauth/id_rsa b/tests/keys/certauth/id_rsa index aa86ac2b..a0b679c2 100644 --- a/tests/keys/certauth/id_rsa +++ b/tests/keys/certauth/id_rsa @@ -1,27 +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----- +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEA0DehrU/ohoimMKojFXdo1uEAcqx4fS87AjDUz8t4s436ppP+0+U3 ++qrhOCE/mXZvXewjTHltmEtCHNSbsWhYTjwrEQUDRNOVahn2PEQTcX/9itvsv9PX79Imbv +ZsLR0f1FsmorkWpnDQmuga7hYBFBj4sV+VML5ieoK2OraUEq46ILsxqRgUTetkErlzX7S2 +SPE7vNM0ahmA+HNBuKNUD+BOtCzkqN54flGA9TZ7kapC7xqiRHK+ZzahQ2PFR4BxbVP1uT +DsanbjKOpBC4hISao3hi4iUnyj0gJ8itmkhQS+oI/2KWSGW01/k9W7jOUXDSt7LGUTSW6s +ILYHzmefCwAAA9B/6IFvf+iBbwAAAAdzc2gtcnNhAAABAQDQN6GtT+iGiKYwqiMVd2jW4Q +ByrHh9LzsCMNTPy3izjfqmk/7T5Tf6quE4IT+Zdm9d7CNMeW2YS0Ic1JuxaFhOPCsRBQNE +05VqGfY8RBNxf/2K2+y/09fv0iZu9mwtHR/UWyaiuRamcNCa6BruFgEUGPixX5UwvmJ6gr +Y6tpQSrjoguzGpGBRN62QSuXNftLZI8Tu80zRqGYD4c0G4o1QP4E60LOSo3nh+UYD1NnuR +qkLvGqJEcr5nNqFDY8VHgHFtU/W5MOxqduMo6kELiEhJqjeGLiJSfKPSAnyK2aSFBL6gj/ +YpZIZbTX+T1buM5RcNK3ssZRNJbqwgtgfOZ58LAAAAAwEAAQAAAQEAxjzxFU0LGWtortSN +apaxnkPCZWuHm8gn6kILm3shg/IdPhORfrSxw1qF6ybcooN8LHPyd5D0oxaj70cMpK+vw2 +zNo/qdzh2UF9x375Dw4hL1lgslMM3EvXPbW7IJ9DnSYCAYfLyzr+ug8JsjaKJSjIvp2xYh +uLLKl9FzJhtGhzDaJr9FCbSmd5R7Telz4En0Lwo/VxYvyzCoRwzhVUVqJZpdtF7/1du4tT +agfPzPYY9zM9muR7AawtzMc4UFvMzl1OtjOHYtqSMVBZx44fRpXT3/fy7A98+7erd0zTWj +s6gaz6I8VmRPk4iTdBH4KBzC8dGZQNMrY9SQ/ZANet8eYQAAAIBI8hS6bX00NpNXwOaEqr +jZKf/u1W71KHpYBAY1w3xanGqPVOX5PEsFH6NLjqSLF75Bk22pvJvZ8EAaoISyvLSqqO5t +1vvjCjVKALSaVIFDcA20NpCXRugmVT1HeQNKHCTt3yOoraL8Sh9wlRbxLmlxgISYS6uH6e +dEPa6qFshMVwAAAIEA7RFx7+mZJfrSJUu9pYiZJc6+Ns2WrSA2mgI+mIhWqreDK4Kw0a5g +akqD0mb9oPHySnf3lCe+17yqNxH2fcX0G3B5LxiRnFVNm2wC4ZGvb+yMU2+0uSI/Sf8L1N +sfWm+z4VC93Qhe0fIpuk8JAMNOwCvFcEFu5rr9sxHtPWjWtj0AAACBAODYXjs6jqsvW+3P +e0efFp9kIezi8CejSxVXX0/zmWMCpTw1laiUmK41coKTKBSDZcNgrsF/ns1uCrPg6C2u/y +evF8J+DeqU3vo1QhnRAJA2fLZk1Dr/GfAsp9mS9w6FdIQiQjQ0f/X9rYgAr9x4qMogYgrG +zkb7k6FUoGgD7sbnAAAAE2xpYnNzaF90b3J0dXJlX2F1dGgBAgMEBQYH +-----END OPENSSH PRIVATE KEY----- diff --git a/tests/keys/certauth/id_rsa-cert.pub b/tests/keys/certauth/id_rsa-cert.pub index 615d39f0..ceb44039 100644 --- a/tests/keys/certauth/id_rsa-cert.pub +++ b/tests/keys/certauth/id_rsa-cert.pub @@ -1 +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 +ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgllK1Wz9hM1kUks5QXU/vXbDmzpQWMtFWObMvi9ymg38AAAADAQABAAABAQDQN6GtT+iGiKYwqiMVd2jW4QByrHh9LzsCMNTPy3izjfqmk/7T5Tf6quE4IT+Zdm9d7CNMeW2YS0Ic1JuxaFhOPCsRBQNE05VqGfY8RBNxf/2K2+y/09fv0iZu9mwtHR/UWyaiuRamcNCa6BruFgEUGPixX5UwvmJ6grY6tpQSrjoguzGpGBRN62QSuXNftLZI8Tu80zRqGYD4c0G4o1QP4E60LOSo3nh+UYD1NnuRqkLvGqJEcr5nNqFDY8VHgHFtU/W5MOxqduMo6kELiEhJqjeGLiJSfKPSAnyK2aSFBL6gj/YpZIZbTX+T1buM5RcNK3ssZRNJbqwgtgfOZ58LAAAAAAAAAAAAAAABAAAAE3RvcnR1cmVfYXV0aF9jYXJsb3MAAAAJAAAABWFsaWNlAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQCnA2n5vHzZbs/GvRkGloJNV1CXHIS5Xnrm05HusUJSWyPq3I1iCMHdYA7oezHa9GCFYbIenaYPy+G6USQRjYQz8SvAZo06SFNeJSsa1kAIqxzdPT9kBrRrYK39PZQPsYVfRPqZBdmc+jwrfz97IFEJyXMI47FoTGkgEq7eu3z2px/tdIZ34I5Hr5DDBxicZi4jluyRUJHfSPoBxyhF7OkPX4bYkrc691jeIQDxubl650WYLHgFfad0xTzBIFE6XUb55Dp5AgRdevSoso1Pe0IKFxxMVpP664LCbYK06Lv6kcotfFlpvUtR1yx8jToGcSoq5sSzTwvXSHCQQ9ZA1hvFAAABFAAAAAxyc2Etc2hhMi01MTIAAAEAeI9eUAWyL5DVWBj0vU2kKdGxMXQx1Y8R68DXwXhnxfLwilJPa6IFg+g988lpF5aZzvAiX6TgDtJAhzfuBU+ZREGfdclUQIpz3xwDG76Gmg/DpQHdmqU76n2Na32s6+4SsSmWWKx6cPPdjbCRS0VMSrMohLuDyPGMoC7RjLfDDxqW5TIbMtqQdOiPl/0PpR73Q0FjB50Ec12buQDkExlEOi2Y+yB830vuTJN3ds7bx6NXM1Jjftg/8D0SzNRAIYDQFnpyXKO6kNrN66o48E3mrnVHXuFBTf+kpdYrK+1LKQVk/hVLBHr+NuVmQltL0zcjJfiXj7i5ZqcsqR1UAU5hKg== libssh_torture_auth diff --git a/tests/keys/certauth/id_rsa.pub b/tests/keys/certauth/id_rsa.pub index 4cbfc4ce..3f83c6fc 100644 --- a/tests/keys/certauth/id_rsa.pub +++ b/tests/keys/certauth/id_rsa.pub @@ -1 +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 +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQN6GtT+iGiKYwqiMVd2jW4QByrHh9LzsCMNTPy3izjfqmk/7T5Tf6quE4IT+Zdm9d7CNMeW2YS0Ic1JuxaFhOPCsRBQNE05VqGfY8RBNxf/2K2+y/09fv0iZu9mwtHR/UWyaiuRamcNCa6BruFgEUGPixX5UwvmJ6grY6tpQSrjoguzGpGBRN62QSuXNftLZI8Tu80zRqGYD4c0G4o1QP4E60LOSo3nh+UYD1NnuRqkLvGqJEcr5nNqFDY8VHgHFtU/W5MOxqduMo6kELiEhJqjeGLiJSfKPSAnyK2aSFBL6gj/YpZIZbTX+T1buM5RcNK3ssZRNJbqwgtgfOZ58L libssh_torture_auth diff --git a/tests/keys/id_rsa_protected b/tests/keys/id_rsa_protected index cdf5c2b8..034cb287 100644 --- a/tests/keys/id_rsa_protected +++ b/tests/keys/id_rsa_protected @@ -1,28 +1,30 @@ ------BEGIN OPENSSH PRIVATE KEY----- -b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBjmItEMS -YKDxy/7xvsZY+uAAAAEAAAAAEAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQCz98jP4bLz -1eNSFd5s2rauzUrREkRlcNt9yh9vXcRIMn19Jt35GUJQzqL5+gRVXbfFZ1qd2zYGSfva0a -Kclp0iA5ZT6SjGn6BGa0ksT842IAolCpErd44k0EfoC33o0yongbC/nobhbry4+APBRVDB -UhzoRzpHKmLPsMT5L76BK8FAhVRC3teQ9xc7I3nO6PmoOFkziXpXs6D0taPj/YgXlpy8qN -8gyl6qaen3PoFNhlC25BTpvVW4RiFfK8zouQzCd2xUaHjqQMoyZFCHIDwDqq8sCWIwyrzy -TmBHgB4l5OeoNH9DXbQjo8ypg2XpMtOTz8qic448NH9dcZveIXrvAAADwCLre52Jer2DTQ -TJi91b/xNm5NRuW9366ZdoOC5NdWtbQFk4YJmdImEDo8k1t3Re24rVNxLMQwHwZX4ZLISl -/e49RtSd6TDP44FkQF4NgtCjLUdmEWRTQj0mtENGto+wdLpL25HkmmI5WGrQU9SufVhhvj -TxKi6ediSXIXEA5bSrWNvUaw084TT3ZfP9g98/6wr9tAYL1jVfTFUabvZzCR6+wRVoJIVc -/+uN1bubj+IdOzYSm9Dhj4kUlK+KvI4GtouCzjuEZosjvn0ino3du1vgyT7SPdjmDxtIds -YI7YiB1Xy3QcWdWFk+SoXhDizf9pupo2r1+G50GoBuXg2ELdsKBLXtxQ9bh37JyAcLzagq -iVMCJjk3XMZvNXhdELRqLeWyhQ7U1BCtUBatbem0VsH6hQZ/pHReX2We8/GAUQkh4ZN8U2 -lkta9v5cb7XaBm49JjzIa3WeOS+tFHIUAWqd7MQ4f2FCTMhBssLAM7EJDOUXyo6938pa75 -+LvdLZRUycE8d/PWG9SuFWSe4CJJrRlBQqPEwx9OPtKNNKgsXIGVKAFLXe+nJ4z6RXTR3R -IGe0uaf8v9Jra5j22rq/dbQG1fP1fZNcCnIZQQo6olLaoyQmGCboC8CiCz1PNTsC1+r4pB -oaRiCx5/qLF6EXQ03mdEqL1L/R+KMDa2+Ncw2hCSRU3GBby4wXmSqFsboRy5uxJB5sK0Ut -sI3FW48k9zijiqVpdysRkalVVSQj8ymTG9LbjjEEmE7qxRf2dZCEnS/iPFUIu7iO9ISiOm -4ThpROBspNyHMXKFR6mKArJX1vIwjehlaLAXA3UMY9PEFRDrWQcbatGWj4f/L6e3Tq+n7a -t0djAgKlh40IvVL+Xf+Bsv8vUr7HAbKnOxpX69nEShiJqR5YWlEPXba+JCOjryE2ycoRB6 -d2d0SgDlB1M04uUmv2sy2Kw/CcSNHPLKGiYqqv8DAZ4GiKH2rI4oWvH9z2uRuQni98/Gw1 -1D5/QwJOHpqrUnVat4JXPBeTqiHYYtbTtqJLeIPX+Dsa6tbdjEOVgx2FkH3104xMwJyUKb -Ccip4AbWsTwfM4GVPnJE6WCBcXC5WR6AOzuEEDQjhyzLs5K7RVb7irfhHa4Vs1/2LvxnRT -dmTzdv/mhUNqS9RIPmFttfsSveDqY0P6WOn+K6FcCHQjpFJ3pK08glD+Sx4cbFv3lUQLfw -hsjL0P+p+M+gTqeJ1kb2z87fiS03mHMV15lmb7nzoqyeJLIukV1jidWdGxf0efnQfmUVfX -Wa+ehGaw== ------END OPENSSH PRIVATE KEY----- +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,0B181CC88F75C33B7DEBE5C18B481F18 + +rYtUw8FhEv48JmNTm3i1TEqEgElC629iaMQu/YzRV5zL/n83HwMKbRpAZ31Cch2a +8thZRQ6YsL/56vr+fKKVgDF7y3wmStp5sVkOQXMeZ9D746ZEGcYGnYH8JQhibDDB +sTK2kQrmBERg7H8rOoNCzxxoK+VZl2Z+S+yLVq0//qxBfuluZwCdk9Tie69Cd/Dk +PeBjOVPnFCavCKCWpUs/So8VQq9jXG84hRltwC0htSTEq+xfgNtS64f63WL6gEnB +WZ5KSO1gyzKC5/YAB6LXPxIIVzfZYXiuOWV/t8DXZK/lvhqQ3gSyPZezSrX8wEMC +xQeX41etQGjCcgxWH41iPCNTuoIKo2t/BPlfLJilJotmUSnYOxDmkZbLabuyS+0p +WGtnEwFSrxQosx6u9GBHX94Ikex0bf00KzNpKExzAIRqTdesaviJ1QX/pRsvT/Xp +TtH2aWV5kYNc+B+BrCQU7mlx/eEtXR2H5zJQxLSrTVKb1vUIHytufnPePk2BkcQ2 +CTE1xT+ZkUaY1WiCBxWgVTflL5FY9E6BerKEGVSfloso8tGCgsoO/Fch0Ho5/bXp +T+3nQEY780KduKJ8xCJJDQgD8GbjNR6sCtcPrewqEsgrpAbJUKyXhU7klGC09zzI +/JnNmdd10w2l/5A92GGrCgXnTYb8/w9J/qa6qyAAYU9/8rPo7ErGb7mKclmzz63j +cksImoExfrr9CIr7wjrXFO0OoupmMegNOZtgwsN7i0FI8vWYc6a3IaFWSWfE29Ux +rw9TK9L9pDvhCqS/WjW86S25muqnTSMQ/bhmiPw8z8tOjdi2YRqNcU2TyWoB2Mct +W+w9G5dSukMwkXQ2RNjDo2GfuXLXpUe5zCVixI2wxYGvIqTGkDZn/u1Jdxy1IxNc +qEsEZAOCVnJU1cQpB9ENsyrRUIsdQVWNQSvsUZz2XSELULwIFTcCTHr2PAJ5xzZ6 +VQy3DGEpZf7+yGACoi8LY8f5Ve5C9NciyA4/C/uvOUd7PhAf4g41mKw8+bAr8NFt +ubeXTo0iI29FkmmebfM1sRBHvomGT7qYsHBW2pgqBrm3X9kFcQ9EFhr6S2ULMcIn +4iX1mbqvC0c1CUmZakkNg94FQp2zbUclAuDkg3BTA0gwbyudvx0ccBmzQ43/6AJ5 +xz1hrfusX5Vcjz6+i5WHJDK/mlUDwTV5GAhcmar9eEcFXJEosD+mrAalflz3Vc2X +5A9plGfKkaFdth8YUGjLr+O2O5ggkDpCMbjYo4HQ6/dslYvqvnavJYrRKrEZbtvj +8fR5E11tPrK1aKzPHO0VLKf4UHs57JNqicSlYGy78FSCPG4d17KQlFyzbXsfbsvp +9EQK4N2jwRNZAOHuTuoqQ8TNzDahdlmbBS2Akd3rVV9H1/eNeN3r6Demww+yixoy +uPhjofn0P28eH7Gqiyhh20QYYqG7aky9IYMPnIBtA1hJp9MtMa1m8aHGxxZrUigj +S62Q34JzA8A6Rwc2kTHRzXG2o6oQ3vCQfy0JGlmDlG2yofcn7YgrMCv+srTniuiA +YBnOeic5cllYnDB9bpF2kufJT6CigoxP18HIw+jhYabuOTHO67MYf2En+is8vlQS +-----END RSA PRIVATE KEY----- diff --git a/tests/keys/ssh_host_dsa_key b/tests/keys/ssh_host_dsa_key deleted file mode 100644 index 9ee8bd86..00000000 --- a/tests/keys/ssh_host_dsa_key +++ /dev/null @@ -1,12 +0,0 @@ ------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 deleted file mode 100644 index 5fcb413d..00000000 --- a/tests/keys/ssh_host_dsa_key.pub +++ /dev/null @@ -1 +0,0 @@ -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/pkcs11/setup-softhsm-tokens.sh b/tests/pkcs11/setup-softhsm-tokens.sh index 532c86a7..f61c5a67 100755 --- a/tests/pkcs11/setup-softhsm-tokens.sh +++ b/tests/pkcs11/setup-softhsm-tokens.sh @@ -5,8 +5,11 @@ TESTDIR=$1 PRIVKEY=$2 OBJNAME=$3 +TOKENLABEL=$3 # yeah. The same as object label LOADPUBLIC=$4 -shift 4 +LIBSOFTHSM_PATH=$5 +P11_KIT_CLIENT=$6 +shift 5 PUBKEY="$PRIVKEY.pub" @@ -14,26 +17,29 @@ echo "TESTDIR: $TESTDIR" echo "PRIVKEY: $PRIVKEY" echo "PUBKEY: $PUBKEY" echo "OBJNAME: $OBJNAME" +echo "TOKENLABEL: $TOKENLABEL" echo "LOADPUBLIC: $LOADPUBLIC" -# Create temporary directory for tokens -install -d -m 0755 $TESTDIR/db +if [ ! -d "$TESTDIR/db" ]; then + # Create temporary directory for tokens + install -d -m 0755 "$TESTDIR/db" -# Create SoftHSM configuration file -cat >$TESTDIR/softhsm.conf <<EOF + # Create SoftHSM configuration file + cat >"$TESTDIR/softhsm.conf" <<EOF directories.tokendir = $TESTDIR/db objectstore.backend = file log.level = DEBUG EOF -export SOFTHSM2_CONF=$TESTDIR/softhsm.conf + cat "$TESTDIR/softhsm.conf" +fi -cat $TESTDIR/softhsm.conf +export SOFTHSM2_CONF=$TESTDIR/softhsm.conf -#init -cmd='softhsm2-util --init-token --label "$OBJNAME" --free --pin 1234 --so-pin 1234' +#init -- each object will have its own token +cmd="softhsm2-util --init-token --label $TOKENLABEL --free --pin 1234 --so-pin 1234" eval echo "$cmd" -out=$(eval $cmd) +out=$(eval "$cmd") ret=$? if [ $ret -ne 0 ]; then echo "Init token failed" @@ -42,9 +48,9 @@ if [ $ret -ne 0 ]; then fi #load private key -cmd='p11tool --provider /usr/lib64/pkcs11/libsofthsm2.so --write --load-privkey "$PRIVKEY" --label "$OBJNAME" --login --set-pin=1234 "pkcs11:token="$OBJNAME""' +cmd="p11tool --provider $LIBSOFTHSM_PATH --write --load-privkey $PRIVKEY --label $OBJNAME --login --set-pin=1234 \"pkcs11:token=$TOKENLABEL\"" eval echo "$cmd" -out=$(eval $cmd) +out=$(eval "$cmd") ret=$? if [ $ret -ne 0 ]; then echo "Loading privkey failed" @@ -52,15 +58,15 @@ if [ $ret -ne 0 ]; then exit 1 fi -cat $PUBKEY +cat "$PUBKEY" -ls -l $TESTDIR +ls -l "$TESTDIR" -if [ $LOADPUBLIC -ne 0 ]; then +if [ "$LOADPUBLIC" -ne 0 ]; then #load public key - cmd='p11tool --provider /usr/lib64/pkcs11/libsofthsm2.so --write --load-pubkey "$PUBKEY" --label "$OBJNAME" --login --set-pin=1234 "pkcs11:token="$OBJNAME""' + cmd="p11tool --provider $LIBSOFTHSM_PATH --write --load-pubkey $PUBKEY --label $OBJNAME --login --set-pin=1234 \"pkcs11:token=$TOKENLABEL\"" eval echo "$cmd" - out=$(eval $cmd) + out=$(eval "$cmd") ret=$? if [ $ret -ne 0 ]; then echo "Loading pubkey failed" @@ -69,15 +75,66 @@ if [ $LOADPUBLIC -ne 0 ]; then fi fi -cmd='p11tool --list-all --login "pkcs11:token="$OBJNAME"" --set-pin=1234' +cmd="p11tool --list-all --login \"pkcs11:token=$TOKENLABEL\" --set-pin=1234" eval echo "$cmd" -out=$(eval $cmd) +out=$(eval "$cmd") +ret=$? +if [ $ret -ne 0 ]; then + echo "Logging in failed" + echo "$out" + exit 1 +fi +echo "$out" + +# Skip the p11-kit if not needed +if [ -z "$P11_KIT_CLIENT" ]; then + exit 0 +fi + +# when creating more keys, we need to restart the p11-kit +# so it can pick up the new keys +if [ -h "$TESTDIR/p11-kit-server.socket" ]; then + kill -9 "$(cat "$TESTDIR/p11-kit-server.pid")" + rm "$TESTDIR/p11-kit-server.socket" +fi + +# p11-kit complains if there is no runtime directory +if [ -z "$XDG_RUNTIME_DIR" ]; then + export XDG_RUNTIME_DIR=$PWD +fi + +# Start the p11-kit server +cmd="p11-kit server --provider $LIBSOFTHSM_PATH pkcs11:" +echo "$cmd" +out=$(eval "$cmd") ret=$? if [ $ret -ne 0 ]; then - echo "Loging failed" + echo "Starting p11-kit server failed" echo "$out" exit 1 fi +eval "$out" + +# Symlink the p11-kit-server socket to "known place" +P11_KIT_SERVER_ADDRESS_PATH=${P11_KIT_SERVER_ADDRESS:10} +cmd="ln -s $P11_KIT_SERVER_ADDRESS_PATH $TESTDIR/p11-kit-server.socket" +echo "$cmd" +out=$(eval "$cmd") + +# Save the PID for the C code to clean up +cmd="echo $P11_KIT_SERVER_PID > $TESTDIR/p11-kit-server.pid" +echo "$cmd" +out=$(eval "$cmd") + +cmd="pkcs11-tool -O --login --pin=1234 --module=$P11_KIT_CLIENT --token-label=$TOKENLABEL" +echo "$cmd" +out=$(eval "$cmd") +ret=$? echo "$out" +if [ $ret -ne 0 ]; then + echo "Failed to list keys through p11-kit remoting" + echo "$out" + exit 1 +fi exit 0 diff --git a/tests/pkd/CMakeLists.txt b/tests/pkd/CMakeLists.txt index 9e550462..f5a62653 100644 --- a/tests/pkd/CMakeLists.txt +++ b/tests/pkd/CMakeLists.txt @@ -5,7 +5,6 @@ if (WITH_SERVER AND UNIX AND NOT WIN32) include_directories(${libssh_SOURCE_DIR}/include ${libssh_BINARY_DIR}/include ${CMOCKA_INCLUDE_DIR} - ${ZLIB_INCLUDE_DIR} ${CMAKE_BINARY_DIR} ${libssh_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}) @@ -26,7 +25,10 @@ set(pkd_libs add_executable(pkd_hello ${pkd_hello_src}) target_compile_options(pkd_hello PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) -target_link_libraries(pkd_hello ${pkd_libs}) +target_link_libraries(pkd_hello PRIVATE ${pkd_libs}) +if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(pkd_hello) +endif (WITH_COVERAGE) # # pkd_hello_i1 runs only one iteration per algorithm combination for @@ -34,23 +36,22 @@ target_link_libraries(pkd_hello ${pkd_libs}) # 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) - +add_test(pkd_hello_i1 ${CMAKE_CURRENT_BINARY_DIR}/pkd_hello -e -o -i1 -w /tmp/pkd_socket_wrapper_XXXXXX -L pkd_scratch_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}") + set(PKD_ENVIRONMENT "LD_PRELOAD=${SOCKET_WRAPPER_LIBRARY};OPENSSL_ENABLE_SHA1_SIGNATURES=1") endif () message(STATUS "PKD_ENVIRONMENT=${PKD_ENVIRONMENT}") set_property(TEST pkd_hello_i1 PROPERTY ENVIRONMENT ${PKD_ENVIRONMENT}) +# +# 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 -L pkd_scratch_XXXXXX) +set_property(TEST pkd_hello_rekey PROPERTY ENVIRONMENT OPENSSL_ENABLE_SHA1_SIGNATURES=1) + endif (WITH_SERVER AND UNIX AND NOT WIN32) diff --git a/tests/pkd/pkd_daemon.c b/tests/pkd/pkd_daemon.c index 5325a5de..ac4b53b7 100644 --- a/tests/pkd/pkd_daemon.c +++ b/tests/pkd/pkd_daemon.c @@ -263,16 +263,10 @@ static int pkd_exec_hello(int fd, struct pkd_daemon_args *args) goto outclose; } - if (type == PKD_RSA) { - opts = SSH_BIND_OPTIONS_RSAKEY; - } else if (type == PKD_ED25519) { + if (type == PKD_RSA || + type == PKD_ED25519 || + type == PKD_ECDSA) { 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; @@ -589,7 +583,8 @@ void pkd_stop(struct pkd_result *out) { close(pkd_state.server_fd); rc = pthread_kill(ctx.tid, SIGUSR1); - assert_int_equal(rc, 0); + assert_int_not_equal(rc, EINVAL); + assert_int_not_equal(rc, ENOTSUP); rc = pthread_join(ctx.tid, NULL); assert_int_equal(rc, 0); diff --git a/tests/pkd/pkd_daemon.h b/tests/pkd/pkd_daemon.h index 493326c1..2745f11a 100644 --- a/tests/pkd/pkd_daemon.h +++ b/tests/pkd/pkd_daemon.h @@ -12,9 +12,6 @@ enum pkd_hostkey_type_e { PKD_RSA, -#ifdef HAVE_DSA - PKD_DSA, -#endif PKD_ED25519, PKD_ECDSA }; @@ -30,6 +27,8 @@ struct pkd_daemon_args { uint64_t rekey_data_limit; + int original_dir_fd; + struct { int list; @@ -42,8 +41,14 @@ struct pkd_daemon_args { unsigned int iterations; struct { + const char *argv_mkdtemp_str; char *mkdtemp_str; } socket_wrapper; + + struct { + const char *argv_mkdtemp_str; + char *mkdtemp_str; + } temp_dir; } opts; }; diff --git a/tests/pkd/pkd_hello.c b/tests/pkd/pkd_hello.c index 01b1b10d..069ed8df 100644 --- a/tests/pkd/pkd_hello.c +++ b/tests/pkd/pkd_hello.c @@ -1,15 +1,17 @@ /* * pkd_hello.c -- * - * (c) 2014, 2017-2018 Jon Simons <jon@jonsimons.org> + * (c) 2014, 2017-2018, 2022 Jon Simons <jon@jonsimons.org> */ #include "config.h" +#include <fcntl.h> #include <setjmp.h> // for cmocka #include <stdarg.h> // for cmocka +#include <stdint.h> // for cmocka #include <stdio.h> #include <stdlib.h> -#include <unistd.h> // for cmocka +#include <unistd.h> #include <cmocka.h> #include "libssh/priv.h" @@ -33,7 +35,7 @@ static size_t default_payload_len = sizeof(default_payload_buf); #include <argp.h> #define PROGNAME "pkd_hello" #define ARGP_PROGNAME "libssh " PROGNAME -const char *argp_program_version = ARGP_PROGNAME " 2017-07-12"; +const char *argp_program_version = ARGP_PROGNAME " 2022-11-12"; const char *argp_program_bug_address = "Jon Simons <jon@jonsimons.org>"; static char doc[] = \ @@ -61,6 +63,8 @@ static struct argp_option options[] = { "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 }, + { "temp-dir", 'L', "<mkdtemp-template>", 0, + "Run in a temporary directory using the given mkdtemp template", 0 }, { "socket-wrapper-dir", 'w', "<mkdtemp-template>", 0, "Run in socket-wrapper mode using the given mkdtemp directory template", 0 }, { "stdout", 'o', NULL, 0, @@ -90,6 +94,9 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) { case 'l': pkd_dargs.opts.list = 1; break; + case 'L': + pkd_dargs.opts.temp_dir.argv_mkdtemp_str = arg; + break; case 'i': pkd_dargs.opts.iterations = atoi(arg); break; @@ -109,7 +116,7 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) { pkd_dargs.opts.libssh_log_level += 1; break; case 'w': - pkd_dargs.opts.socket_wrapper.mkdtemp_str = arg; + pkd_dargs.opts.socket_wrapper.argv_mkdtemp_str = arg; break; default: return ARGP_ERR_UNKNOWN; @@ -177,15 +184,6 @@ static int torture_pkd_setup_ed25519(void **state) { 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); @@ -217,16 +215,9 @@ static int torture_pkd_setup_ecdsa_521(void **state) { 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. */ \ @@ -308,44 +299,7 @@ static int torture_pkd_setup_ecdsa_521(void **state) { 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) */ +#if defined(WITH_GEX) /* GEX_SHA256 is included in PKDTESTS_KEX_FIPS if available */ #define PKDTESTS_KEX(f, client, kexcmd) \ /* Kex algorithms. */ \ @@ -360,22 +314,6 @@ static int torture_pkd_setup_ecdsa_521(void **state) { 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) \ @@ -389,8 +327,8 @@ static int torture_pkd_setup_ecdsa_521(void **state) { 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 CHACHA20 "chacha20-poly1305@openssh.com" #define PKDTESTS_CIPHER_COMMON(f, client, ciphercmd) \ f(client, rsa_aes128_ctr, ciphercmd("aes128-ctr"), setup_rsa, teardown) \ @@ -413,19 +351,18 @@ static int torture_pkd_setup_ecdsa_521(void **state) { f(client, ecdsa_521_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_521, teardown) \ f(client, ecdsa_521_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_521, teardown) -#ifdef HAVE_DSA +#define PKDTESTS_CIPHER_CHACHA(f, client, ciphercmd) \ + f(client, rsa_chacha20, ciphercmd(CHACHA20), setup_rsa, teardown) \ + f(client, ed25519_chacha20, ciphercmd(CHACHA20), setup_ed25519, teardown) \ + f(client, ecdsa_256_chacha20, ciphercmd(CHACHA20), setup_ecdsa_256, teardown) \ + f(client, ecdsa_384_chacha20, ciphercmd(CHACHA20), setup_ecdsa_384, teardown) \ + f(client, ecdsa_521_chacha20, ciphercmd(CHACHA20), setup_ecdsa_521, teardown) + #define PKDTESTS_CIPHER(f, client, ciphercmd) \ /* Ciphers. */ \ PKDTESTS_CIPHER_COMMON(f, client, ciphercmd) \ - f(client, dsa_aes128_ctr, ciphercmd("aes128-ctr"), setup_dsa, teardown) \ - f(client, dsa_aes256_ctr, ciphercmd("aes256-ctr"), setup_dsa, teardown) -#else -#define PKDTESTS_CIPHER(f, client, ciphercmd) \ - /* Ciphers. */ \ - PKDTESTS_CIPHER_COMMON(f, client, ciphercmd) -#endif + PKDTESTS_CIPHER_CHACHA(f, client, ciphercmd) -#define CHACHA20 "chacha20-poly1305@openssh.com" #define AES128_GCM "aes128-gcm@openssh.com" #define AES256_GCM "aes256-gcm@openssh.com" @@ -439,7 +376,6 @@ static int torture_pkd_setup_ecdsa_521(void **state) { 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) \ @@ -448,15 +384,6 @@ static int torture_pkd_setup_ecdsa_521(void **state) { f(client, rsa_aes192_cbc, ciphercmd("aes192-cbc"), setup_rsa, teardown) \ f(client, rsa_aes256_cbc, ciphercmd("aes256-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_3des_cbc, ciphercmd("3des-cbc"), setup_dsa, teardown) \ - f(client, dsa_aes128_cbc, ciphercmd("aes128-cbc"), setup_dsa, teardown) \ - f(client, dsa_aes192_cbc, ciphercmd("aes192-cbc"), setup_dsa, teardown) \ - f(client, dsa_aes256_cbc, ciphercmd("aes256-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) \ @@ -464,7 +391,6 @@ static int torture_pkd_setup_ecdsa_521(void **state) { 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_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_256, teardown) \ @@ -472,56 +398,16 @@ static int torture_pkd_setup_ecdsa_521(void **state) { f(client, ecdsa_256_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_256, teardown) \ f(client, ecdsa_256_aes256_cbc, ciphercmd("aes256-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_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_aes256_cbc, ciphercmd("aes256-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_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_aes256_cbc, ciphercmd("aes256-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_3des_cbc, ciphercmd("3des-cbc"), setup_rsa, teardown) \ - f(client, rsa_aes128_cbc, ciphercmd("aes128-cbc"), setup_rsa, teardown) \ - f(client, rsa_aes192_cbc, ciphercmd("aes192-cbc"), setup_rsa, teardown) \ - f(client, rsa_aes256_cbc, ciphercmd("aes256-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_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_aes256_cbc, ciphercmd("aes256-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_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_384, teardown) \ f(client, ecdsa_384_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_384, teardown) \ f(client, ecdsa_384_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_384, teardown) \ f(client, ecdsa_384_aes256_cbc, ciphercmd("aes256-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_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_521, teardown) \ f(client, ecdsa_521_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_521, teardown) \ f(client, ecdsa_521_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_521, teardown) \ f(client, ecdsa_521_aes256_cbc, ciphercmd("aes256-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 + f(client, ecdsa_521_aes192_ctr, ciphercmd("aes192-ctr"), setup_ecdsa_521, teardown) #define PKDTESTS_MAC_FIPS(f, client, maccmd) \ @@ -552,25 +438,6 @@ static int torture_pkd_setup_ecdsa_521(void **state) { 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) @@ -582,7 +449,6 @@ static int torture_pkd_setup_ecdsa_521(void **state) { 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) \ @@ -638,21 +504,6 @@ static void torture_pkd_runtest(const char *testname, /* * 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) @@ -724,17 +575,6 @@ struct { 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) @@ -789,16 +629,6 @@ static int pkd_run_tests(void) { 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) @@ -830,6 +660,10 @@ static int pkd_run_tests(void) { PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_ed, OPENSSH_MAC_CMD) }; + /* It is not possible to test hostkey and kex algorithms, because + * dbclient does not support setting hostkey and kex algorithms + * through cli (see 'man dbclient') + */ const struct CMUnitTest dropbear_tests[] = { PKDTESTS_DEFAULT(emit_unit_test_comma, dropbear, DROPBEAR_CMD) PKDTESTS_CIPHER(emit_unit_test_comma, dropbear, DROPBEAR_CIPHER_CMD) @@ -942,23 +776,66 @@ static int pkd_run_tests(void) { cleanup_ecdsa_keys(); if (!ssh_fips_mode()) { cleanup_ed25519_key(); -#ifdef HAVE_DSA - cleanup_dsa_key(); -#endif } return rc; } +static int pkd_init_temp_dir(void) { + int rc = 0; + char *mkdtemp_str = NULL; + pkd_dargs.original_dir_fd = -1; + + if (pkd_dargs.opts.temp_dir.argv_mkdtemp_str == NULL) { + return 0; + } + + pkd_dargs.original_dir_fd = open(".", O_RDONLY); + if (pkd_dargs.original_dir_fd < 0) { + fprintf(stderr, "pkd_init_temp_dir open failed\n"); + return -1; + } + + mkdtemp_str = strdup(pkd_dargs.opts.temp_dir.argv_mkdtemp_str); + if (mkdtemp_str == NULL) { + fprintf(stderr, "pkd_init_temp_dir strdup failed\n"); + goto errstrdup; + } + pkd_dargs.opts.temp_dir.mkdtemp_str = mkdtemp_str; + + if (mkdtemp(mkdtemp_str) == NULL) { + fprintf(stderr, "pkd_init_temp_dir mkdtemp '%s' failed\n", mkdtemp_str); + goto errmkdtemp; + } + + rc = chdir(mkdtemp_str); + if (rc != 0) { + fprintf(stderr, "pkd_init_temp_dir chdir '%s' failed\n", mkdtemp_str); + goto errchdir; + } + + return 0; + +errchdir: + rmdir(mkdtemp_str); +errmkdtemp: + free(mkdtemp_str); +errstrdup: + close(pkd_dargs.original_dir_fd); + pkd_dargs.original_dir_fd = -1; + rc = -1; + 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) { + if (pkd_dargs.opts.socket_wrapper.argv_mkdtemp_str == NULL) { goto out; } - mkdtemp_str = strdup(pkd_dargs.opts.socket_wrapper.mkdtemp_str); + mkdtemp_str = strdup(pkd_dargs.opts.socket_wrapper.argv_mkdtemp_str); if (mkdtemp_str == NULL) { fprintf(stderr, "pkd_init_socket_wrapper strdup failed\n"); goto errstrdup; @@ -991,6 +868,33 @@ static int pkd_rmfiles(const char *path) { return system_checked(bin); } +static int pkd_cleanup_temp_dir(void) { + int rc = 0; + + if (pkd_dargs.opts.temp_dir.mkdtemp_str == NULL) { + return 0; + } + + if (fchdir(pkd_dargs.original_dir_fd) != 0) { + fprintf(stderr, "pkd_cleanup_temp_dir failed fchdir\n"); + rc = -1; + goto out; + } + + if (rmdir(pkd_dargs.opts.temp_dir.mkdtemp_str) != 0) { + fprintf(stderr, "pkd_cleanup_temp_dir rmdir '%s' failed\n", + pkd_dargs.opts.temp_dir.mkdtemp_str); + rc = -1; + goto out; + } + +out: + close(pkd_dargs.original_dir_fd); + pkd_dargs.original_dir_fd = -1; + free(pkd_dargs.opts.temp_dir.mkdtemp_str); + return rc; +} + static int pkd_cleanup_socket_wrapper(void) { int rc = 0; @@ -1001,22 +905,22 @@ static int pkd_cleanup_socket_wrapper(void) { /* 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); + 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); + pkd_dargs.opts.socket_wrapper.mkdtemp_str); goto errrmdir; } - free(pkd_dargs.opts.socket_wrapper.mkdtemp_str); - - goto out; + goto outfree; errrmdir: errrmfiles: rc = -1; +outfree: + free(pkd_dargs.opts.socket_wrapper.mkdtemp_str); out: return rc; } @@ -1042,10 +946,16 @@ int main(int argc, char **argv) { (void) argc; (void) argv; #endif /* HAVE_ARGP_H */ + rc = pkd_init_temp_dir(); + if (rc != 0) { + fprintf(stderr, "pkd_init_temp_dir failed: %d\n", rc); + goto out_finalize; + } + rc = pkd_init_socket_wrapper(); if (rc != 0) { fprintf(stderr, "pkd_init_socket_wrapper failed: %d\n", rc); - goto out_finalize; + goto out_tempdir; } if (pkd_dargs.opts.list != 0) { @@ -1064,6 +974,12 @@ int main(int argc, char **argv) { fprintf(stderr, "pkd_cleanup_socket_wrapper failed: %d\n", rc); } +out_tempdir: + rc = pkd_cleanup_temp_dir(); + if (rc != 0) { + fprintf(stderr, "pkd_cleanup_temp_dir failed: %d\n", rc); + } + out_finalize: rc = ssh_finalize(); if (rc != 0) { diff --git a/tests/pkd/pkd_keyutil.c b/tests/pkd/pkd_keyutil.c index 3991bcbb..daaab134 100644 --- a/tests/pkd/pkd_keyutil.c +++ b/tests/pkd/pkd_keyutil.c @@ -8,6 +8,7 @@ #include <setjmp.h> // for cmocka #include <stdarg.h> // for cmocka +#include <stdint.h> // for cmocka #include <unistd.h> // for cmocka #include <cmocka.h> @@ -22,7 +23,7 @@ #include "pkd_keyutil.h" #include "pkd_util.h" -void setup_rsa_key() { +void setup_rsa_key(void) { int rc = 0; if (access(LIBSSH_RSA_TESTKEY, F_OK) != 0) { rc = system_checked(OPENSSH_KEYGEN " -t rsa -q -N \"\" -f " @@ -31,7 +32,7 @@ void setup_rsa_key() { assert_int_equal(rc, 0); } -void setup_ed25519_key() { +void setup_ed25519_key(void) { int rc = 0; if (access(LIBSSH_ED25519_TESTKEY, F_OK) != 0) { rc = system_checked(OPENSSH_KEYGEN " -t ed25519 -q -N \"\" -f " @@ -40,18 +41,7 @@ void setup_ed25519_key() { 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() { +void setup_ecdsa_keys(void) { int rc = 0; if (access(LIBSSH_ECDSA_256_TESTKEY, F_OK) != 0) { @@ -71,27 +61,21 @@ void setup_ecdsa_keys() { } } -void cleanup_rsa_key() { +void cleanup_rsa_key(void) { cleanup_key(LIBSSH_RSA_TESTKEY); } -void cleanup_ed25519_key() { +void cleanup_ed25519_key(void) { cleanup_key(LIBSSH_ED25519_TESTKEY); } -#ifdef HAVE_DSA -void cleanup_dsa_key() { - cleanup_key(LIBSSH_DSA_TESTKEY); -} -#endif - -void cleanup_ecdsa_keys() { +void cleanup_ecdsa_keys(void) { cleanup_key(LIBSSH_ECDSA_256_TESTKEY); cleanup_key(LIBSSH_ECDSA_384_TESTKEY); cleanup_key(LIBSSH_ECDSA_521_TESTKEY); } -void setup_openssh_client_keys() { +void setup_openssh_client_keys(void) { int rc = 0; if (access(OPENSSH_CA_TESTKEY, F_OK) != 0) { @@ -156,19 +140,6 @@ void setup_openssh_client_keys() { 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 " @@ -184,7 +155,7 @@ void setup_openssh_client_keys() { } } -void cleanup_openssh_client_keys() { +void cleanup_openssh_client_keys(void) { cleanup_key(OPENSSH_CA_TESTKEY); cleanup_key(OPENSSH_RSA_TESTKEY); cleanup_file(OPENSSH_RSA_TESTKEY "-sha256-cert.pub"); @@ -193,13 +164,10 @@ void cleanup_openssh_client_keys() { 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() { +void setup_dropbear_client_rsa_key(void) { int rc = 0; if (access(DROPBEAR_RSA_TESTKEY, F_OK) != 0) { rc = system_checked(DROPBEAR_KEYGEN " -t rsa -f " @@ -208,6 +176,6 @@ void setup_dropbear_client_rsa_key() { assert_int_equal(rc, 0); } -void cleanup_dropbear_client_rsa_key() { +void cleanup_dropbear_client_rsa_key(void) { unlink(DROPBEAR_RSA_TESTKEY); } diff --git a/tests/pkd/pkd_keyutil.h b/tests/pkd/pkd_keyutil.h index 7b189040..8e8f50ae 100644 --- a/tests/pkd/pkd_keyutil.h +++ b/tests/pkd/pkd_keyutil.h @@ -10,32 +10,20 @@ #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" diff --git a/tests/pkd/pkd_util.c b/tests/pkd/pkd_util.c index 0e3b19b4..e8e6fbb7 100644 --- a/tests/pkd/pkd_util.c +++ b/tests/pkd/pkd_util.c @@ -81,6 +81,7 @@ static int is_openssh_client_new_enough(void) { ((major < 1) || (major > 100))) { fprintf(stderr, "failed to parse OpenSSH client version, " "errno %d\n", errno); + errno = 0; goto errversion; } diff --git a/tests/server/CMakeLists.txt b/tests/server/CMakeLists.txt index 96457cd2..f741e95a 100644 --- a/tests/server/CMakeLists.txt +++ b/tests/server/CMakeLists.txt @@ -11,6 +11,7 @@ set(LIBSSH_SERVER_TESTS torture_server_auth_kbdint torture_server_config torture_server_algorithms + torture_sftpserver ) include_directories(${libssh_SOURCE_DIR}/include diff --git a/tests/server/test_server/CMakeLists.txt b/tests/server/test_server/CMakeLists.txt index fe5e7d09..7e0f88c1 100644 --- a/tests/server/test_server/CMakeLists.txt +++ b/tests/server/test_server/CMakeLists.txt @@ -10,7 +10,11 @@ set(server_SRCS add_library(testserver STATIC test_server.c - default_cb.c) + default_cb.c + sftpserver_cb.c) +if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(testserver) +endif (WITH_COVERAGE) set(LIBSSH_SERVER_TESTS # torture_server_kbdint @@ -28,10 +32,10 @@ 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) + PRIVATE testserver ssh::ssh ${ARGP_LIBRARIES} util) + if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(test_server) + endif (WITH_COVERAGE) 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 index 03c34190..a4ac81b0 100644 --- a/tests/server/test_server/default_cb.c +++ b/tests/server/test_server/default_cb.c @@ -51,6 +51,40 @@ #include <util.h> #endif +int auth_none_cb(UNUSED_PARAM(ssh_session session), + const char *user, + void *userdata) +{ + struct session_data_st *sdata = NULL; + ssh_string banner = NULL; + + sdata = (struct session_data_st *)userdata; + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + goto denied; + } + + if (sdata->username == NULL) { + fprintf(stderr, "Error: expected username not set\n"); + goto denied; + } + + printf("None authentication of user %s\n", user); + + /* Send the banner */ + banner = ssh_string_from_char(SSHD_BANNER_MESSAGE); + if (banner == NULL) { + goto denied; + } + if (ssh_send_issue_banner(session, banner) == SSH_ERROR) { + fprintf(stderr, "Error: Failed to send the banner.\n"); + goto denied; + } +denied: + ssh_string_free(banner); + return SSH_AUTH_DENIED; +} + int auth_pubkey_cb(UNUSED_PARAM(ssh_session session), const char *user, UNUSED_PARAM(struct ssh_key_struct *pubkey), @@ -76,7 +110,7 @@ int auth_pubkey_cb(UNUSED_PARAM(ssh_session session), } /* TODO */ - /* Check wheter the user and public key are in authorized keys list */ + /* Check whether the user and public key are in authorized keys list */ /* Authenticated */ printf("Authenticated\n"); @@ -743,6 +777,7 @@ struct ssh_server_callbacks_struct *get_default_server_cb(void) goto end; } + cb->auth_none_function = auth_none_cb; cb->auth_password_function = auth_password_cb; cb->auth_pubkey_function = auth_pubkey_cb; cb->channel_open_request_session_function = channel_new_session_cb; @@ -851,7 +886,6 @@ void default_handle_session_cb(ssh_event event, } sdata.server_state = (void *)state; - cdata.server_state = (void *)state; #ifdef WITH_PCAP set_pcap(&sdata, session, state->pcap_file); @@ -867,7 +901,7 @@ void default_handle_session_cb(ssh_event event, if (ssh_handle_key_exchange(session) != SSH_OK) { fprintf(stderr, "%s\n", ssh_get_error(session)); - return; + goto end; } /* Set the supported authentication methods */ @@ -886,7 +920,7 @@ void default_handle_session_cb(ssh_event event, /* 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; + goto end; } if (ssh_event_dopoll(event, 100) == SSH_ERROR) { diff --git a/tests/server/test_server/default_cb.h b/tests/server/test_server/default_cb.h index 487794c0..529c71e1 100644 --- a/tests/server/test_server/default_cb.h +++ b/tests/server/test_server/default_cb.h @@ -24,6 +24,7 @@ #include "config.h" #include <libssh/libssh.h> +#include <libssh/sftp.h> #include <libssh/callbacks.h> #define SSHD_DEFAULT_USER "libssh" @@ -32,6 +33,8 @@ #define SSHD_DEFAULT_ADDRESS "127.0.0.1" #define SSHD_DEFAULT_PCAP_FILE "debug.server.pcap" +#define SSHD_BANNER_MESSAGE "Test Banner Message\nlibssh-send-banner\n" + #ifndef KEYS_FOLDER #ifdef _WIN32 #define KEYS_FOLDER @@ -68,6 +71,7 @@ struct channel_data_st { void *server_state; /* This pointer is useful to set data for custom callbacks */ void *extra_data; + sftp_session sftp; }; /* A userdata struct for session. */ diff --git a/tests/server/test_server/main.c b/tests/server/test_server/main.c index b4676b4b..39c01223 100644 --- a/tests/server/test_server/main.c +++ b/tests/server/test_server/main.c @@ -45,7 +45,6 @@ struct arguments_st { char *port; char *ecdsa_key; - char *dsa_key; char *ed25519_key; char *rsa_key; char *host_key; @@ -74,7 +73,6 @@ static void free_arguments(struct arguments_st *arguments) 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); @@ -153,8 +151,6 @@ static void print_server_state(struct server_state_st *state) 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", @@ -218,13 +214,6 @@ static int init_server_state(struct server_state_st *state, 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; @@ -364,14 +353,6 @@ static struct argp_option options[] = { .group = 0 }, { - .name = "dsakey", - .key = 'd', - .arg = "FILE", - .flags = 0, - .doc = "Set the DSA key.", - .group = 0 - }, - { .name = "ed25519key", .key = 'e', .arg = "FILE", @@ -486,14 +467,6 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) 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) { @@ -623,9 +596,12 @@ int main(UNUSED_PARAM(int argc), UNUSED_PARAM(char **argv)) .address = NULL, .with_global_config = true, }; - struct server_state_st state = { - .address = NULL, - }; + struct server_state_st *state = calloc(1, sizeof(struct server_state_st)); + + if (state == NULL) { + printf("Failed to allocate memory\n"); + return -1; + } #ifdef HAVE_ARGP_H argp_parse (&argp, argc, argv, 0, 0, &arguments); @@ -635,6 +611,8 @@ int main(UNUSED_PARAM(int argc), UNUSED_PARAM(char **argv)) pid_file = fopen(arguments.pid_file, "w"); if (pid_file == NULL) { rc = -1; + free_server_state(state); + SAFE_FREE(state); goto free_arguments; } pid = getpid(); @@ -643,22 +621,19 @@ int main(UNUSED_PARAM(int argc), UNUSED_PARAM(char **argv)) } /* Initialize the state using default or given parameters */ - rc = init_server_state(&state, &arguments); + rc = init_server_state(state, &arguments); if (rc != 0) { + free_server_state(state); + SAFE_FREE(state); 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; - } + /* Run the server: Frees the state in all processes */ + rc = run_server(state); -free_state: - free_server_state(&state); free_arguments: free_arguments(&arguments); return rc; diff --git a/tests/server/test_server/sftpserver_cb.c b/tests/server/test_server/sftpserver_cb.c new file mode 100644 index 00000000..6c58b262 --- /dev/null +++ b/tests/server/test_server/sftpserver_cb.c @@ -0,0 +1,403 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki <ansasaki@redhat.com> + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "test_server.h" +#include "default_cb.h" + +#include <libssh/callbacks.h> +#include <libssh/server.h> +#include <libssh/priv.h> +#include <libssh/sftp.h> +#include <libssh/sftpserver.h> + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stdbool.h> +#include <poll.h> +#include <signal.h> +#include <sys/wait.h> +#include <sys/ioctl.h> +#include <sys/stat.h> + +#ifdef HAVE_LIBUTIL_H +#include <libutil.h> +#endif +#ifdef HAVE_PTY_H +#include <pty.h> +#endif +#ifdef HAVE_UTMP_H +#include <utmp.h> +#endif +#ifdef HAVE_UTIL_H +#include <util.h> +#endif + +/* below are for sftp */ +#include <sys/types.h> +#include <sys/statvfs.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <fcntl.h> +#include <unistd.h> +#include <dirent.h> +#include <time.h> + + +#define BUF_SIZE 1048576 +#define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) + + +/* TODO implement proper pam authentication cb */ +static int sftp_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; +} + +static ssh_channel sftp_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 + + +/* 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_sftp_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 = sftp_auth_password_cb; + cb->channel_open_request_session_function = sftp_channel_new_session_cb; + +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_sftp_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_data_function = sftp_channel_default_data_callback; + cb->channel_subsystem_request_function = sftp_channel_default_subsystem_request; + +end: + return cb; +}; + +void sftp_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 = { + .event = NULL, + .winsize = &wsize, + .sftp = NULL + }; + + /* 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_sftp_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; + +#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)); + goto end; + } + + /* 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) { + goto end; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + fprintf(stderr, "do_poll error: %s\n", ssh_get_error(session)); + goto end; + } + 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_sftp_channel_cb(); + if (channel_cb == NULL) { + goto end; + } + + channel_cb->userdata = &(cdata.sftp); + + 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) { + continue; + } + + } while (ssh_channel_is_open(sdata.channel)); + + ssh_channel_send_eof(sdata.channel); + ssh_channel_close(sdata.channel); + sftp_server_free(cdata.sftp); + + /* 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/test_server.c b/tests/server/test_server/test_server.c index 42d21495..6d0f0808 100644 --- a/tests/server/test_server/test_server.c +++ b/tests/server/test_server/test_server.c @@ -40,13 +40,12 @@ void free_server_state(struct server_state_st *state) { if (state == NULL) { - goto end; + return; } 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); @@ -56,9 +55,7 @@ void free_server_state(struct server_state_st *state) SAFE_FREE(state->expected_username); SAFE_FREE(state->expected_password); SAFE_FREE(state->config_file); - -end: - return; + SAFE_FREE(state->log_file); } /* SIGCHLD handler for cleaning up dead children. */ @@ -67,6 +64,15 @@ static void sigchld_handler(int signo) { while (waitpid(-1, NULL, WNOHANG) > 0); } +bool done = false; + +static void sigterm_handler(int signo) +{ + (void) signo; + fprintf(stderr, "Received SIGTERM. Gracefully exiting ...\n"); + done = true; +} + int run_server(struct server_state_st *state) { ssh_session session = NULL; @@ -77,12 +83,12 @@ int run_server(struct server_state_st *state) .sa_flags = 0 }; - int rc; + int rc = SSH_ERROR; /* Check provided state */ if (state == NULL) { fprintf(stderr, "Invalid state\n"); - return SSH_ERROR; + goto out; } /* Set up SIGCHLD handler. */ @@ -92,23 +98,57 @@ int run_server(struct server_state_st *state) if (sigaction(SIGCHLD, &sa, NULL) != 0) { fprintf(stderr, "Failed to register SIGCHLD handler\n"); - return SSH_ERROR; + goto out; } - if (state->address == NULL) { - fprintf(stderr, "Missing bind address\n"); - return SSH_ERROR; + /* Set up SIGTERM handler. */ + sa.sa_handler = sigterm_handler; + sa.sa_flags = 0; + + if (sigaction(SIGTERM, &sa, NULL) != 0) { + fprintf(stderr, "Failed to register SIGTERM handler\n"); + goto out; + } + + /* Redirect all the output and errors to the file to avoid mixing up with + * the output from the client */ + if (state->log_file != NULL) { + int fd; + FILE *f = fopen(state->log_file, "a"); + if (f == NULL) { + fprintf(stderr, "Failed to open the log file: %s\n", strerror(errno)); + goto out; + } + fd = dup2(fileno(f), STDERR_FILENO); + if (fd == -1) { + fprintf(stderr, "dup2 of log file to stderr failed: %s\n", + strerror(errno)); + goto out; + } + fd = dup2(fileno(f), STDOUT_FILENO); + if (fd == -1) { + fprintf(stderr, "dup2 of log file to stdout failed: %s\n", + strerror(errno)); + goto out; + } + fclose(f); } if (state->address == NULL) { fprintf(stderr, "Missing bind address\n"); - return SSH_ERROR; + goto out; + } + + if (state->host_key == NULL && state->rsa_key == NULL && + state->ecdsa_key == NULL && state->ed25519_key) { + fprintf(stderr, "Missing host key\n"); + goto out; } sshbind = ssh_bind_new(); if (sshbind == NULL) { fprintf(stderr, "Out of memory\n"); - return SSH_ERROR; + goto out; } if (state->verbosity) { @@ -119,7 +159,7 @@ int run_server(struct server_state_st *state) fprintf(stderr, "Error setting verbosity level: %s\n", ssh_get_error(sshbind)); - goto free_sshbind; + goto out; } } @@ -128,14 +168,14 @@ int run_server(struct server_state_st *state) SSH_BIND_OPTIONS_PROCESS_CONFIG, &(state->parse_global_config)); if (rc != 0) { - goto free_sshbind; + goto out; } } if (state->config_file) { rc = ssh_bind_options_parse_config(sshbind, state->config_file); if (rc != 0) { - goto free_sshbind; + goto out; } } @@ -146,7 +186,7 @@ int run_server(struct server_state_st *state) fprintf(stderr, "Error setting bind address: %s\n", ssh_get_error(sshbind)); - goto free_sshbind; + goto out; } rc = ssh_bind_options_set(sshbind, @@ -156,42 +196,30 @@ int run_server(struct server_state_st *state) 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; - } + goto out; } if (state->rsa_key != NULL) { rc = ssh_bind_options_set(sshbind, - SSH_BIND_OPTIONS_RSAKEY, + SSH_BIND_OPTIONS_HOSTKEY, state->rsa_key); if (rc != 0) { fprintf(stderr, "Error setting RSA key: %s\n", ssh_get_error(sshbind)); - goto free_sshbind; + goto out; } } if (state->ecdsa_key != NULL) { rc = ssh_bind_options_set(sshbind, - SSH_BIND_OPTIONS_ECDSAKEY, + SSH_BIND_OPTIONS_HOSTKEY, state->ecdsa_key); if (rc != 0) { fprintf(stderr, "Error setting ECDSA key: %s\n", ssh_get_error(sshbind)); - goto free_sshbind; + goto out; } } @@ -203,7 +231,7 @@ int run_server(struct server_state_st *state) fprintf(stderr, "Error setting hostkey: %s\n", ssh_get_error(sshbind)); - goto free_sshbind; + goto out; } } @@ -212,17 +240,17 @@ int run_server(struct server_state_st *state) fprintf(stderr, "Error listening to socket: %s\n", ssh_get_error(sshbind)); - goto free_sshbind; + goto out; } - printf("Started libssh test server on port %d\n", state->port); + printf("%d: Started libssh test server on port %d\n", getpid(), state->port); - for (;;) { + while (done == false) { session = ssh_new(); if (session == NULL) { fprintf(stderr, "Out of memory\n"); rc = SSH_ERROR; - goto free_sshbind; + goto out; } /* Blocks until there is a new incoming connection. */ @@ -235,6 +263,9 @@ int run_server(struct server_state_st *state) /* Remove the SIGCHLD handler inherited from parent. */ sa.sa_handler = SIG_DFL; sigaction(SIGCHLD, &sa, NULL); + /* Remove the SIGTERM handler inherited from parent. */ + sa.sa_handler = SIG_DFL; + sigaction(SIGTERM, &sa, NULL); /* Remove socket binding, which allows us to restart the * parent process, without terminating existing sessions. */ ssh_bind_free(sshbind); @@ -252,11 +283,12 @@ int run_server(struct server_state_st *state) ssh_free(session); free_server_state(state); - + SAFE_FREE(state); exit(0); case -1: fprintf(stderr, "Failed to fork\n"); } + fprintf(stderr, "Forked process PID %d\n", pid); } else { fprintf(stderr, "Error accepting a connection: %s\n", @@ -271,12 +303,17 @@ int run_server(struct server_state_st *state) rc = 0; -free_sshbind: +out: + free_server_state(state); + SAFE_FREE(state); ssh_bind_free(sshbind); return rc; } -pid_t fork_run_server(struct server_state_st *state) +pid_t +fork_run_server(struct server_state_st *state, + void (*free_test_state) (void **userdata), + void *userdata) { pid_t pid; int rc; @@ -306,6 +343,8 @@ pid_t fork_run_server(struct server_state_st *state) pid = fork(); switch(pid) { case 0: + /* no longer needed */ + free_test_state(userdata); /* Remove the SIGCHLD handler inherited from parent. */ sa.sa_handler = SIG_DFL; sigaction(SIGCHLD, &sa, NULL); @@ -324,6 +363,7 @@ pid_t fork_run_server(struct server_state_st *state) return -1; default: /* Return the child pid */ + fprintf(stderr, "Forked process PID %d\n", pid); return pid; } } diff --git a/tests/server/test_server/test_server.h b/tests/server/test_server/test_server.h index b53894bb..7c6bcb76 100644 --- a/tests/server/test_server/test_server.h +++ b/tests/server/test_server/test_server.h @@ -36,7 +36,6 @@ struct server_state_st { int port; char *ecdsa_key; - char *dsa_key; char *ed25519_key; char *rsa_key; char *host_key; @@ -53,6 +52,8 @@ struct server_state_st { char *config_file; bool parse_global_config; + char *log_file; + /* State */ int max_tries; int error; @@ -73,4 +74,7 @@ void free_server_state(struct server_state_st *state); int run_server(struct server_state_st *state); /*TODO: Add documentation */ -pid_t fork_run_server(struct server_state_st *state); +pid_t +fork_run_server(struct server_state_st *state, + void (*free_state) (void **userdata), + void *userdata); diff --git a/tests/server/torture_server.c b/tests/server/torture_server.c index 910cd1bd..f6b9dea4 100644 --- a/tests/server/torture_server.c +++ b/tests/server/torture_server.c @@ -174,6 +174,7 @@ static void torture_server_auth_none(void **state) struct test_server_st *tss = *state; struct torture_state *s = NULL; ssh_session session = NULL; + char *banner = NULL; int rc; assert_non_null(tss); @@ -193,6 +194,11 @@ static void torture_server_auth_none(void **state) rc = ssh_userauth_none(session, NULL); assert_int_equal(rc, SSH_AUTH_DENIED); + banner = ssh_get_issue_banner(session); + assert_string_equal(banner, SSHD_BANNER_MESSAGE); + free(banner); + banner = 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); @@ -364,6 +370,118 @@ static void torture_server_unknown_global_request(void **state) ssh_channel_close(channel); } +static void torture_server_no_more_sessions(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + ssh_channel channels[2]; + 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); + + /* Open a channel session */ + channels[0] = ssh_channel_new(session); + assert_non_null(channels[0]); + + rc = ssh_channel_open_session(channels[0]); + assert_ssh_return_code(session, rc); + + /* Send no-more-sessions@openssh.com global request */ + rc = ssh_request_no_more_sessions(session); + assert_ssh_return_code(session, rc); + + /* Try to open an extra session and expect failure */ + channels[1] = ssh_channel_new(session); + assert_non_null(channels[1]); + + rc = ssh_channel_open_session(channels[1]); + assert_int_equal(rc, SSH_ERROR); + + /* Free the unused channel */ + ssh_channel_free(channels[1]); + + /* Close and free open channel */ + ssh_channel_close(channels[0]); + ssh_channel_free(channels[0]); +} + +static void torture_server_set_disconnect_message(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + const char *message = "Goodbye"; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_session_set_disconnect_message(session,message); + assert_ssh_return_code(session, rc); + assert_string_equal(session->disconnect_message,message); +} + +static void torture_null_server_set_disconnect_message(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + 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_session_set_disconnect_message(NULL,"Goodbye"); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_server_set_null_disconnect_message(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + 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_session_set_disconnect_message(session,NULL); + assert_int_equal(rc, SSH_OK); + assert_string_equal(session->disconnect_message,"Bye Bye"); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -382,6 +500,18 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_server_unknown_global_request, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_server_no_more_sessions, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_set_disconnect_message, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_null_server_set_disconnect_message, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_set_null_disconnect_message, + session_setup, + session_teardown), }; ssh_init(); diff --git a/tests/server/torture_server_auth_kbdint.c b/tests/server/torture_server_auth_kbdint.c index 5bf81598..1d5eb733 100644 --- a/tests/server/torture_server_auth_kbdint.c +++ b/tests/server/torture_server_auth_kbdint.c @@ -96,11 +96,7 @@ static void cleanup_pcap(struct session_data_st *sdata) 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. - */ + ssh_pcap_file_free(sdata->pcap); sdata->pcap = NULL; } #endif @@ -161,7 +157,7 @@ static int authenticate_kbdint(ssh_session session, initial_prompt[0] = "username: "; initial_prompt[1] = "password: "; - /* Prompt for aditional prompts */ + /* Prompt for additional prompts */ retype_prompt[0] = "retype password: "; if ((session == NULL) || (message == NULL) || (sdata == NULL)) { @@ -514,6 +510,14 @@ end: return; } +static void free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + static int setup_kbdint_server(void **state) { struct torture_state *s; @@ -523,6 +527,7 @@ static int setup_kbdint_server(void **state) char rsa_hostkey[1024] = {0}; char sshd_path[1024]; + char log_file[1024]; int rc; @@ -550,6 +555,11 @@ static int setup_kbdint_server(void **state) rc = mkdir(sshd_path, 0755); assert_return_code(rc, errno); + snprintf(log_file, + sizeof(log_file), + "%s/sshd/log", + s->socket_dir); + snprintf(rsa_hostkey, sizeof(rsa_hostkey), "%s/sshd/ssh_host_rsa_key", @@ -569,7 +579,9 @@ static int setup_kbdint_server(void **state) ss->host_key = strdup(rsa_hostkey); assert_non_null(rsa_hostkey); + /* not to mix up the client and server messages */ ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); #ifdef WITH_PCAP ss->with_pcap = 1; @@ -580,12 +592,15 @@ static int setup_kbdint_server(void **state) ss->max_tries = 3; ss->error = 0; + tss->state = s; + tss->ss = ss; + /* 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); + pid = fork_run_server(ss, free_test_server_state, &tss); if (pid < 0) { fail(); } @@ -597,11 +612,8 @@ static int setup_kbdint_server(void **state) setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); unsetenv("PAM_WRAPPER"); - /* Wait 200ms */ - usleep(200 * 1000); - - tss->state = s; - tss->ss = ss; + rc = torture_wait_for_daemon(15); + assert_int_equal(rc, 0); *state = tss; diff --git a/tests/server/torture_server_config.c b/tests/server/torture_server_config.c index 5f66f015..0bb11ec0 100644 --- a/tests/server/torture_server_config.c +++ b/tests/server/torture_server_config.c @@ -51,9 +51,6 @@ struct test_server_st { 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) @@ -117,14 +114,6 @@ static int setup_files(void **state) 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; @@ -372,10 +361,6 @@ static size_t setup_hostkey_files(struct test_server_st *tss) 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 */ @@ -670,18 +655,6 @@ static void torture_server_config_hostkey_algorithms(void **state) 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) diff --git a/tests/server/torture_sftpserver.c b/tests/server/torture_sftpserver.c new file mode 100644 index 00000000..275f4508 --- /dev/null +++ b/tests/server/torture_sftpserver.c @@ -0,0 +1,1012 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki <ansasaki@redhat.com> + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/sftp.h" + +#define LIBSSH_STATIC + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <pwd.h> + +#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; +}; + +void sftp_handle_session_cb(ssh_event event, + ssh_session session, + struct server_state_st *state); + +static void free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static int setup_default_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + //char trusted_ca_pubkey[1024]; + + char sshd_path[1024]; + char log_file[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(log_file, + sizeof(log_file), + "%s/sshd/log", + s->socket_dir); + + 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)); + + 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)); + + /* 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); + + ss->ed25519_key = strdup(ed25519_hostkey); + assert_non_null(ss->ed25519_key); + + 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; + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + + 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; + + tss->state = s; + tss->ss = ss; + + /* Use the default session handling function */ + ss->handle_session = sftp_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, free_test_server_state, &tss); + 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); + + *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(); + 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); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = (struct torture_sftp*)calloc(1, sizeof(struct torture_sftp)); + assert_non_null(s->ssh.tsftp); + + 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_setup_sftp(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + ssh_session session; + sftp_session sftp; + int rc; + + assert_non_null(tss); + + rc = session_setup(state); + assert_int_equal(rc, 0); + + 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); + + 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, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + ssh_get_issue_banner(session); + + /* init sftp session */ + tsftp = s->ssh.tsftp; + + printf("in establish before sftp_new\n"); + sftp = sftp_new(session); + assert_non_null(sftp); + tsftp->sftp = sftp; + + rc = sftp_init(sftp); + assert_int_equal(rc, SSH_OK); + + 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); + + sftp_free(s->ssh.tsftp->sftp); + SAFE_FREE(s->ssh.tsftp); + + 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_establish_sftp(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + ssh_session session; + sftp_session sftp; + 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); + + ssh_get_issue_banner(session); + + /* init sftp session */ + tsftp = s->ssh.tsftp; + sftp = tsftp->sftp; + + printf("in establish before sftp_new\n"); + sftp = sftp_new(session); + assert_non_null(sftp); + + rc = sftp_init(sftp); + assert_int_equal(rc, SSH_OK); + + tsftp->sftp = sftp; +} + +static void torture_server_test_sftp_function(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + ssh_session session; + sftp_session sftp; + int rc; + char *rv_str; + sftp_dir dir; + + char data[65535] = {0}; + sftp_file source; + sftp_file to; + int read_len; + int write_len; + + 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); + + 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); + + rv_str = ssh_get_issue_banner(session); + + /* init sftp session */ + tsftp = s->ssh.tsftp; + sftp = sftp_new(session); + assert_non_null(sftp); + tsftp->sftp = sftp; + + rc = sftp_init(sftp); + assert_int_equal(rc, SSH_OK); + + /* symbol link */ + rc = sftp_symlink(sftp, "/tmp/this_is_the_link", "/tmp/sftp_symlink_test"); + assert_int_equal(rc, SSH_OK); + + rv_str = sftp_readlink(sftp, "/tmp/sftp_symlink_test"); + assert_non_null(rv_str); + ssh_string_free_char(rv_str); + + rc = sftp_unlink(sftp, "/tmp/sftp_symlink_test"); + assert_int_equal(rc, SSH_OK); + + /* open and close dir */ + dir = sftp_opendir(sftp, "./"); + assert_non_null(dir); + + rc = sftp_closedir(dir); + assert_int_equal(rc, SSH_OK); + + /* file read and write */ + source = sftp_open(sftp, "/usr/bin/ssh", O_RDONLY, 0); + assert_non_null(source); + + to = sftp_open(sftp, "ssh-copy", O_WRONLY | O_CREAT, 0700); + assert_non_null(to); + + read_len = sftp_read(source, data, 4096); + write_len = sftp_write(to, data, read_len); + assert_int_equal(write_len, read_len); + + rc = sftp_close(source); + assert_int_equal(rc, SSH_OK); + + rc = sftp_close(to); + assert_int_equal(rc, SSH_OK); + + rc = sftp_unlink(sftp, "ssh-copy"); + assert_int_equal(rc, SSH_OK); +} + +static void torture_server_sftp_open_read_write(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + sftp_attributes a = NULL; + sftp_file new_file = NULL; + char tmp_file[PATH_MAX] = {0}; + char data[10] = "0123456789"; + char read_data[10] = {0}; + struct stat sb; + int rc, write_len, read_len; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + snprintf(tmp_file, sizeof(tmp_file), "%s/newfile", tss->temp_dir); + + /* + * Create a new file + */ + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0751); + assert_non_null(new_file); + + /* Write should work ok */ + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + + /* Reading should fail */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, SSH_ERROR); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* Verify locally the mode is correct */ + rc = stat(tmp_file, &sb); + assert_int_equal(rc, 0); + assert_int_equal(sb.st_mode, S_IFREG | 0751); + assert_int_equal(sb.st_size, sizeof(data)); /* 10b written */ + + /* Remote stat */ + a = sftp_stat(sftp, tmp_file); + assert_non_null(a); + assert_int_equal(a->permissions, S_IFREG | 0751); + assert_int_equal(a->size, sizeof(data)); /* 10b written */ + assert_int_equal(a->type, SSH_FILEXFER_TYPE_REGULAR); + sftp_attributes_free(a); + + /* + * Now, lets try O_APPEND, mode is ignored + */ + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_APPEND, 0); + assert_non_null(new_file); + + /* fstat is not implemented */ + a = sftp_fstat(new_file); + assert_null(a); + + /* Write should work ok */ + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + + /* Reading should fail */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, SSH_ERROR); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* + * Now, lets try read+write, mode is ignored + */ + new_file = sftp_open(sftp, tmp_file, O_RDWR, 0); + assert_non_null(new_file); + + /* Reading should work */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, sizeof(read_data)); + assert_int_equal(sizeof(read_data), sizeof(data)); /* sanity */ + assert_memory_equal(read_data, data, sizeof(data)); + + rc = sftp_seek(new_file, 20); + assert_ssh_return_code(session, rc); + + /* Write should work also ok */ + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* Remove the file */ + rc = sftp_unlink(sftp, tmp_file); + assert_ssh_return_code(session, rc); + + /* again: the file does not exist anymore so we should fail now */ + rc = sftp_unlink(sftp, tmp_file); + assert_int_equal(rc, SSH_ERROR); + + /* + * Now, lets try read+write+create + */ + new_file = sftp_open(sftp, tmp_file, O_RDWR | O_CREAT, 0700); + assert_non_null(new_file); + + /* Reading should not fail but return no data */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, 0); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* be nice */ + rc = sftp_unlink(sftp, tmp_file); + assert_ssh_return_code(session, rc); + + /* null flags should be invalid */ + /* but there is no way in libssh client to force null flags so skip this + new_file = sftp_open(sftp, tmp_file, 0, 0700); + assert_null(new_file); + */ + + /* Only O_CREAT is invalid on file which does not exist. Read is implicit */ + new_file = sftp_open(sftp, tmp_file, O_CREAT, 0700); + assert_null(new_file); +} + +static void torture_server_sftp_mkdir(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + sftp_file new_file = NULL; + char tmp_dir[PATH_MAX] = {0}; + char tmp_file[PATH_MAX] = {0}; + sftp_attributes a = NULL; + struct stat sb; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + snprintf(tmp_dir, sizeof(tmp_dir), "%s/newdir", tss->temp_dir); + + /* create a test dir */ + rc = sftp_mkdir(sftp, tmp_dir, 0751); + assert_ssh_return_code(session, rc); + + /* try the same path again -- we should get an error */ + rc = sftp_mkdir(sftp, tmp_dir, 0751); + assert_int_equal(rc, SSH_ERROR); + + /* Verify locally the mode is correct */ + rc = stat(tmp_dir, &sb); + assert_int_equal(rc, 0); + assert_int_equal(sb.st_mode, S_IFDIR | 0751); + + /* Remote stat */ + a = sftp_stat(sftp, tmp_dir); + assert_non_null(a); + assert_int_equal(a->permissions, S_IFDIR | 0751); + assert_int_equal(a->type, SSH_FILEXFER_TYPE_DIRECTORY); + sftp_attributes_free(a); + + snprintf(tmp_file, sizeof(tmp_file), "%s/newdir/newfile", tss->temp_dir); + + /* create a file in there */ + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* remove of non-empty directory fails */ + rc = sftp_rmdir(sftp, tmp_dir); + assert_int_equal(rc, SSH_ERROR); + + /* Unlink can not remove directory either */ + rc = sftp_unlink(sftp, tmp_dir); + assert_int_equal(rc, SSH_ERROR); + + /* Remove the file */ + rc = sftp_unlink(sftp, tmp_file); + assert_int_equal(rc, SSH_OK); + + /* Now it should work */ + rc = sftp_rmdir(sftp, tmp_dir); + assert_ssh_return_code(session, rc); +} + +static void torture_server_sftp_realpath(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + char path[PATH_MAX] = {0}; + char exp_path[PATH_MAX] = {0}; + char *new_path = NULL; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* first try with the empty string, which should be equivalent to CWD */ + new_path = sftp_canonicalize_path(sftp, path); + assert_non_null(new_path); + assert_string_equal(new_path, tss->cwd); + ssh_string_free_char(new_path); + + /* now, lets try some more complicated paths relative to the CWD */ + snprintf(path, sizeof(path), "%s/.././%s", + tss->temp_dir, tss->temp_dir); + new_path = sftp_canonicalize_path(sftp, path); + assert_non_null(new_path); + snprintf(exp_path, sizeof(exp_path), "%s/%s", + tss->cwd, tss->temp_dir); + assert_string_equal(new_path, exp_path); + ssh_string_free_char(new_path); + + /* and this one does not exists, which is an error */ + snprintf(path, sizeof(path), "%s/.././%s/nodir", + tss->temp_dir, tss->temp_dir); + new_path = sftp_canonicalize_path(sftp, path); + assert_null(new_path); + ssh_string_free_char(new_path); +} + +static void torture_server_sftp_symlink(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + sftp_file new_file = NULL; + char tmp_dir[PATH_MAX] = {0}; + char tmp_file[PATH_MAX] = {0}; + char path[PATH_MAX] = {0}; + char abs_path[PATH_MAX] = {0}; + char data[42] = "012345678901234567890123456789012345678901"; + char *new_path = NULL; + sftp_attributes a = NULL; + sftp_dir dir; + int write_len, num_files = 0; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* create a test dir */ + snprintf(tmp_dir, sizeof(tmp_dir), "%s/newdir", tss->temp_dir); + rc = sftp_mkdir(sftp, tmp_dir, 0751); + assert_ssh_return_code(session, rc); + + /* create a file in there */ + snprintf(tmp_file, sizeof(tmp_file), "%s/%s/newdir/newfile", + tss->cwd, tss->temp_dir); + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* now, lets create a (relative) symlink to the new file */ + snprintf(path, sizeof(path), "%s/newdir/linkname", tss->temp_dir); + rc = sftp_symlink(sftp, tmp_file, path); + assert_ssh_return_code(session, rc); + + /* when the destination exists, it should fail */ + rc = sftp_symlink(sftp, tmp_dir, tmp_file); + assert_int_equal(rc, SSH_ERROR); + + /* now, there are different versions of stat that follow symlinks or not */ + /* lstat should not follow the symlink and show information about the link + * itself */ + a = sftp_lstat(sftp, path); + assert_non_null(a); + assert_int_not_equal(a->size, sizeof(data)); + assert_int_equal(a->type, SSH_FILEXFER_TYPE_SYMLINK); + sftp_attributes_free(a); + + /* readlink should give us more information about the target of the symlink + */ + new_path = sftp_readlink(sftp, path); + assert_non_null(new_path); + snprintf(abs_path, sizeof(abs_path), "%s/%s/newdir/newfile", + tss->cwd, tss->temp_dir); + assert_string_equal(new_path, abs_path); + ssh_string_free_char(new_path); + + /* stat should follow the symlink and show information about the link + * target */ + a = sftp_stat(sftp, path); + assert_non_null(a); + assert_int_equal(a->size, sizeof(data)); + assert_int_equal(a->permissions, S_IFREG | 0700); + assert_int_equal(a->type, SSH_FILEXFER_TYPE_REGULAR); + sftp_attributes_free(a); + + /* on non-existing path, they fail */ + a = sftp_lstat(sftp, "non-existing"); + assert_null(a); + a = sftp_stat(sftp, "non-existing"); + assert_null(a); + + /**** readdir ****/ + dir = sftp_opendir(sftp, tmp_dir); + assert_non_null(dir); + while ((a = sftp_readdir(sftp, dir))) { + if (strcmp(a->name, ".") != 0 && + strcmp(a->name, "..") != 0 && + strcmp(a->name, "newfile") != 0 && + strcmp(a->name, "linkname") != 0) { + /* There is a file we did not create */ + assert_true(false); + } + + num_files++; + sftp_attributes_free(a); + } + assert_int_equal(num_files, 4); + rc = sftp_dir_eof(dir); + assert_int_equal(rc, 1); + rc = sftp_closedir(dir); + assert_ssh_return_code(session, rc); + + /* now, remove the target of the link, the stat should not handle that, + * while lstat should keep working */ + rc = sftp_unlink(sftp, tmp_file); + assert_int_equal(rc, SSH_OK); + + a = sftp_lstat(sftp, path); + assert_non_null(a); + assert_int_not_equal(a->size, sizeof(data)); + sftp_attributes_free(a); + + a = sftp_stat(sftp, path); + assert_null(a); + + /* readlink works ok on broken symlinks */ + new_path = sftp_readlink(sftp, path); + assert_non_null(new_path); + snprintf(abs_path, sizeof(abs_path), "%s/%s/newdir/newfile", + tss->cwd, tss->temp_dir); + assert_string_equal(new_path, abs_path); + ssh_string_free_char(new_path); + + /* readlink should fail on directories */ + new_path = sftp_readlink(sftp, tmp_dir); + assert_null(new_path); + /* readlink should fail on or on non-existing files */ + new_path = sftp_readlink(sftp, tmp_file); + assert_null(new_path); + + /* Clean up symlink */ + rc = sftp_unlink(sftp, path); + assert_int_equal(rc, SSH_OK); + /* Clean up temporary directory */ + rc = sftp_rmdir(sftp, tmp_dir); + assert_int_equal(rc, SSH_OK); +} + +static void torture_server_sftp_extended(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + sftp_file new_file = NULL; + char tmp_dir[PATH_MAX] = {0}; + char tmp_file[PATH_MAX] = {0}; + sftp_statvfs_t st = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* create a test dir */ + snprintf(tmp_dir, sizeof(tmp_dir), "%s/newdir", tss->temp_dir); + rc = sftp_mkdir(sftp, tmp_dir, 0751); + assert_ssh_return_code(session, rc); + + /* create a file in there */ + snprintf(tmp_file, sizeof(tmp_file), "%s/%s/newdir/newfile", + tss->cwd, tss->temp_dir); + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + + /* extended fstatvsf is not advertised nor supported now but calling this + * message will keep hanging the server. The extension protocol says that + * the clients can not request extension that are not supported by the + * server so before doing this, we should use sftp_extension_supported() + * anyway */ + /* st = sftp_fstatvfs(new_file); + assert_null(st); */ + + /* close */ + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* extended statvsf */ + st = sftp_statvfs(sftp, tmp_file); + assert_non_null(st); + /* probably hard to check more */ + sftp_statvfs_free(st); + + /* Clean up temporary directory */ + rc = sftp_unlink(sftp, tmp_file); + assert_int_equal(rc, SSH_OK); + rc = sftp_rmdir(sftp, tmp_dir); + assert_int_equal(rc, SSH_OK); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_server_establish_sftp, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_test_sftp_function, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_open_read_write, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_mkdir, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_realpath, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_symlink, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_extended, + session_setup_sftp, + 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/ssh_ping.c b/tests/ssh_ping.c index bdb3cea0..01754590 100644 --- a/tests/ssh_ping.c +++ b/tests/ssh_ping.c @@ -60,7 +60,7 @@ int main(int argc, char **argv) goto out; } - /* Enable all supported algorithms (including DSA) */ + /* Enable all supported algorithms */ hostkeys = ssh_kex_get_supported_method(SSH_HOSTKEYS); rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, hostkeys); if (rc < 0) { diff --git a/tests/suppressions/lsan.supp b/tests/suppressions/lsan.supp new file mode 100644 index 00000000..42a17e35 --- /dev/null +++ b/tests/suppressions/lsan.supp @@ -0,0 +1 @@ +leak:libcrypto.so diff --git a/tests/tests_config.h.cmake b/tests/tests_config.h.cmake index 13815ec6..3b6bde0e 100644 --- a/tests/tests_config.h.cmake +++ b/tests/tests_config.h.cmake @@ -52,20 +52,21 @@ #cmakedefine OPENSSH_SSH_ED25519 1 #cmakedefine OPENSSH_SSH_ED25519_CERT_V01_OPENSSH_COM 1 #cmakedefine OPENSSH_SSH_RSA 1 -#cmakedefine OPENSSH_SSH_DSS 1 #cmakedefine OPENSSH_ECDSA_SHA2_NISTP256 1 #cmakedefine OPENSSH_ECDSA_SHA2_NISTP384 1 #cmakedefine OPENSSH_ECDSA_SHA2_NISTP521 1 #cmakedefine OPENSSH_SSH_RSA_CERT_V01_OPENSSH_COM 1 -#cmakedefine OPENSSH_SSH_DSS_CERT_V01_OPENSSH_COM 1 #cmakedefine OPENSSH_ECDSA_SHA2_NISTP256_CERT_V01_OPENSSH_COM 1 #cmakedefine OPENSSH_ECDSA_SHA2_NISTP384_CERT_V01_OPENSSH_COM 1 #cmakedefine OPENSSH_ECDSA_SHA2_NISTP521_CERT_V01_OPENSSH_COM 1 /* Available programs */ -#cmakedefine NC_EXECUTABLE "${NC_EXECUTABLE}" +#cmakedefine NCAT_EXECUTABLE "${NCAT_EXECUTABLE}" #cmakedefine SSHD_EXECUTABLE "${SSHD_EXECUTABLE}" #cmakedefine SSH_EXECUTABLE "${SSH_EXECUTABLE}" #cmakedefine WITH_TIMEOUT ${WITH_TIMEOUT} #cmakedefine TIMEOUT_EXECUTABLE "${TIMEOUT_EXECUTABLE}" +#cmakedefine SOFTHSM2_LIBRARY "${SOFTHSM2_LIBRARY}" +#cmakedefine P11_KIT_CLIENT "${P11_KIT_CLIENT}" +#cmakedefine PKCS11SPY "${PKCS11SPY}" diff --git a/tests/torture.c b/tests/torture.c index 2d21b3b9..78edaae1 100644 --- a/tests/torture.c +++ b/tests/torture.c @@ -40,11 +40,6 @@ #include <unistd.h> #elif (defined _WIN32) || (defined _WIN64) #include <direct.h> -#include <io.h> -#define read _read -#define open _open -#define write _write -#define close _close #define chdir _chdir #endif @@ -53,6 +48,10 @@ #include "libssh/misc.h" #include "libssh/token.h" +#ifdef HAVE_VALGRIND_VALGRIND_H +#include <valgrind/valgrind.h> +#endif + #define TORTURE_SSHD_SRV_IPV4 "127.0.0.10" /* socket wrapper IPv6 prefix fd00::5357:5fxx */ #define TORTURE_SSHD_SRV_IPV6 "fd00::5357:5f0a" @@ -77,6 +76,7 @@ static const char *pattern = NULL; #ifndef _WIN32 +/* TODO missing code coverage */ static int _torture_auth_kbdint(ssh_session session, const char *password) { const char *prompt; @@ -244,6 +244,13 @@ int torture_terminate_process(const char *pidfile) /* 10 ms */ usleep(10 * 1000); +#ifdef HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) { + SSH_LOG(SSH_LOG_INFO, "Running within Valgrind, wait one more " + "second for the server to clean up."); + usleep(1000 * 1000); + } +#endif /* HAVE_VALGRIND_VALGRIND_H */ rc = kill(pid, 0); if (rc != 0) { @@ -383,18 +390,12 @@ ssh_bind torture_ssh_bind(const char *addr, } 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; + case SSH_KEYTYPE_ED25519: + opts = SSH_BIND_OPTIONS_HOSTKEY; break; default: goto out_free; @@ -631,9 +632,6 @@ void torture_setup_create_libssh_config(void **state) { 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 sshd_config[2048]; @@ -647,9 +645,6 @@ void torture_setup_create_libssh_config(void **state) "%s %s\n" "%s %s\n" "%s %s\n" -#ifdef HAVE_DSA - "%s %s\n" -#endif /* HAVE_DSA */ "%s\n"; /* The space for test-specific options */ bool written = false; int rc; @@ -686,13 +681,6 @@ void torture_setup_create_libssh_config(void **state) "%s/sshd/ssh_host_ecdsa_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 */ - if (!written) { torture_write_file(ed25519_hostkey, torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); @@ -700,10 +688,6 @@ void torture_setup_create_libssh_config(void **state) torture_get_testkey(SSH_KEYTYPE_RSA, 0)); torture_write_file(ecdsa_hostkey, torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); -#ifdef HAVE_DSA - torture_write_file(dsa_hostkey, - torture_get_testkey(SSH_KEYTYPE_DSS, 0)); -#endif /* HAVE_DSA */ } additional_config = (s->srv_additional_config != NULL ? @@ -714,21 +698,16 @@ void torture_setup_create_libssh_config(void **state) "HostKey", ed25519_hostkey, "HostKey", rsa_hostkey, "HostKey", ecdsa_hostkey, -#ifdef HAVE_DSA - "HostKey", dsa_hostkey, -#endif /* HAVE_DSA */ additional_config); torture_write_file(s->srv_config, sshd_config); } +#ifdef SSHD_EXECUTABLE 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]; @@ -746,10 +725,8 @@ static void torture_setup_create_sshd_config(void **state, bool pam) const char config_string[]= "Port 22\n" "ListenAddress 127.0.0.10\n" + "ListenAddress fd00::5357:5f0a\n" "%s %s\n" /* ed25519 HostKey */ -#ifdef HAVE_DSA - "%s %s\n" /* DSA HostKey */ -#endif /* HAVE_DSA */ "%s %s\n" /* RSA HostKey */ "%s %s\n" /* ECDSA HostKey */ "\n" @@ -784,6 +761,7 @@ static void torture_setup_create_sshd_config(void **state, bool pam) const char fips_config_string[]= "Port 22\n" "ListenAddress 127.0.0.10\n" + "ListenAddress fd00::5357:5f0a\n" "%s %s\n" /* RSA HostKey */ "%s %s\n" /* ECDSA HostKey */ "\n" @@ -869,13 +847,6 @@ static void torture_setup_create_sshd_config(void **state, bool pam) "%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", @@ -894,10 +865,6 @@ static void torture_setup_create_sshd_config(void **state, bool pam) 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, @@ -934,9 +901,6 @@ static void torture_setup_create_sshd_config(void **state, bool pam) 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, @@ -949,7 +913,7 @@ static void torture_setup_create_sshd_config(void **state, bool pam) torture_write_file(s->srv_config, sshd_config); } -static int torture_wait_for_daemon(unsigned int seconds) +int torture_wait_for_daemon(unsigned int seconds) { struct ssh_timestamp start; int rc; @@ -1032,11 +996,13 @@ void torture_setup_libssh_server(void **state, const char *server_path) } /* Write the environment setting */ + /* OPENSSL variable is needed to enable SHA1 */ printed = snprintf(env, sizeof(env), "SOCKET_WRAPPER_DIR=%s " "SOCKET_WRAPPER_DEFAULT_IFACE=10 " "LD_PRELOAD=%s " - "%s", + "%s " + "OPENSSL_ENABLE_SHA1_SIGNATURES=1", s->socket_dir, ld_preload, force_fips); if (printed < 0) { fail_msg("Failed to print env!"); @@ -1145,23 +1111,14 @@ void torture_setup_sshd_server(void **state, bool pam) assert_int_equal(rc, 0); } -void torture_setup_tokens(const char *temp_dir, - const char *filename, - const char object_name[], - const char *load_public) +void torture_free_state(struct torture_state *s) { - char token_setup_start_cmd[1024] = {0}; - int rc; - - snprintf(token_setup_start_cmd, sizeof(token_setup_start_cmd), - "%s/tests/pkcs11/setup-softhsm-tokens.sh %s %s %s %s", - BINARYDIR, - temp_dir, - filename, - object_name, load_public); - - rc = system(token_setup_start_cmd); - assert_return_code(rc, errno); + free(s->srv_config); + free(s->socket_dir); + free(s->pcap_file); + free(s->srv_pidfile); + free(s->srv_additional_config); + free(s); } void torture_teardown_socket_dir(void **state) @@ -1187,13 +1144,7 @@ void torture_teardown_socket_dir(void **state) } 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); + torture_free_state(s); } static int @@ -1245,6 +1196,82 @@ void torture_teardown_sshd_server(void **state) torture_teardown_socket_dir(state); } +#endif /* SSHD_EXECUTABLE */ + +#ifdef WITH_PKCS11_URI +void torture_setup_tokens(const char *temp_dir, + const char *filename, + const char object_name[], + const char *load_public) +{ + char token_setup_start_cmd[1024] = {0}; + char socket_path[1204] = {0}; + char conf_path[1024] = {0}; + char *env = NULL; + int rc; + + rc = snprintf(token_setup_start_cmd, + sizeof(token_setup_start_cmd), + "%s/tests/pkcs11/setup-softhsm-tokens.sh %s %s %s %s %s %s", + BINARYDIR, + temp_dir, + filename, + object_name, + load_public, + SOFTHSM2_LIBRARY, +#ifdef WITH_PKCS11_PROVIDER + P11_KIT_CLIENT +#else + "" +#endif + ); + assert_int_not_equal(rc, sizeof(token_setup_start_cmd)); + + rc = system(token_setup_start_cmd); + assert_return_code(rc, errno); + +#ifdef WITH_PKCS11_PROVIDER + rc = snprintf(socket_path, + sizeof(socket_path), + "unix:path=%s/p11-kit-server.socket", + temp_dir); + assert_int_not_equal(rc, sizeof(socket_path)); + setenv("P11_KIT_SERVER_ADDRESS", socket_path, 1); + + setenv("PKCS11_PROVIDER_MODULE", P11_KIT_CLIENT, 1); + /* This is useful for debugging PKCS#11 calls */ + + env = getenv("TORTURE_PKCS11"); + if (env != NULL && env[0] != '\0') { +#ifdef PKCS11SPY + setenv("PKCS11SPY", P11_KIT_CLIENT, 1); + setenv("PKCS11_PROVIDER_MODULE", PKCS11SPY, 1); +#else + fprintf(stderr, "[ TORTURE ] >>> pkcs11-spy not found\n"); +#endif + } +#else + (void)env; + + snprintf(conf_path, sizeof(conf_path), "%s/softhsm.conf", temp_dir); + setenv("SOFTHSM2_CONF", conf_path, 1); +#endif /* WITH_PKCS11_PROVIDER */ +} + +void torture_cleanup_tokens(const char *temp_dir) +{ + char pidfile[1024] = {0}; + int rc; + pid_t pid; + +#ifdef WITH_PKCS11_PROVIDER + snprintf(pidfile, sizeof(pidfile), "%s/p11-kit-server.pid", temp_dir); + torture_terminate_process(pidfile); +#else + unsetenv("SOFTHSM2_CONF"); +#endif /* WITH_PKCS11_PROVIDER */ +} +#endif /* WITH_PKCS11_URI */ char *torture_make_temp_dir(const char *template) { @@ -1625,12 +1652,14 @@ void torture_reset_config(ssh_session session) memset(session->opts.options_seen, 0, sizeof(session->opts.options_seen)); } -#if ((defined _WIN32) || (defined _WIN64)) && (defined USE_ATTRIBUTE_WEAK) +#if defined(HAVE_WEAK_ATTRIBUTE) && defined(TORTURE_SHARED) __attribute__((weak)) int torture_run_tests(void) { - fail(); + fail_msg("torture_run_tests from shared library called"); + + return -1; } -#endif +#endif /* defined(HAVE_WEAK_ATTRIBUTE) && defined(TORTURE_SHARED) */ int main(int argc, char **argv) { struct argument_s arguments; diff --git a/tests/torture.h b/tests/torture.h index 599b53ea..eb2765da 100644 --- a/tests/torture.h +++ b/tests/torture.h @@ -24,10 +24,6 @@ #ifndef _TORTURE_H #define _TORTURE_H -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - #include <stdio.h> #include <stdlib.h> #include <stdarg.h> @@ -50,6 +46,7 @@ #endif /* assert_return_code */ #define TORTURE_SSH_SERVER "127.0.0.10" +#define TORTURE_SSH_SERVER_IP6 "fd00::5357:5f0a" #define TORTURE_SSH_USER_BOB "bob" #define TORTURE_SSH_USER_BOB_PASSWORD "secret" @@ -124,17 +121,25 @@ void _torture_filter_tests(struct CMUnitTest *tests, size_t ntests); const char *torture_server_address(int domain); int torture_server_port(void); +int torture_wait_for_daemon(unsigned int seconds); + +#ifdef SSHD_EXECUTABLE void torture_setup_socket_dir(void **state); void torture_setup_sshd_server(void **state, bool pam); -void torture_setup_tokens(const char *temp_dir, - const char *filename, - const char object_name[], - const char *load_public); void torture_teardown_socket_dir(void **state); void torture_teardown_sshd_server(void **state); int torture_update_sshd_config(void **state, const char *config); +#endif /* SSHD_EXECUTABLE */ + +#ifdef WITH_PKCS11_URI +void torture_setup_tokens(const char *temp_dir, + const char *filename, + const char object_name[], + const char *load_public); +void torture_cleanup_tokens(const char *temp_dir); +#endif /* WITH_PKCS11_URI */ void torture_reset_config(ssh_session session); @@ -142,15 +147,17 @@ void torture_setup_create_libssh_config(void **state); void torture_setup_libssh_server(void **state, const char *server_path); +#if defined(HAVE_WEAK_ATTRIBUTE) && defined(TORTURE_SHARED) +__attribute__((weak)) int torture_run_tests(void); +#else /* * This function must be defined in every unit test file. */ -#if ((defined _WIN32) || (defined _WIN64)) && (defined USE_ATTRIBUTE_WEAK) -__attribute__((weak)) int torture_run_tests(void); -#else int torture_run_tests(void); #endif +void torture_free_state(struct torture_state *s); + char *torture_make_temp_dir(const char *template); char *torture_create_temp_file(const char *template); diff --git a/tests/torture_key.c b/tests/torture_key.c index c4fb08ea..9bf71995 100644 --- a/tests/torture_key.c +++ b/tests/torture_key.c @@ -28,840 +28,1004 @@ #include "torture.h" #include "torture_key.h" +enum torture_format_e { + FORMAT_PEM = 0, + FORMAT_OPENSSH, + FORMAT_PKCS8, +}; + /**************************************************************************** - * DSA KEYS + * RSA 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"; + "-----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_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCsA5ERRaUFckAp\n" + "nmEAFjLGdFrINk/Vsl4ts9Ur6enF6auEfJmCN1tjcAOi34lHJaO+WXbDYYj7duW3\n" + "SP7H9lbCMwq79BhzJxinkcvTWCjE7G66xluL4qIdEYHrPQQx1cztTzZTuUD+P/8f\n" + "JmmnIONQOeJZptdAmB7ySwZcZOIV4An/rzu5X4klyMY/EAYVDHPKOK1/8Wsv1LRY\n" + "YplvKp4YPPJ4FnU0si5qI45HIsZJbh24csM3vwSawmfCqDaAlCZFJoPgE1kyO1t+\n" + "IVxIv1TDhdAVOxa6BQMRjUBThzmDXWeHMfMGL2ow63kPOtlCkPiPSADYs4ekeGg5\n" + "2DVm4esZAgMBAAECggEAUlNqrL+OWWry9nhMqIP3TTAG+uezke1KUclN7zSGpChP\n" + "5JrVxZLE9HR56QvfYePL2KBszNnze5bOHsia2A04PL29F3Od+A7wJ9MJoF8DNCyM\n" + "WmvxCKQxPoRQj8umJEvVRoaw06VIS+dJLRlvW4PMB2uMv6ewQcQMm534r2PBKQJG\n" + "J9UzLt8t9q6b5gql14TPsrYyCbmkn9WD72y8jZXWrscA4jh5RVOAk0dfLl52JEmV\n" + "qNAd/+U1TMcqqfuIf4lGXTWC3rZfyugbrZtNsXKVuF6oyb1rC5IXa5w5zH6C67CG\n" + "Q+KvwEQxvTyPeGIr5N/A91jQXpAfg6GIorsxOpNWAQKBgQDS+aa7kY4G3BGarqBU\n" + "0IeF0sNBZrEBmpBGZFvPFWEWlu/QaNtAQXu222rd+MOvMN/oIT1G1lbDYFRJdWr4\n" + "hcH9tdvWSBZTbFbLCb44UOT05dCzy6L4xjsWI/fK6rdgmhvaDkIKxVBaXYIe9YaZ\n" + "MuETU4OWXf23tjW7B1wOhsk34QKBgQDQuVGF8yPJ1abCqihT1MONc0GbC5/JiU0V\n" + "MffnVqZiAVQOk1/flIwI/iEOixTD/MJMOlz7XDTgEkl+QfVI29dI6KWl1phFNi6v\n" + "IO8aZYLrorYpfWcsSuGWGzXsLDNX33udiAMfc45Tgd+ogqZ0ZuO9aWSZj6YsfIbX\n" + "Rtuk5Nu6OQKBgBzrzEd6NJYMJP2nYqM90tYthka45RrrLUv5BfzlWWHXXxiaxzOP\n" + "xn3Splu3dlvVv1v68oN7bnlqAyPuzLM04tr8yx4c07Kr71n2A/WEYV6RAoMjLzwb\n" + "MI5gb9+JPYoolhjWS4dedR1uJCBKWCE4MFKN6EP8roM4nzT8VUpIf90BAoGBAMn5\n" + "qlPOlmEnZ/mTovaMOrdUQrYAJ51kSb0q31NzXAoWcvqvzeSS4HeLQv3JxExq+LG/\n" + "AF8UOO5jg86zkOHyKn+Y8UJrSGohJke9vOJ0PJ48k73OZdpMk8MnjaR+mSh9UCd+\n" + "iFcVATL5yC5OTzjwn4yYwgp/ITSHRAxpuv3zoFSxAoGARWCiLhFfCa0ai+0x7DTU\n" + "5E9X0w5fe/5WewKAPj/npREo4CeCJLvTEQ843A1tMJa66/jccbMmhUWIAwadA/UE\n" + "AnuCQeSNJ6A4YqSLDXYQ9RaEVwyD4aSA+Fyk6o239UwE+Mn/KwcmqYt9Ar0DfklX\n" + "oKZ2mF744iXbxtwqEA7PwPA=\n" + "-----END 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"; + "-----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"; + "-----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"; + "-----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"; - + "-----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"; + "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_public_testkey_pem[] = - "-----BEGIN PUBLIC KEY-----\n" - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArAOREUWlBXJAKZ5hABYy\n" - "xnRayDZP1bJeLbPVK+npxemrhHyZgjdbY3ADot+JRyWjvll2w2GI+3blt0j+x/ZW\n" - "wjMKu/QYcycYp5HL01goxOxuusZbi+KiHRGB6z0EMdXM7U82U7lA/j//HyZppyDj\n" - "UDniWabXQJge8ksGXGTiFeAJ/687uV+JJcjGPxAGFQxzyjitf/FrL9S0WGKZbyqe\n" - "GDzyeBZ1NLIuaiOORyLGSW4duHLDN78EmsJnwqg2gJQmRSaD4BNZMjtbfiFcSL9U\n" - "w4XQFTsWugUDEY1AU4c5g11nhzHzBi9qMOt5DzrZQpD4j0gA2LOHpHhoOdg1ZuHr\n" - "GQIDAQAB\n" - "-----END PUBLIC KEY-----\n"; - + "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArAOREUWlBXJAKZ5hABYy\n" + "xnRayDZP1bJeLbPVK+npxemrhHyZgjdbY3ADot+JRyWjvll2w2GI+3blt0j+x/ZW\n" + "wjMKu/QYcycYp5HL01goxOxuusZbi+KiHRGB6z0EMdXM7U82U7lA/j//HyZppyDj\n" + "UDniWabXQJge8ksGXGTiFeAJ/687uV+JJcjGPxAGFQxzyjitf/FrL9S0WGKZbyqe\n" + "GDzyeBZ1NLIuaiOORyLGSW4duHLDN78EmsJnwqg2gJQmRSaD4BNZMjtbfiFcSL9U\n" + "w4XQFTsWugUDEY1AU4c5g11nhzHzBi9qMOt5DzrZQpD4j0gA2LOHpHhoOdg1ZuHr\n" + "GQIDAQAB\n" + "-----END PUBLIC KEY-----\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"; + "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"; + "-----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_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIIBSwIBADCCASsGByqGSM44BAEwggEeAoGBAJTK9U8SSfdSdkOPMLNLNIelOW3O\n" + "vQRz7WbP8kAKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+fAC7ltzHDxOQhKrthE5Dj\n" + "T0+rDA+/LQ3cZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hodQn5qX3HUnFOZzpPQYGrW\n" + "XK74JNRTKHblo0MXAhUArziPbQaZT7N7CNDCwhe5VvoL88cCgYBeG4hWwnFRAnmd\n" + "ZBEt/ujdcQZD4RxWYc7MwHXEKweNiekSGFyj6v8cNlIPfWTMN4BlTJzPfVaoYvzJ\n" + "ev45lEuoSwYLt3AQDM+JcO6XTMdyXTKIo+tGsuA0kd4pxPol+UGeAruNBEhVSDcX\n" + "fXTh9tVravBqeIuXgZIFk9cylR2eDwQXAhUAjUd3z+I/OyDttPOg2+NAv42jKLI=\n" + "-----END 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"; + "-----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"; + "-----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"; + "-----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"; + "-----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"; + "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"; + "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"; + "-----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_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEIN55hgC1fcSew/S\n" + "tq3BWk1NoD/grU3pW8cyJRmVV2GhRANCAATH7253zxDkZa0t37Iy1hpqkLzmD7Zk\n" + "untW0UcDQRs0eoFRDGiH4iTz0yWvsBTFwU936QKj40TrKr+Y1BwvsTqJ\n" + "-----END 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"; + "-----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"; + "-----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"; + "-----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"; + "-----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"; + "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNT" + "YAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPtmS6e1bRRwNBGzR6gVEMaIfiJPPTJa+w" + "FMXBT3fpAqPjROsqv5jUHC+xOok= aris@kalix86\n"; static const char torture_ecdsa256_public_testkey_pem[] = - "-----BEGIN PUBLIC KEY-----\n" - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEx+9ud88Q5GWtLd+yMtYaapC85g+2\n" - "ZLp7VtFHA0EbNHqBUQxoh+Ik89Mlr7AUxcFPd+kCo+NE6yq/mNQcL7E6iQ==\n" - "-----END PUBLIC KEY-----\n"; + "-----BEGIN PUBLIC KEY-----\n" + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEx+9ud88Q5GWtLd+yMtYaapC85g+2\n" + "ZLp7VtFHA0EbNHqBUQxoh+Ik89Mlr7AUxcFPd+kCo+NE6yq/mNQcL7E6iQ==\n" + "-----END PUBLIC KEY-----\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"; + "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"; + "-----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_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBY8jEa5DtRy4AVeTWh\n" + "PJ/TK257behiC3uafEi6YA2oHORibqX55EDNwz29MT40mQShZANiAARXc4BN6BrV\n" + "o1QMi3+i/B85Lu7SMuzBi+1PbJti8xz+Szgq64gaBGOK9o+WOdLAd/w7p7DJLdzt\n" + "J0bYoyT4V3B3ZqR9RyGq6mYCjkXlc5YbYHjueBbp0oeNXqsXHNAWQZo=\n" + "-----END 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"; + "-----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"; + "-----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"; + "-----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"; + "-----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"; + "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzOD" + "QAAABhBFdzgE3oGtWjVAyLf6L8Hzku7tIy7MGL7U9sm2LzHP5LOCrriBoEY4r2j5Y5" + "0sB3/DunsMkt3O0nRtijJPhXcHdmpH1HIarqZgKOReVzlhtgeO54FunSh41eqxcc0B" + "ZBmg== aris@kalix86"; static const char torture_ecdsa384_public_testkey_pem[] = - "-----BEGIN PUBLIC KEY-----\n" - "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEV3OATega1aNUDIt/ovwfOS7u0jLswYvt\n" - "T2ybYvMc/ks4KuuIGgRjivaPljnSwHf8O6ewyS3c7SdG2KMk+Fdwd2akfUchqupm\n" - "Ao5F5XOWG2B47ngW6dKHjV6rFxzQFkGa\n" - "-----END PUBLIC KEY-----\n"; + "-----BEGIN PUBLIC KEY-----\n" + "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEV3OATega1aNUDIt/ovwfOS7u0jLswYvt\n" + "T2ybYvMc/ks4KuuIGgRjivaPljnSwHf8O6ewyS3c7SdG2KMk+Fdwd2akfUchqupm\n" + "Ao5F5XOWG2B47ngW6dKHjV6rFxzQFkGa\n" + "-----END PUBLIC KEY-----\n"; 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"; + "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"; + "-----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_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAvN50idki6IgbxJLt\n" + "SbXkClsf17ekx5LIAu66yGk1Jjc5PutH9LAgphpZdHYK2S5niMPk7ZxtRNtFWX5Q\n" + "iqviHZGhgYkDgYYABAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl17sFnTTrT\n" + "8wYo1FCzE07wV262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee16VwhJGsM\n" + "zGGh1FJ0XXhRJjUbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOOB7EmFZd1\n" + "8g==\n" + "-----END 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"; + "-----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"; + "-----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"; + "-----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"; - + "-----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"; + "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1Mj" + "EAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl17sFnTTrT8wYo1FCzE07w" + "V262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee16VwhJGsMzGGh1FJ0XXhRJj" + "UbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOOB7EmFZd18g== aris@kalix86"; static const char torture_ecdsa521_public_testkey_pem[] = - "-----BEGIN PUBLIC KEY-----\n" - "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBzoPvbx1tpCQedI+3O1pHAnDrcIGX\n" - "XlzKQHhJde7BZ0060/MGKNRQsxNO8FdutryAvgBN311Ce7CfwpBSHZP/P4EBGNY8\n" - "qFwdwbHntelcISRrDMxhodRSdF14USY1GxtfKmx/SYkoBNTeHyDNMyRuvHwMlMUc\n" - "QiNcFKGzjgexJhWXdfI=\n" - "-----END PUBLIC KEY-----\n"; + "-----BEGIN PUBLIC KEY-----\n" + "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBzoPvbx1tpCQedI+3O1pHAnDrcIGX\n" + "XlzKQHhJde7BZ0060/MGKNRQsxNO8FdutryAvgBN311Ce7CfwpBSHZP/P4EBGNY8\n" + "qFwdwbHntelcISRrDMxhodRSdF14USY1GxtfKmx/SYkoBNTeHyDNMyRuvHwMlMUc\n" + "QiNcFKGzjgexJhWXdfI=\n" + "-----END PUBLIC KEY-----\n"; 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"; + "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"; + "-----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"; + "-----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"; + "-----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"; + "-----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"; + "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) + "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_public_internal(enum ssh_keytypes_e type, + enum torture_format_e 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; - } + case SSH_KEYTYPE_DSS: + return torture_dsa_public_testkey; + case SSH_KEYTYPE_RSA: + if (format == FORMAT_OPENSSH) { + return torture_rsa_public_testkey; + } + return torture_rsa_public_testkey_pem; + case SSH_KEYTYPE_ECDSA_P521: + if (format == FORMAT_OPENSSH) { + return torture_ecdsa521_public_testkey; + } + return torture_ecdsa521_public_testkey_pem; + case SSH_KEYTYPE_ECDSA_P384: + if (format == FORMAT_OPENSSH) { + return torture_ecdsa384_public_testkey; + } + return torture_ecdsa384_public_testkey_pem; + case SSH_KEYTYPE_ECDSA_P256: + if (format == FORMAT_OPENSSH) { + return torture_ecdsa256_public_testkey; + } + return torture_ecdsa256_public_testkey_pem; + case SSH_KEYTYPE_ED25519: + if (format == FORMAT_OPENSSH) { + return torture_ed25519_public_testkey; + } + /* not available in other formats */ + return NULL; + 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_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_UNKNOWN: + return NULL; + } + + return NULL; +} + +static const char * +torture_get_testkey_encrypted_internal(enum ssh_keytypes_e type, + enum torture_format_e format) +{ + switch (type) { + case SSH_KEYTYPE_DSS: + switch (format) { + case FORMAT_OPENSSH: + return torture_dsa_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_dsa_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_dsa_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_RSA: + switch (format) { + case FORMAT_OPENSSH: + return torture_rsa_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_rsa_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_rsa_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P521: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa521_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_ecdsa521_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_ecdsa521_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P384: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa384_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_ecdsa384_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_ecdsa384_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P256: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa256_private_openssh_testkey_pasphrase; + case FORMAT_PKCS8: + return torture_ecdsa256_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_ecdsa256_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_ED25519: + switch (format) { + case FORMAT_OPENSSH: + return torture_ed25519_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_ed25519_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + /* ed25519 keys are not available in legacy PEM format */ + return NULL; + } + return NULL; + 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_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_UNKNOWN: + return NULL; + } + + return NULL; +} + +static const char * +torture_get_testkey_internal(enum ssh_keytypes_e type, + enum torture_format_e format) +{ + switch (type) { + case SSH_KEYTYPE_DSS: + switch (format) { + case FORMAT_OPENSSH: + return torture_dsa_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_dsa_private_pkcs8_testkey; + case FORMAT_PEM: return torture_dsa_private_testkey; - case SSH_KEYTYPE_RSA: - if (pubkey) { - if (format == 1) { - return torture_rsa_public_testkey_pem; - } else { - 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 NULL; + case SSH_KEYTYPE_RSA: + switch (format) { + case FORMAT_OPENSSH: + return torture_rsa_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_rsa_private_pkcs8_testkey; + case FORMAT_PEM: return torture_rsa_private_testkey; - case SSH_KEYTYPE_ECDSA_P521: - if (pubkey) { - if (format == 1) { - return torture_ecdsa521_public_testkey_pem; - } else { - 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 NULL; + case SSH_KEYTYPE_ECDSA_P521: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa521_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_ecdsa521_private_pkcs8_testkey; + case FORMAT_PEM: return torture_ecdsa521_private_testkey; - case SSH_KEYTYPE_ECDSA_P384: - if (pubkey) { - if (format == 1) { - return torture_ecdsa384_public_testkey_pem; - } else { - 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 NULL; + case SSH_KEYTYPE_ECDSA_P384: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa384_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_ecdsa384_private_pkcs8_testkey; + case FORMAT_PEM: return torture_ecdsa384_private_testkey; - case SSH_KEYTYPE_ECDSA_P256: - if (pubkey) { - if (format == 1) { - return torture_ecdsa256_public_testkey_pem; - } else { - 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 NULL; + case SSH_KEYTYPE_ECDSA_P256: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa256_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_ecdsa256_private_pkcs8_testkey; + case FORMAT_PEM: 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 NULL; + case SSH_KEYTYPE_ED25519: + switch (format) { + case FORMAT_OPENSSH: + return torture_ed25519_private_openssh_testkey; + case FORMAT_PKCS8: 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_SK_ECDSA: - case SSH_KEYTYPE_SK_ECDSA_CERT01: - case SSH_KEYTYPE_SK_ED25519: - case SSH_KEYTYPE_SK_ED25519_CERT01: - case SSH_KEYTYPE_UNKNOWN: + case FORMAT_PEM: + /* ed25519 keys are not available in legacy PEM format */ return NULL; + } + return NULL; + 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_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + 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) +const char * +torture_get_openssh_testkey(enum ssh_keytypes_e type, bool with_passphrase) { - return torture_get_testkey_internal(type, with_passphrase, 0, 1); + if (with_passphrase) { + return torture_get_testkey_encrypted_internal(type, FORMAT_OPENSSH); + } else { + return torture_get_testkey_internal(type, FORMAT_OPENSSH); + } } /* Return the private key in PEM format */ -const char *torture_get_testkey(enum ssh_keytypes_e type, - bool with_passphrase) +const char * +torture_get_testkey(enum ssh_keytypes_e type, bool with_passphrase) { + enum torture_format_e format = FORMAT_PEM; + + if (with_passphrase) { +/* This is the new PKCS8 PEM format, which works only in OpenSSL */ #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); + format = FORMAT_PKCS8; #endif + return torture_get_testkey_encrypted_internal(type, format); + } else { +/* The unencrypted format works also in mbedTLS */ +#if defined(HAVE_LIBCRYPTO) || defined(HAVE_LIBMBEDCRYPTO) + format = FORMAT_PKCS8; +#endif + return torture_get_testkey_internal(type, format); + } } -const char *torture_get_testkey_pub(enum ssh_keytypes_e type) +const char * +torture_get_testkey_pub(enum ssh_keytypes_e type) { - return torture_get_testkey_internal(type, 0, 1, 0); + return torture_get_testkey_public_internal(type, FORMAT_OPENSSH); } -const char *torture_get_testkey_pub_pem(enum ssh_keytypes_e type) +const char * +torture_get_testkey_pub_pem(enum ssh_keytypes_e type) { - return torture_get_testkey_internal(type, 0, 1, 1); + return torture_get_testkey_public_internal(type, FORMAT_PEM); } -const char *torture_get_testkey_passphrase(void) +const char * +torture_get_testkey_passphrase(void) { return TORTURE_TESTKEY_PASSWORD; } diff --git a/tests/torture_pki.c b/tests/torture_pki.c index cbf64951..f5420946 100644 --- a/tests/torture_pki.c +++ b/tests/torture_pki.c @@ -9,15 +9,10 @@ #ifdef HAVE_UNISTD_H #include <unistd.h> -#elif (defined _WIN32) || (defined _WIN64) -#include <io.h> -#define read _read -#define open _open -#define write _write -#define close _close #endif #include "torture_pki.h" +#include <libssh/priv.h> char *torture_pki_read_file(const char *filename) { diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt index 3ce3f10a..c053e5b8 100644 --- a/tests/unittests/CMakeLists.txt +++ b/tests/unittests/CMakeLists.txt @@ -1,8 +1,7 @@ project(unittests C) -include_directories(${OPENSSL_INCLUDE_DIR}) - set(LIBSSH_UNIT_TESTS + torture_bignum torture_buffer torture_bytearray torture_callbacks @@ -30,6 +29,12 @@ set(LIBSSH_THREAD_UNIT_TESTS torture_threads_crypto ) +set(TORTURE_UNIT_ENVIRONMENT + "LSAN_OPTIONS=suppressions=${libssh-tests_SOURCE_DIR}/suppressions/lsan.supp;") +if (OPENSSL_FOUND) + list(APPEND TORTURE_UNIT_ENVIRONMENT OPENSSL_ENABLE_SHA1_SIGNATURES=1) +endif (OPENSSL_FOUND) + if (UNIX AND NOT WIN32) set(LIBSSH_UNIT_TESTS ${LIBSSH_UNIT_TESTS} @@ -39,6 +44,7 @@ if (UNIX AND NOT WIN32) torture_keyfiles torture_pki torture_pki_rsa + torture_pki_dsa torture_pki_ed25519 # requires /dev/null torture_channel @@ -56,20 +62,13 @@ if (UNIX AND NOT WIN32) endif() endif() - - if (HAVE_DSA) - set(LIBSSH_UNIT_TESTS - ${LIBSSH_UNIT_TESTS} - torture_pki_dsa - ) - endif() - if (WITH_PKCS11_URI) set(LIBSSH_UNIT_TESTS ${LIBSSH_UNIT_TESTS} torture_pki_rsa_uri torture_pki_ecdsa_uri ) + list(APPEND TORTURE_UNIT_ENVIRONMENT PKCS11_PROVIDER_DEBUG=file:/tmp/p11prov-debug.log) endif() if (HAVE_ECC) @@ -84,10 +83,12 @@ if (UNIX AND NOT WIN32) # 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) + if (WITH_SERVER) + set(LIBSSH_THREAD_UNIT_TESTS + ${LIBSSH_THREAD_UNIT_TESTS} + torture_unit_server + ) + endif (WITH_SERVER) endif (UNIX AND NOT WIN32) foreach(_UNIT_TEST ${LIBSSH_UNIT_TESTS}) @@ -96,6 +97,10 @@ foreach(_UNIT_TEST ${LIBSSH_UNIT_TESTS}) COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} LINK_LIBRARIES ${TEST_TARGET_LIBRARIES} ) + + set_property(TEST ${_UNIT_TEST} + PROPERTY + ENVIRONMENT ${TORTURE_UNIT_ENVIRONMENT}) endforeach() if (CMAKE_USE_PTHREADS_INIT) @@ -105,6 +110,10 @@ if (CMAKE_USE_PTHREADS_INIT) COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} LINK_LIBRARIES ${TEST_TARGET_LIBRARIES} Threads::Threads ) + + set_property(TEST ${_UNIT_TEST} + PROPERTY + ENVIRONMENT ${TORTURE_UNIT_ENVIRONMENT}) endforeach() endif () diff --git a/tests/unittests/hello world.sh b/tests/unittests/hello world.sh new file mode 100755 index 00000000..8f687028 --- /dev/null +++ b/tests/unittests/hello world.sh @@ -0,0 +1,2 @@ +#!/bin/sh +printf '%s' "$1" 2>&1 diff --git a/tests/unittests/torture_bignum.c b/tests/unittests/torture_bignum.c new file mode 100644 index 00000000..c36b81f8 --- /dev/null +++ b/tests/unittests/torture_bignum.c @@ -0,0 +1,106 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/bignum.h" +#include "libssh/string.h" + +static void check_str (int n, ssh_string str) +{ + if (n > 0 && n <= 127) { + assert_int_equal(1, ntohl (str->size)); + assert_int_equal(n, str->data[0]); + } else if (n > 127 && n <= 255) { + assert_int_equal(2, ntohl (str->size)); + assert_int_equal(0, str->data[0]); + assert_int_equal(n, str->data[1]); + } else if (n > 255 && n <= 32767) { + assert_int_equal(2, ntohl (str->size)); + assert_int_equal(n >> 8, str->data[0]); + assert_int_equal(n & 0xFF, str->data[1]); + } else { + assert_int_equal(3, ntohl (str->size)); + assert_int_equal(n >> 16, str->data[0]); + assert_int_equal((n >> 8) & 0xFF, str->data[1]); + assert_int_equal(n & 0xFF, str->data[2]); + } +} + +static void check_bignum(int n, const char *nstr) { + bignum num, num2; + ssh_string str; + char *dec; + + num = bignum_new(); + assert_non_null(num); + + assert_int_equal (1, bignum_set_word (num, n)); + + ssh_print_bignum("num", num); + + dec = bignum_bn2dec (num); + assert_non_null (dec); + assert_string_equal (nstr, dec); + ssh_crypto_free(dec); + + /* ssh_make_bignum_string */ + + str = ssh_make_bignum_string(num); + assert_non_null(str); + + check_str (n, str); + + /* ssh_make_string_bn */ + + num2 = ssh_make_string_bn(str); + ssh_string_free (str); + assert_non_null(num2); + + ssh_print_bignum("num2", num2); + + assert_int_equal (0, bignum_cmp (num, num2)); + + dec = bignum_bn2dec (num2); + assert_non_null (dec); + assert_string_equal (nstr, dec); + ssh_crypto_free(dec); + + bignum_safe_free(num); + bignum_safe_free(num2); +} + + +static void torture_bignum(void **state) { + (void) state; /* unused */ + + ssh_set_log_level(SSH_LOG_TRACE); + + check_bignum (1, "1"); + check_bignum (17, "17"); + check_bignum (42, "42"); + check_bignum (127, "127"); + check_bignum (128, "128"); + check_bignum (254, "254"); + check_bignum (255, "255"); + check_bignum (256, "256"); + check_bignum (257, "257"); + check_bignum (300, "300"); + check_bignum (32767, "32767"); + check_bignum (32768, "32768"); + check_bignum (65535, "65535"); + check_bignum (65536, "65536"); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_bignum), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_bind_config.c b/tests/unittests/torture_bind_config.c index 11d8672e..a2f9be06 100644 --- a/tests/unittests/torture_bind_config.c +++ b/tests/unittests/torture_bind_config.c @@ -54,9 +54,6 @@ extern LIBSSH_THREAD int ssh_log_level; #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 @@ -64,37 +61,94 @@ extern LIBSSH_THREAD int ssh_log_level; #endif #define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS "libssh_test_bind_config_listenaddress" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_STRING "ListenAddress "LISTEN_ADDRESS"\n" #define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2 "libssh_test_bind_config_listenaddress2" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2_STRING "ListenAddress "LISTEN_ADDRESS2"\n" #define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE "libssh_test_bind_config_listenaddress_twice" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_STRING \ + "ListenAddress "LISTEN_ADDRESS"\n" \ + "ListenAddress "LISTEN_ADDRESS2"\n" #define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC "libssh_test_bind_config_listenaddress_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC_STRING \ + "ListenAddress "LISTEN_ADDRESS"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2"\n" #define LIBSSH_TEST_BIND_CONFIG_PORT "libssh_test_bind_config_port" +#define LIBSSH_TEST_BIND_CONFIG_PORT_STRING "Port 123\n" #define LIBSSH_TEST_BIND_CONFIG_PORT2 "libssh_test_bind_config_port2" +#define LIBSSH_TEST_BIND_CONFIG_PORT2_STRING "Port 456\n" #define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE "libssh_test_bind_config_port_twice" +#define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_STRING \ + "Port 123\n" \ + "Port 456\n" #define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC "libssh_test_bind_config_port_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC_STRING \ + "Port 123\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_PORT2"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY "libssh_test_bind_config_hostkey" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_STRING "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY2 "libssh_test_bind_config_hostkey2" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY2_STRING "HostKey "LIBSSH_RSA_TESTKEY"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE "libssh_test_bind_config_hostkey_twice" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_STRING \ + "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" \ + "HostKey "LIBSSH_RSA_TESTKEY"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC "libssh_test_bind_config_hostkey_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC_STRING \ + "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_HOSTKEY2"\n" #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_STRING "LogLevel "LOGLEVEL"\n" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1 "libssh_test_bind_config_loglevel2" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1_STRING "LogLevel "LOGLEVEL2"\n" #define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE "libssh_test_bind_config_loglevel_twice" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_STRING \ + "LogLevel "LOGLEVEL"\n" \ + "LogLevel "LOGLEVEL2"\n" #define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC "libssh_test_bind_config_loglevel_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC_STRING \ + "LogLevel "LOGLEVEL"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1"\n" #define LIBSSH_TEST_BIND_CONFIG_CIPHERS "libssh_test_bind_config_ciphers" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_STRING "Ciphers "CIPHERS"\n" #define LIBSSH_TEST_BIND_CONFIG_CIPHERS2 "libssh_test_bind_config_ciphers2" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS2_STRING "Ciphers "CIPHERS2"\n" #define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE "libssh_test_bind_config_ciphers_twice" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_STRING \ + "Ciphers "CIPHERS"\n" \ + "Ciphers "CIPHERS2"\n" #define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC "libssh_test_bind_config_ciphers_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC_STRING \ + "Ciphers "CIPHERS"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_CIPHERS2"\n" #define LIBSSH_TEST_BIND_CONFIG_MACS "libssh_test_bind_config_macs" +#define LIBSSH_TEST_BIND_CONFIG_MACS_STRING "MACs "MACS"\n" #define LIBSSH_TEST_BIND_CONFIG_MACS2 "libssh_test_bind_config_macs2" +#define LIBSSH_TEST_BIND_CONFIG_MACS2_STRING "MACs "MACS2"\n" #define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE "libssh_test_bind_config_macs_twice" +#define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_STRING \ + "MACs "MACS"\n" \ + "MACs "MACS2"\n" #define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC "libssh_test_bind_config_macs_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC_STRING \ + "MACs "MACS"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_MACS2"\n" #define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS "libssh_test_bind_config_kexalgorithms" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_STRING "KexAlgorithms "KEXALGORITHMS"\n" #define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2 "libssh_test_bind_config_kexalgorithms2" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2_STRING "KexAlgorithms "KEXALGORITHMS2"\n" #define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE "libssh_test_bind_config_kexalgorithms_twice" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_STRING \ + "KexAlgorithms "KEXALGORITHMS"\n" \ + "KexAlgorithms "KEXALGORITHMS2"\n" #define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC "libssh_test_bind_config_kexalgorithms_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC_STRING \ + "KexAlgorithms "KEXALGORITHMS"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2"\n" #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_INCLUDE_RECURSIVE_LOOP "libssh_test_bind_config_include_recursive_loop" #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" @@ -106,16 +160,34 @@ extern LIBSSH_THREAD int ssh_log_level; #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_ACCEPTED_STRING "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" #define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2 "libssh_test_bind_config_pubkey2" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2_STRING "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n" #define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE "libssh_test_bind_config_pubkey_twice" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_STRING \ + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" \ + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n" #define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC "libssh_test_bind_config_pubkey_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC_STRING \ + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n" #define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN "libssh_test_bind_config_pubkey_unknown" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN_STRING "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES_UNKNOWN"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS "libssh_test_bind_config_hostkey_alg" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_STRING "HostKeyAlgorithms "HOSTKEYALGORITHMS"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2 "libssh_test_bind_config_hostkey_alg2" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2_STRING "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE "libssh_test_bind_config_hostkey_alg_twice" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_STRING \ + "HostKeyAlgorithms "HOSTKEYALGORITHMS"\n" \ + "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC "libssh_test_bind_config_hostkey_alg_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC_STRING \ + "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN "libssh_test_bind_config_hostkey_alg_unknown" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN_STRING "HostKeyAlgorithms "HOSTKEYALGORITHMS_UNKNOWN"\n" const char template[] = "temp_dir_XXXXXX"; @@ -162,87 +234,69 @@ static int setup_config_files(void **state) 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"); + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2, - "ListenAddress "LISTEN_ADDRESS2"\n"); + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE, - "ListenAddress "LISTEN_ADDRESS"\n" - "ListenAddress "LISTEN_ADDRESS2"\n"); + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC, - "ListenAddress "LISTEN_ADDRESS"\n" - "Include "LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2"\n"); + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT, - "Port 123\n"); + LIBSSH_TEST_BIND_CONFIG_PORT_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT2, - "Port 456\n"); + LIBSSH_TEST_BIND_CONFIG_PORT2_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT_TWICE, - "Port 123\n" - "Port 456\n"); + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC, - "Port 123\n" - "Include "LIBSSH_TEST_BIND_CONFIG_PORT2"\n"); + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY, - "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY2, - "HostKey "LIBSSH_RSA_TESTKEY"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY2_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE, - "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" - "HostKey "LIBSSH_RSA_TESTKEY"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC, - "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" - "Include "LIBSSH_TEST_BIND_CONFIG_HOSTKEY2"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL, - "LogLevel "LOGLEVEL"\n"); - torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL2, - "LogLevel "LOGLEVEL2"\n"); + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE, - "LogLevel "LOGLEVEL"\n" - "LogLevel "LOGLEVEL2"\n"); + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC, - "LogLevel "LOGLEVEL"\n" - "Include "LIBSSH_TEST_BIND_CONFIG_LOGLEVEL2"\n"); + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS, - "Ciphers "CIPHERS"\n"); + LIBSSH_TEST_BIND_CONFIG_CIPHERS_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS2, - "Ciphers "CIPHERS2"\n"); + LIBSSH_TEST_BIND_CONFIG_CIPHERS2_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE, - "Ciphers "CIPHERS"\n" - "Ciphers "CIPHERS2"\n"); + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC, - "Ciphers "CIPHERS"\n" - "Include "LIBSSH_TEST_BIND_CONFIG_CIPHERS2"\n"); + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS, - "MACs "MACS"\n"); + LIBSSH_TEST_BIND_CONFIG_MACS_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS2, - "MACs "MACS2"\n"); + LIBSSH_TEST_BIND_CONFIG_MACS2_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS_TWICE, - "MACs "MACS"\n" - "MACs "MACS2"\n"); + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC, - "MACs "MACS"\n" - "Include "LIBSSH_TEST_BIND_CONFIG_MACS2"\n"); + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS, - "KexAlgorithms "KEXALGORITHMS"\n"); + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2, - "KexAlgorithms "KEXALGORITHMS2"\n"); + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE, - "KexAlgorithms "KEXALGORITHMS"\n" - "KexAlgorithms "KEXALGORITHMS2"\n"); + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC, - "KexAlgorithms "KEXALGORITHMS"\n" - "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2"\n"); + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_FULL, "ListenAddress "LISTEN_ADDRESS"\n" @@ -265,6 +319,9 @@ static int setup_config_files(void **state) torture_write_file(LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE, "Include "LIBSSH_TEST_BIND_CONFIG_INCLUDE"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE_LOOP, + "Include "LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE_LOOP"\n"); + /* Unsupported options and corner cases */ torture_write_file(LIBSSH_TEST_BIND_CONFIG_CORNER_CASES, "\n" /* empty line */ @@ -334,30 +391,26 @@ static int setup_config_files(void **state) "\tLogLevel "LOGLEVEL4"\n"); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED, - "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n"); + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2, - "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n"); + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE, - "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" - "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n"); + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC, - "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n" - "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n"); + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN, - "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES_UNKNOWN"\n"); + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS, - "HostKeyAlgorithms "HOSTKEYALGORITHMS"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2, - "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE, - "HostKeyAlgorithms "HOSTKEYALGORITHMS"\n" - "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC, - "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n" - "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN, - "HostKeyAlgorithms "HOSTKEYALGORITHMS_UNKNOWN"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN_STRING); return 0; } @@ -405,11 +458,43 @@ static int sshbind_teardown(void **state) return 0; } -static void torture_bind_config_listen_address(void **state) +/** + * @brief helper function loading configuration from either file or string + */ +static void +_parse_config(ssh_bind bind, + const char *file, + const char *string, + int expected) +{ + int ret = -1; + + /* make sure either config file or config string is given, + * not both */ + assert_int_not_equal(file == NULL, string == NULL); + + if (file != NULL) { + ret = ssh_bind_config_parse_file(bind, file); + } else if (string != NULL) { + ret = ssh_bind_config_parse_string(bind, string); + } else { + /* should not happen */ + fail(); + } + + /* make sure parsing went as expected */ + assert_return_code(ret, expected); +} + + +static void +torture_bind_config_listen_address(void **state, + const char *file, + const char *string, + const char *expect) { struct bind_st *test_state; ssh_bind bind; - int rc; assert_non_null(state); test_state = *((struct bind_st **)state); @@ -417,37 +502,84 @@ static void torture_bind_config_listen_address(void **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); + _parse_config(bind, file, string, SSH_OK); - 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); + assert_string_equal(bind->bindaddr, expect); +} - 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); +static void torture_bind_config_listen_address_file(void **state) +{ + torture_bind_config_listen_address(state, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS, + NULL, + 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_listen_address_string(void **state) +{ + torture_bind_config_listen_address(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_STRING, + LISTEN_ADDRESS); +} + +static void torture_bind_config_listen_address2_file(void **state) +{ + torture_bind_config_listen_address(state, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2, + NULL, + LISTEN_ADDRESS2); +} + +static void torture_bind_config_listen_address2_string(void **state) +{ + torture_bind_config_listen_address(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2_STRING, + LISTEN_ADDRESS2); +} + +static void torture_bind_config_listen_address_twice_file(void **state) +{ + torture_bind_config_listen_address(state, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE, + NULL, + LISTEN_ADDRESS); +} + +static void torture_bind_config_listen_address_twice_string(void **state) +{ + torture_bind_config_listen_address(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_STRING, + LISTEN_ADDRESS); +} + +static void torture_bind_config_listen_address_twice_rec_file(void **state) +{ + torture_bind_config_listen_address(state, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC, + NULL, + LISTEN_ADDRESS); +} +static void torture_bind_config_listen_address_twice_rec_string(void **state) +{ + torture_bind_config_listen_address(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC_STRING, + LISTEN_ADDRESS); } -static void torture_bind_config_port(void **state) +static void +torture_bind_config_port(void **state, + const char *file, + const char *string, + int expect) { struct bind_st *test_state; ssh_bind bind; - int rc; assert_non_null(state); test_state = *((struct bind_st **)state); @@ -455,29 +587,82 @@ static void torture_bind_config_port(void **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); + _parse_config(bind, file, string, SSH_OK); - rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_PORT_TWICE); - assert_int_equal(rc, 0); - assert_int_equal(bind->bindport, 123); + assert_int_equal(bind->bindport, expect); +} - 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); +static void torture_bind_config_port_file(void **state) +{ + torture_bind_config_port(state, + LIBSSH_TEST_BIND_CONFIG_PORT, + NULL, + 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_port_string(void **state) +{ + torture_bind_config_port(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PORT_STRING, + 123); +} + +static void torture_bind_config_port2_file(void **state) +{ + torture_bind_config_port(state, + LIBSSH_TEST_BIND_CONFIG_PORT2, + NULL, + 456); +} + +static void torture_bind_config_port2_string(void **state) +{ + torture_bind_config_port(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PORT2_STRING, + 456); +} + +static void torture_bind_config_port_twice_file(void **state) +{ + torture_bind_config_port(state, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE, + NULL, + 123); +} + +static void torture_bind_config_port_twice_string(void **state) +{ + torture_bind_config_port(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_STRING, + 123); +} + +static void torture_bind_config_port_twice_rec_file(void **state) +{ + torture_bind_config_port(state, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC, + NULL, + 123); +} + +static void torture_bind_config_port_twice_rec_string(void **state) +{ + torture_bind_config_port(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC_STRING, + 123); } -static void torture_bind_config_hostkey(void **state) +static void +torture_bind_config_hostkey(void **state, + const char *file, + const char *string) { struct bind_st *test_state; ssh_bind bind; - int rc; assert_non_null(state); test_state = *((struct bind_st **)state); @@ -485,25 +670,19 @@ static void torture_bind_config_hostkey(void **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); + _parse_config(bind, file, string, SSH_OK); - 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) +static void +torture_bind_config_hostkey2(void **state, + const char *file, + const char *string) { struct bind_st *test_state; ssh_bind bind; - int rc; assert_non_null(state); test_state = *((struct bind_st **)state); @@ -511,15 +690,44 @@ static void torture_bind_config_hostkey_twice_rec(void **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); + _parse_config(bind, file, string, SSH_OK); + 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_file(void **state) +{ + torture_bind_config_hostkey(state, LIBSSH_TEST_BIND_CONFIG_HOSTKEY, NULL); +} + +static void torture_bind_config_hostkey_string(void **state) +{ + torture_bind_config_hostkey(state, NULL, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_STRING); +} + +static void torture_bind_config_hostkey_twice_file(void **state) +{ + torture_bind_config_hostkey2(state, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE, NULL); +} + +static void torture_bind_config_hostkey_twice_string(void **state) +{ + torture_bind_config_hostkey2(state, NULL, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_STRING); +} + +static void torture_bind_config_hostkey_twice_rec_file(void **state) +{ + torture_bind_config_hostkey2(state, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC, NULL); +} + +static void torture_bind_config_hostkey_twice_rec_string(void **state) +{ + torture_bind_config_hostkey2(state, NULL, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC_STRING); +} + static void torture_bind_config_hostkey_separately(void **state) { struct bind_st *test_state; @@ -545,12 +753,17 @@ static void torture_bind_config_hostkey_separately(void **state) assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); } -static void torture_bind_config_loglevel(void **state) +static void +torture_bind_config_loglevel(void **state, + const char *file, + const char *string, + int expect) { struct bind_st *test_state; ssh_bind bind; - int rc; - int previous_level, new_level; + int previous_level, new_level, rc; + + previous_level = ssh_get_log_level(); assert_non_null(state); test_state = *((struct bind_st **)state); @@ -558,45 +771,88 @@ static void torture_bind_config_loglevel(void **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); + _parse_config(bind, file, string, SSH_OK); new_level = ssh_get_log_level(); - assert_int_equal(new_level, 2); + assert_int_equal(new_level, expect); - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE); - assert_int_equal(rc, 0); + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} - new_level = ssh_get_log_level(); - assert_int_equal(new_level, 2); +static void torture_bind_config_loglevel_file(void **state) +{ + torture_bind_config_loglevel(state, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL, + NULL, + 2); +} - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC); - assert_int_equal(rc, 0); +static void torture_bind_config_loglevel_string(void **state) +{ + torture_bind_config_loglevel(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_STRING, + 2); +} - new_level = ssh_get_log_level(); - assert_int_equal(new_level, 2); +static void torture_bind_config_loglevel1_file(void **state) +{ + torture_bind_config_loglevel(state, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1, + NULL, + 1); +} - rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_LOGLEVEL2); - assert_int_equal(rc, 0); +static void torture_bind_config_loglevel1_string(void **state) +{ + torture_bind_config_loglevel(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1_STRING, + 1); +} - new_level = ssh_get_log_level(); - assert_int_equal(new_level, 1); +static void torture_bind_config_loglevel_twice_file(void **state) +{ + torture_bind_config_loglevel(state, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE, + NULL, + 2); +} - rc = ssh_set_log_level(previous_level); - assert_int_equal(rc, SSH_OK); +static void torture_bind_config_loglevel_twice_string(void **state) +{ + torture_bind_config_loglevel(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_STRING, + 2); +} + +static void torture_bind_config_loglevel_twice_rec_file(void **state) +{ + torture_bind_config_loglevel(state, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC, + NULL, + 2); +} + +static void torture_bind_config_loglevel_twice_rec_string(void **state) +{ + torture_bind_config_loglevel(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC_STRING, + 2); } -static void torture_bind_config_ciphers(void **state) +static void +torture_bind_config_ciphers(void **state, + const char *file, + const char *string, + const char *expect) { 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); @@ -605,73 +861,96 @@ static void torture_bind_config_ciphers(void **state) bind = test_state->bind; if (ssh_fips_mode()) { - fips_ciphers = ssh_keep_fips_algos(SSH_CRYPT_C_S, CIPHERS); + fips_ciphers = ssh_keep_fips_algos(SSH_CRYPT_C_S, expect); 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); - } + _parse_config(bind, file, string, SSH_OK); - 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); + SAFE_FREE(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); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], expect); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], expect); } +} - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC); - assert_int_equal(rc, 0); +static void torture_bind_config_ciphers_file(void **state) +{ + torture_bind_config_ciphers(state, + LIBSSH_TEST_BIND_CONFIG_CIPHERS, + NULL, + CIPHERS); +} - 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); - } +static void torture_bind_config_ciphers_string(void **state) +{ + torture_bind_config_ciphers(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_STRING, + CIPHERS); +} - rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_CIPHERS2); - assert_int_equal(rc, 0); +static void torture_bind_config_ciphers2_file(void **state) +{ + torture_bind_config_ciphers(state, + LIBSSH_TEST_BIND_CONFIG_CIPHERS2, + NULL, + CIPHERS2); +} - 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); - } +static void torture_bind_config_ciphers2_string(void **state) +{ + torture_bind_config_ciphers(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_CIPHERS2_STRING, + CIPHERS2); +} - SAFE_FREE(fips_ciphers); - SAFE_FREE(fips_ciphers2); +static void torture_bind_config_ciphers_twice_file(void **state) +{ + torture_bind_config_ciphers(state, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE, + NULL, + CIPHERS); +} + +static void torture_bind_config_ciphers_twice_string(void **state) +{ + torture_bind_config_ciphers(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_STRING, + CIPHERS); +} + +static void torture_bind_config_ciphers_twice_rec_file(void **state) +{ + torture_bind_config_ciphers(state, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC, + NULL, + CIPHERS); } -static void torture_bind_config_macs(void **state) +static void torture_bind_config_ciphers_twice_rec_string(void **state) +{ + torture_bind_config_ciphers(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC_STRING, + CIPHERS); +} + +static void +torture_bind_config_macs(void **state, + const char *file, + const char *string, + const char *expect) { struct bind_st *test_state; ssh_bind bind; - int rc; assert_non_null(state); test_state = *((struct bind_st **)state); @@ -679,59 +958,87 @@ static void torture_bind_config_macs(void **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); + _parse_config(bind, file, string, SSH_OK); 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_string_equal(bind->wanted_methods[SSH_MAC_C_S], expect); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], expect); +} - assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); - assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], MACS); +static void torture_bind_config_macs_file(void **state) +{ + torture_bind_config_macs(state, + LIBSSH_TEST_BIND_CONFIG_MACS, + NULL, + MACS); +} - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC); - assert_int_equal(rc, 0); +static void torture_bind_config_macs_string(void **state) +{ + torture_bind_config_macs(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_MACS_STRING, + MACS); +} - assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); - assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], MACS); +static void torture_bind_config_macs2_file(void **state) +{ + torture_bind_config_macs(state, + LIBSSH_TEST_BIND_CONFIG_MACS2, + NULL, + MACS2); +} - assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); - assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], MACS); +static void torture_bind_config_macs2_string(void **state) +{ + torture_bind_config_macs(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_MACS2_STRING, + MACS2); +} - rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_MACS2); - assert_int_equal(rc, 0); +static void torture_bind_config_macs_twice_file(void **state) +{ + torture_bind_config_macs(state, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE, + NULL, + MACS); +} - assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); - assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], MACS2); +static void torture_bind_config_macs_twice_string(void **state) +{ + torture_bind_config_macs(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_STRING, + MACS); +} - 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_macs_twice_rec_file(void **state) +{ + torture_bind_config_macs(state, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC, + NULL, + MACS); } -static void torture_bind_config_kexalgorithms(void **state) +static void torture_bind_config_macs_twice_rec_string(void **state) +{ + torture_bind_config_macs(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC_STRING, + MACS); +} + +static void +torture_bind_config_kexalgorithms(void **state, + const char *file, + const char *string, + const char *expect) { 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); @@ -739,64 +1046,95 @@ static void torture_bind_config_kexalgorithms(void **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); + fips_kex = ssh_keep_fips_algos(SSH_KEX, expect); + assert_non_null(fips_kex); } - 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); - } + _parse_config(bind, file, string, SSH_OK); - 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); + SAFE_FREE(fips_kex); } else { - assert_string_equal(bind->wanted_methods[SSH_KEX], KEXALGORITHMS); + assert_string_equal(bind->wanted_methods[SSH_KEX], expect); } +} - 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); - } +static void torture_bind_config_kexalgorithms_file(void **state) +{ + torture_bind_config_kexalgorithms(state, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS, + NULL, + KEXALGORITHMS); +} - SAFE_FREE(fips_kex); - SAFE_FREE(fips_kex2); +static void torture_bind_config_kexalgorithms_string(void **state) +{ + torture_bind_config_kexalgorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_STRING, + KEXALGORITHMS); +} + +static void torture_bind_config_kexalgorithms2_file(void **state) +{ + torture_bind_config_kexalgorithms(state, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2, + NULL, + KEXALGORITHMS2); +} + +static void torture_bind_config_kexalgorithms2_string(void **state) +{ + torture_bind_config_kexalgorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2_STRING, + KEXALGORITHMS2); } -static void torture_bind_config_pubkey_accepted(void **state) +static void torture_bind_config_kexalgorithms_twice_file(void **state) +{ + torture_bind_config_kexalgorithms(state, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE, + NULL, + KEXALGORITHMS); +} + +static void torture_bind_config_kexalgorithms_twice_string(void **state) +{ + torture_bind_config_kexalgorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_STRING, + KEXALGORITHMS); +} + +static void torture_bind_config_kexalgorithms_twice_rec_file(void **state) +{ + torture_bind_config_kexalgorithms(state, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC, + NULL, + KEXALGORITHMS); +} + +static void torture_bind_config_kexalgorithms_twice_rec_string(void **state) +{ + torture_bind_config_kexalgorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC_STRING, + KEXALGORITHMS); +} + +static void +torture_bind_config_pubkey_accepted(void **state, + const char *file, + const char *string, + const char *expect) { 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); @@ -804,75 +1142,111 @@ static void torture_bind_config_pubkey_accepted(void **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); + fips_pubkeys = ssh_keep_fips_algos(SSH_HOSTKEYS, expect); + assert_non_null(fips_pubkeys); } - 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); - } + _parse_config(bind, file, string, SSH_OK); - 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); + SAFE_FREE(fips_pubkeys); } else { - assert_string_equal(bind->pubkey_accepted_key_types, PUBKEYACCEPTEDTYPES); + assert_string_equal(bind->pubkey_accepted_key_types, expect); } +} - 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); - } +static void torture_bind_config_pubkey_accepted_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED, + NULL, + PUBKEYACCEPTEDTYPES); +} - 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); - } +static void torture_bind_config_pubkey_accepted_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_STRING, + PUBKEYACCEPTEDTYPES); +} - SAFE_FREE(fips_pubkeys); - SAFE_FREE(fips_pubkeys2); +static void torture_bind_config_pubkey_accepted_twice_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE, + NULL, + PUBKEYACCEPTEDTYPES); } -static void torture_bind_config_hostkey_algorithms(void **state) +static void torture_bind_config_pubkey_accepted_twice_string(void **state) { - struct bind_st *test_state; - ssh_bind bind; - int rc; + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_STRING, + PUBKEYACCEPTEDTYPES); +} + +static void torture_bind_config_pubkey_accepted_twice_rec_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC, + NULL, + PUBKEYACCEPTEDTYPES2); +} - char *fips_hostkeys = NULL; - char *fips_hostkeys2 = NULL; +static void torture_bind_config_pubkey_accepted_twice_rec_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC_STRING, + PUBKEYACCEPTEDTYPES2); +} - 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); - } +static void torture_bind_config_pubkey_accepted_unknown_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN, + NULL, + PUBKEYACCEPTEDTYPES); +} + +static void torture_bind_config_pubkey_accepted_unknown_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN_STRING, + PUBKEYACCEPTEDTYPES); +} + +static void torture_bind_config_pubkey_accepted2_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2, + NULL, + PUBKEYACCEPTEDTYPES2); +} + +static void torture_bind_config_pubkey_accepted2_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2_STRING, + PUBKEYACCEPTEDTYPES2); +} + +static void +torture_bind_config_hostkey_algorithms(void **state, + const char *file, + const char *string, + const char *expect) +{ + struct bind_st *test_state; + ssh_bind bind; + char *fips_hostkey = NULL; assert_non_null(state); test_state = *((struct bind_st **)state); @@ -880,58 +1254,100 @@ static void torture_bind_config_hostkey_algorithms(void **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); + fips_hostkey = ssh_keep_fips_algos(SSH_HOSTKEYS, expect); + assert_non_null(fips_hostkey); } - 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); - } + _parse_config(bind, file, string, SSH_OK); - 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); + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], fips_hostkey); + SAFE_FREE(fips_hostkey); } else { - assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], HOSTKEYALGORITHMS); + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], expect); } +} - 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); - } +static void torture_bind_config_hostkey_algorithms_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS, + NULL, + HOSTKEYALGORITHMS); +} - 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); - } +static void torture_bind_config_hostkey_algorithms_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_STRING, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_twice_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE, + NULL, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_twice_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_STRING, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_twice_rec_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC, + NULL, + HOSTKEYALGORITHMS2); +} + +static void torture_bind_config_hostkey_algorithms_twice_rec_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC_STRING, + HOSTKEYALGORITHMS2); +} + +static void torture_bind_config_hostkey_algorithms2_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2, + NULL, + HOSTKEYALGORITHMS2); +} - SAFE_FREE(fips_hostkeys); - SAFE_FREE(fips_hostkeys2); +static void torture_bind_config_hostkey_algorithms2_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2_STRING, + HOSTKEYALGORITHMS2); +} + +static void torture_bind_config_hostkey_algorithms_unknown_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN, + NULL, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_unknown_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN_STRING, + HOSTKEYALGORITHMS); } static int assert_full_bind_config(void **state) @@ -1076,6 +1492,23 @@ static void torture_bind_config_include_recursive(void **state) assert_int_equal(rc, SSH_OK); } +static void torture_bind_config_include_recursive_loop(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_INCLUDE_RECURSIVE_LOOP); + assert_int_equal(rc, 0); +} + /** * @brief Verify the configuration parser does not choke on unknown * or unsupported configuration options @@ -1263,23 +1696,115 @@ int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { - cmocka_unit_test_setup_teardown(torture_bind_config_listen_address, + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address2_file, sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_config_port, + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address2_string, sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_config_hostkey, + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_twice_file, sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_rec, + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_rec_string, 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, + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel1_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel1_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_file, sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_config_ciphers, + cmocka_unit_test_setup_teardown(torture_bind_config_macs_string, sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_config_macs, + cmocka_unit_test_setup_teardown(torture_bind_config_macs2_file, sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms, + cmocka_unit_test_setup_teardown(torture_bind_config_macs2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_twice_rec_string, sshbind_setup, sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_config_full, sshbind_setup, sshbind_teardown), @@ -1287,6 +1812,8 @@ int torture_run_tests(void) 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_include_recursive_loop, + 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, @@ -1301,9 +1828,45 @@ int torture_run_tests(void) 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, + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_unknown_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_unknown_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_unknown_file, sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms, + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_unknown_string, sshbind_setup, sshbind_teardown), }; diff --git a/tests/unittests/torture_callbacks.c b/tests/unittests/torture_callbacks.c index 85f4d1f4..25111b2f 100644 --- a/tests/unittests/torture_callbacks.c +++ b/tests/unittests/torture_callbacks.c @@ -43,7 +43,7 @@ static int teardown(void **state) } static void torture_callbacks_size(void **state) { - struct ssh_callbacks_struct *cb = *state;; + struct ssh_callbacks_struct *cb = *state; assert_int_not_equal(cb->size, 0); } diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c index 08b67deb..ebc2cdbd 100644 --- a/tests/unittests/torture_config.c +++ b/tests/unittests/torture_config.c @@ -2,11 +2,18 @@ #define LIBSSH_STATIC +#ifndef _WIN32 +#define _POSIX_PTHREAD_SEMANTICS +#include <pwd.h> +#endif + #include "torture.h" #include "libssh/options.h" #include "libssh/session.h" #include "libssh/config_parser.h" #include "match.c" +#include "config.c" +#include "libssh/socket.h" extern LIBSSH_THREAD int ssh_log_level; @@ -34,10 +41,16 @@ extern LIBSSH_THREAD int ssh_log_level; #define LIBSSH_TESTCONFIG10 "libssh_testconfig10.tmp" #define LIBSSH_TESTCONFIG11 "libssh_testconfig11.tmp" #define LIBSSH_TESTCONFIG12 "libssh_testconfig12.tmp" +#define LIBSSH_TESTCONFIG14 "libssh_testconfig14.tmp" +#define LIBSSH_TESTCONFIG15 "libssh_testconfig15.tmp" +#define LIBSSH_TESTCONFIG16 "libssh_testconfig16.tmp" +#define LIBSSH_TESTCONFIG17 "libssh_testconfig17.tmp" #define LIBSSH_TESTCONFIGGLOB "libssh_testc*[36].tmp" #define LIBSSH_TEST_PUBKEYTYPES "libssh_test_PubkeyAcceptedKeyTypes.tmp" +#define LIBSSH_TEST_PUBKEYALGORITHMS "libssh_test_PubkeyAcceptedAlgorithms.tmp" #define LIBSSH_TEST_NONEWLINEEND "libssh_test_NoNewLineEnd.tmp" #define LIBSSH_TEST_NONEWLINEONELINE "libssh_test_NoNewLineOneline.tmp" +#define LIBSSH_TEST_RECURSIVE_INCLUDE "libssh_test_recursive_include.tmp" #define LIBSSH_TESTCONFIG_STRING1 \ "User "USERNAME"\nInclude "LIBSSH_TESTCONFIG2"\n\n" @@ -50,7 +63,7 @@ extern LIBSSH_THREAD int ssh_log_level; "\n\nIdentityFile "ID_FILE"\n" \ "\n\nKexAlgorithms "KEXALGORITHMS"\n" \ "\n\nHostKeyAlgorithms "HOSTKEYALGORITHMS"\n" \ - "\n\nPubkeyAcceptedTypes "PUBKEYACCEPTEDTYPES"\n" \ + "\n\nPubkeyAcceptedAlgorithms "PUBKEYACCEPTEDTYPES"\n" \ "\n\nMACs "MACS"\n" /* Multiple Port settings -> parsing returns early. */ @@ -168,14 +181,49 @@ extern LIBSSH_THREAD int ssh_log_level; "Host time4\n" \ "\tRekeyLimit default 9600\n" -/* Multiple IdentityFile settings all are aplied */ +/* Multiple IdentityFile settings all are applied */ #define LIBSSH_TESTCONFIG_STRING13 \ "IdentityFile id_rsa_one\n" \ - "IdentityFile id_ecdsa_two\n" + "CertificateFile id_rsa_one-cert.pub\n" \ + "IdentityFile id_ecdsa_two\n" \ + "CertificateFile id_ecdsa_two-cert.pub\n" \ + +/* +,-,^ features for all supported list */ +/* kex won't work in fips */ +#define LIBSSH_TESTCONFIG_STRING14 \ + "HostKeyAlgorithms +ssh-rsa\n" \ + "Ciphers +aes128-cbc,aes256-cbc\n" \ + "KexAlgorithms +diffie-hellman-group14-sha1,diffie-hellman-group1-sha1\n" \ + "MACs +hmac-sha1,hmac-sha1-etm@openssh.com\n" + +/* have to be algorithms which are in the default list */ +#define LIBSSH_TESTCONFIG_STRING15 \ + "HostKeyAlgorithms -rsa-sha2-512,rsa-sha2-256\n" \ + "Ciphers -aes256-ctr\n" \ + "KexAlgorithms -diffie-hellman-group18-sha512,diffie-hellman-group16-sha512\n" \ + "MACs -hmac-sha2-256-etm@openssh.com\n" + +#define LIBSSH_TESTCONFIG_STRING16 \ + "HostKeyAlgorithms ^rsa-sha2-512,rsa-sha2-256\n" \ + "Ciphers ^aes256-cbc\n" \ + "KexAlgorithms ^diffie-hellman-group18-sha512,diffie-hellman-group16-sha512\n" \ + "MACs ^hmac-sha1\n" + +/* Connection Multiplexing */ +#define LIBSSH_TESTCONFIG_STRING17 \ + "Host simple\n" \ + "\tControlMaster auto\n" \ + "\tControlPath /tmp/ssh-%r@%h:%p\n" \ + "Host none\n" \ + "\tControlMaster yes\n" \ + "\tControlPath none\n" #define LIBSSH_TEST_PUBKEYTYPES_STRING \ "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" +#define LIBSSH_TEST_PUBKEYALGORITHMS_STRING \ + "PubkeyAcceptedAlgorithms "PUBKEYACCEPTEDTYPES"\n" + #define LIBSSH_TEST_NONEWLINEEND_STRING \ "ConnectTimeout 30\n" \ "LogLevel DEBUG3" @@ -183,6 +231,9 @@ extern LIBSSH_THREAD int ssh_log_level; #define LIBSSH_TEST_NONEWLINEONELINE_STRING \ "ConnectTimeout 30" +#define LIBSSH_TEST_RECURSIVE_INCLUDE_STRING \ + "Include " LIBSSH_TEST_RECURSIVE_INCLUDE + /** * @brief helper function loading configuration from either file or string */ @@ -224,7 +275,12 @@ static int setup_config_files(void **state) unlink(LIBSSH_TESTCONFIG10); unlink(LIBSSH_TESTCONFIG11); unlink(LIBSSH_TESTCONFIG12); + unlink(LIBSSH_TESTCONFIG14); + unlink(LIBSSH_TESTCONFIG15); + unlink(LIBSSH_TESTCONFIG16); + unlink(LIBSSH_TESTCONFIG17); unlink(LIBSSH_TEST_PUBKEYTYPES); + unlink(LIBSSH_TEST_PUBKEYALGORITHMS); unlink(LIBSSH_TEST_NONEWLINEEND); unlink(LIBSSH_TEST_NONEWLINEONELINE); @@ -270,9 +326,22 @@ static int setup_config_files(void **state) torture_write_file(LIBSSH_TESTCONFIG12, LIBSSH_TESTCONFIG_STRING12); + /* +,-,^ feature */ + torture_write_file(LIBSSH_TESTCONFIG14, + LIBSSH_TESTCONFIG_STRING14); + torture_write_file(LIBSSH_TESTCONFIG15, + LIBSSH_TESTCONFIG_STRING15); + torture_write_file(LIBSSH_TESTCONFIG16, + LIBSSH_TESTCONFIG_STRING16); + torture_write_file(LIBSSH_TESTCONFIG17, + LIBSSH_TESTCONFIG_STRING17); + torture_write_file(LIBSSH_TEST_PUBKEYTYPES, LIBSSH_TEST_PUBKEYTYPES_STRING); + torture_write_file(LIBSSH_TEST_PUBKEYALGORITHMS, + LIBSSH_TEST_PUBKEYALGORITHMS_STRING); + torture_write_file(LIBSSH_TEST_NONEWLINEEND, LIBSSH_TEST_NONEWLINEEND_STRING); @@ -298,7 +367,12 @@ static int teardown_config_files(void **state) unlink(LIBSSH_TESTCONFIG10); unlink(LIBSSH_TESTCONFIG11); unlink(LIBSSH_TESTCONFIG12); + unlink(LIBSSH_TESTCONFIG14); + unlink(LIBSSH_TESTCONFIG15); + unlink(LIBSSH_TESTCONFIG16); + unlink(LIBSSH_TESTCONFIG17); unlink(LIBSSH_TEST_PUBKEYTYPES); + unlink(LIBSSH_TEST_PUBKEYALGORITHMS); return 0; } @@ -306,6 +380,25 @@ static int teardown_config_files(void **state) static int setup(void **state) { ssh_session session = NULL; + char *wd = NULL; + int verbosity; + + session = ssh_new(); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + wd = torture_get_current_working_dir(); + ssh_options_set(session, SSH_OPTIONS_SSH_DIR, wd); + free(wd); + + *state = session; + + return 0; +} + +static int setup_no_sshdir(void **state) +{ + ssh_session session = NULL; int verbosity; session = ssh_new(); @@ -418,6 +511,22 @@ static void torture_config_include_string(void **state) } /** + * @brief tests ssh_config_parse_file with recursive Include directives from file + */ +static void torture_config_include_recursive_file(void **state) +{ + _parse_config(*state, LIBSSH_TEST_RECURSIVE_INCLUDE, NULL, SSH_OK); +} + +/** + * @brief tests ssh_config_parse_string with Include directives from string + */ +static void torture_config_include_recursive_string(void **state) +{ + _parse_config(*state, NULL, LIBSSH_TEST_RECURSIVE_INCLUDE_STRING, SSH_OK); +} + +/** * @brief tests ssh_config_parse_file with multiple Port settings. */ static void torture_config_double_ports_file(void **state) @@ -487,12 +596,14 @@ static void torture_config_new(void ** state, 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"); + "zlib@openssh.com,none"); assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], - "zlib@openssh.com,zlib"); + "zlib@openssh.com,none"); #else - assert_null(session->opts.wanted_methods[SSH_COMP_C_S]); - assert_null(session->opts.wanted_methods[SSH_COMP_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none"); #endif /* WITH_ZLIB */ assert_int_equal(session->opts.StrictHostKeyChecking, 0); assert_int_equal(session->opts.gss_delegate_creds, 1); @@ -593,7 +704,7 @@ static void torture_config_unknown(void **state, /* test corner cases */ _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, - "ssh -W [%h]:%p many-spaces.com"); + "ssh -W '[%h]:%p' many-spaces.com"); assert_string_equal(session->opts.host, "equal.sign"); ret = ssh_config_parse_file(session, "/etc/ssh/ssh_config"); @@ -889,28 +1000,28 @@ static void torture_config_proxyjump(void **state, torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); _parse_config(session, file, string, SSH_OK); - assert_string_equal(session->opts.ProxyCommand, "ssh -W [%h]:%p jumpbox"); + 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"); _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, - "ssh -l user -W [%h]:%p jumpbox"); + "ssh -l user -W '[%h]:%p' jumpbox"); /* With port */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "port"); _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, - "ssh -p 2222 -W [%h]:%p jumpbox"); + "ssh -p 2222 -W '[%h]:%p' jumpbox"); /* Two step jump */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "two-step"); _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, - "ssh -l u1 -p 222 -J u2@second:33 -W [%h]:%p first"); + "ssh -l u1 -p 222 -J u2@second:33 -W '[%h]:%p' first"); /* none */ torture_reset_config(session); @@ -918,43 +1029,42 @@ static void torture_config_proxyjump(void **state, _parse_config(session, file, string, SSH_OK); assert_true(session->opts.ProxyCommand == NULL); - /* If also ProxyCommand is specifed, the first is applied */ + /* If also ProxyCommand is specified, the first is applied */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "only-command"); _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, PROXYCMD); - /* If also ProxyCommand is specifed, the first is applied */ + /* If also ProxyCommand is specified, the first is applied */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "only-jump"); _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, - "ssh -W [%h]:%p jumpbox"); + "ssh -W '[%h]:%p' jumpbox"); /* IPv6 address */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "ipv6"); _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, - "ssh -W [%h]:%p 2620:52:0::fed"); + "ssh -W '[%h]:%p' 2620:52:0::fed"); - /* In this part, we try various other config files and strings. */ - - /* Try to create some invalid configurations */ - /* Non-numeric port */ - config = "Host bad-port\n" - "\tProxyJump jumpbox:22bad22\n"; + /* Multiple @ is allowed in second jump */ + config = "Host allowed-hostname\n" + "\tProxyJump localhost,user@principal.com@jumpbox:22\n"; if (file != NULL) { torture_write_file(file, config); } else { string = config; } torture_reset_config(session); - ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port"); - _parse_config(session, file, string, SSH_ERROR); + ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -J user@principal.com@jumpbox:22 -W '[%h]:%p' localhost"); - /* Too many @ */ - config = "Host bad-hostname\n" + /* Multiple @ is allowed */ + config = "Host allowed-hostname\n" "\tProxyJump user@principal.com@jumpbox:22\n"; if (file != NULL) { torture_write_file(file, config); @@ -962,7 +1072,24 @@ static void torture_config_proxyjump(void **state, string = config; } torture_reset_config(session); - ssh_options_set(session, SSH_OPTIONS_HOST, "bad-hostname"); + ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -l user@principal.com -p 22 -W '[%h]:%p' jumpbox"); + + /* In this part, we try various other config files and strings. */ + + /* Try to create some invalid configurations */ + /* Non-numeric port */ + config = "Host bad-port\n" + "\tProxyJump jumpbox:22bad22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port"); _parse_config(session, file, string, SSH_ERROR); /* Braces mismatch in hostname */ @@ -1037,18 +1164,6 @@ static void torture_config_proxyjump(void **state, ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port-2"); _parse_config(session, file, string, SSH_ERROR); - /* Too many @ in second jump */ - config = "Host bad-hostname\n" - "\tProxyJump localhost,user@principal.com@jumpbox:22\n"; - if (file != NULL) { - torture_write_file(file, config); - } else { - string = config; - } - torture_reset_config(session); - ssh_options_set(session, SSH_OPTIONS_HOST, "bad-hostname"); - _parse_config(session, file, string, SSH_ERROR); - /* Braces mismatch in second jump */ config = "Host mismatch\n" "\tProxyJump localhost,[::1:20\n"; @@ -1127,6 +1242,76 @@ static void torture_config_proxyjump_string(void **state) } /** + * @brief Verify we can parse ControlPath configuration option + */ +static void torture_config_control_path(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.control_path, "/tmp/ssh-%r@%h:%p"); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "none"); + _parse_config(session, file, string, SSH_OK); + assert_null(session->opts.control_path); +} + +/** + * @brief Verify we can parse ControlPath configuration option from string + */ +static void torture_config_control_path_string(void **state) +{ + torture_config_control_path(state, NULL, LIBSSH_TESTCONFIG_STRING17); +} + +/** + * @brief Verify we can parse ControlPath configuration option from file + */ +static void torture_config_control_path_file(void **state) +{ + torture_config_control_path(state, LIBSSH_TESTCONFIG17, NULL); +} + +/** + * @brief Verify we can parse ControlMaster configuration option + */ +static void torture_config_control_master(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_AUTO); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "none"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_YES); +} + +/** + * @brief Verify we can parse ControlMaster configuration option from string + */ +static void torture_config_control_master_string(void **state) +{ + torture_config_control_master(state, NULL, LIBSSH_TESTCONFIG_STRING17); +} + +/** + * @brief Verify we can parse ControlMaster configuration option from file + */ +static void torture_config_control_master_file(void **state) +{ + torture_config_control_master(state, LIBSSH_TESTCONFIG17, NULL); +} + +/** * @brief Verify the configuration parser handles all the possible * versions of RekeyLimit configuration option. */ @@ -1212,6 +1397,349 @@ static void torture_config_rekey_string(void **state) } /** + * @brief Remove substring from a string + * + * @param occurrence 0 means "remove the first occurrence" + * 1 means "remove the second occurrence" and so on + */ +static void helper_remove_substring(char *s, const char *subs, int occurrence) { + char *p; + /* remove the substring from the defaults */ + p = strstr(s, subs); + assert_non_null(p); + /* look for second occurrence */ + for (int i = 0; i < occurrence; i++) { + p = strstr(p + 1, subs); + assert_non_null(p); + } + memmove(p, p + strlen(subs), strlen(p + strlen(subs)) + 1); +} + +/** + * @brief test that openssh style '+' feature works + */ +static void torture_config_plus(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + const char *def_hostkeys = ssh_kex_get_default_methods(SSH_HOSTKEYS); + const char *fips_hostkeys = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + const char *def_ciphers = ssh_kex_get_default_methods(SSH_CRYPT_C_S); + const char *fips_ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_C_S); + const char *def_kex = ssh_kex_get_default_methods(SSH_KEX); + const char *fips_kex = ssh_kex_get_fips_methods(SSH_KEX); + const char *def_mac = ssh_kex_get_default_methods(SSH_MAC_C_S); + const char *fips_mac = ssh_kex_get_fips_methods(SSH_MAC_C_S); + const char *hostkeys_added = ",ssh-rsa"; + const char *ciphers_added = "aes128-cbc,aes256-cbc"; + const char *kex_added = ",diffie-hellman-group14-sha1,diffie-hellman-group1-sha1"; + const char *mac_added = ",hmac-sha1,hmac-sha1-etm@openssh.com"; + char *awaited = NULL; + int rc; + + _parse_config(session, file, string, SSH_OK); + + /* check hostkeys */ + if (ssh_fips_mode()) { + /* ssh-rsa is disabled in fips */ + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], fips_hostkeys); + } else { + awaited = calloc(strlen(def_hostkeys) + strlen(hostkeys_added) + 1, 1); + rc = snprintf(awaited, strlen(def_hostkeys) + strlen(hostkeys_added) + 1, + "%s%s", def_hostkeys, hostkeys_added); + assert_int_equal(rc, strlen(def_hostkeys) + strlen(hostkeys_added)); + + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], awaited); + free(awaited); + } + + /* check ciphers */ + if (ssh_fips_mode()) { + /* already all supported is in the list */ + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], fips_ciphers); + } else { + awaited = calloc(strlen(def_ciphers) + strlen(ciphers_added) + 1, 1); + rc = snprintf(awaited, strlen(def_ciphers) + strlen(ciphers_added) + 1, + "%s%s", def_ciphers, ciphers_added); + assert_int_equal(rc, strlen(def_ciphers) + strlen(ciphers_added)); + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], awaited); + free(awaited); + } + + /* check kex */ + if (ssh_fips_mode()) { + /* sha1 is disabled in fips */ + assert_string_equal(session->opts.wanted_methods[SSH_KEX], fips_kex); + } else { + awaited = calloc(strlen(def_kex) + strlen(kex_added) + 1, 1); + rc = snprintf(awaited, strlen(def_kex) + strlen(kex_added) + 1, + "%s%s", def_kex, kex_added); + assert_int_equal(rc, strlen(def_kex) + strlen(kex_added)); + assert_string_equal(session->opts.wanted_methods[SSH_KEX], awaited); + free(awaited); + } + + /* check mac */ + if (ssh_fips_mode()) { + /* the added algos are already in the fips_methods */ + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], fips_mac); + } else { + awaited = calloc(strlen(def_mac) + strlen(mac_added) + 1, 1); + rc = snprintf(awaited, strlen(def_mac) + strlen(mac_added) + 1, + "%s%s", def_mac, mac_added); + assert_int_equal(rc, strlen(def_mac) + strlen(mac_added)); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], awaited); + free(awaited); + } +} + +/** + * @brief test that openssh style '+' feature works from file + */ +static void torture_config_plus_file(void **state) +{ + torture_config_plus(state, LIBSSH_TESTCONFIG14, NULL); +} + +/** + * @brief test that openssh style '+' feature works from string + */ +static void torture_config_plus_string(void **state) +{ + torture_config_plus(state, NULL, LIBSSH_TESTCONFIG_STRING14); +} + +/** + * @brief test that openssh style '-' feature works from string + */ +static void torture_config_minus(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + const char *def_hostkeys = ssh_kex_get_default_methods(SSH_HOSTKEYS); + const char *fips_hostkeys = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + const char *def_ciphers = ssh_kex_get_default_methods(SSH_CRYPT_C_S); + const char *fips_ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_C_S); + const char *def_kex = ssh_kex_get_default_methods(SSH_KEX); + const char *fips_kex = ssh_kex_get_fips_methods(SSH_KEX); + const char *def_mac = ssh_kex_get_default_methods(SSH_MAC_C_S); + const char *fips_mac = ssh_kex_get_fips_methods(SSH_MAC_C_S); + const char *hostkeys_removed = ",rsa-sha2-512,rsa-sha2-256"; + const char *ciphers_removed = ",aes256-ctr"; + const char *kex_removed = ",diffie-hellman-group18-sha512,diffie-hellman-group16-sha512"; + const char *fips_kex_removed = ",diffie-hellman-group16-sha512,diffie-hellman-group18-sha512"; + const char *mac_removed = "hmac-sha2-256-etm@openssh.com,"; + char *awaited = NULL; + int rc; + + _parse_config(session, file, string, SSH_OK); + + /* check hostkeys */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_hostkeys) + 1, 1); + rc = snprintf(awaited, strlen(fips_hostkeys) + 1, "%s", fips_hostkeys); + assert_int_equal(rc, strlen(fips_hostkeys)); + } else { + awaited = calloc(strlen(def_hostkeys) + 1, 1); + rc = snprintf(awaited, strlen(def_hostkeys) + 1, "%s", def_hostkeys); + assert_int_equal(rc, strlen(def_hostkeys)); + } + /* remove the substring from the defaults */ + helper_remove_substring(awaited, hostkeys_removed, 0); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], awaited); + free(awaited); + + /* check ciphers */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(fips_ciphers) + 1, "%s", fips_ciphers); + assert_int_equal(rc, strlen(fips_ciphers)); + } else { + awaited = calloc(strlen(def_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(def_ciphers) + 1, "%s", def_ciphers); + assert_int_equal(rc, strlen(def_ciphers)); + /* remove the comma at the end of the list */ + awaited[strlen(awaited) - 1] = '\0'; + } + /* remove the substring from the defaults */ + helper_remove_substring(awaited, ciphers_removed, 0); + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], awaited); + free(awaited); + + /* check kex */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_kex) + 1, 1); + rc = snprintf(awaited, strlen(fips_kex) + 1, "%s", fips_kex); + assert_int_equal(rc, strlen(fips_kex)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, fips_kex_removed, 0); + } else { + awaited = calloc(strlen(def_kex) + 1, 1); + rc = snprintf(awaited, strlen(def_kex) + 1, "%s", def_kex); + assert_int_equal(rc, strlen(def_kex)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, kex_removed, 0); + } + assert_string_equal(session->opts.wanted_methods[SSH_KEX], awaited); + free(awaited); + + /* check mac */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_mac) + 1, 1); + rc = snprintf(awaited, strlen(fips_mac) + 1, "%s", fips_mac); + assert_int_equal(rc, strlen(fips_mac)); + } else { + awaited = calloc(strlen(def_mac) + 1, 1); + rc = snprintf(awaited, strlen(def_mac) + 1, "%s", def_mac); + assert_int_equal(rc, strlen(def_mac)); + } + /* remove the substring from the defaults */ + helper_remove_substring(awaited, mac_removed, 0); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], awaited); + free(awaited); +} + +/** + * @brief test that openssh style '-' feature works from file + */ +static void torture_config_minus_file(void **state) +{ + torture_config_minus(state, LIBSSH_TESTCONFIG15, NULL); +} + +/** + * @brief test that openssh style '-' feature works from string + */ +static void torture_config_minus_string(void **state) +{ + torture_config_minus(state, NULL, LIBSSH_TESTCONFIG_STRING15); +} + +/** + * @brief test that openssh style '^' feature works from string + */ +static void torture_config_caret(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + const char *def_hostkeys = ssh_kex_get_default_methods(SSH_HOSTKEYS); + const char *fips_hostkeys = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + const char *def_ciphers = ssh_kex_get_default_methods(SSH_CRYPT_C_S); + const char *fips_ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_C_S); + const char *def_kex = ssh_kex_get_default_methods(SSH_KEX); + const char *fips_kex = ssh_kex_get_fips_methods(SSH_KEX); + const char *def_mac = ssh_kex_get_default_methods(SSH_MAC_C_S); + const char *fips_mac = ssh_kex_get_fips_methods(SSH_MAC_C_S); + const char *hostkeys_prio = "rsa-sha2-512,rsa-sha2-256"; + const char *ciphers_prio = "aes256-cbc,"; + const char *kex_prio = "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512,"; + const char *fips_kex_prio = ",diffie-hellman-group16-sha512,diffie-hellman-group18-sha512"; + const char *mac_prio = "hmac-sha1,"; + char *awaited = NULL; + int rc; + + _parse_config(session, file, string, SSH_OK); + + /* check hostkeys */ + /* +2 for the added comma and the \0 */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(hostkeys_prio) + strlen(fips_hostkeys) + 2, 1); + rc = snprintf(awaited, strlen(hostkeys_prio) + strlen(fips_hostkeys) + 2, + "%s,%s", hostkeys_prio, fips_hostkeys); + assert_int_equal(rc, strlen(hostkeys_prio) + strlen(fips_hostkeys) + 1); + } else { + awaited = calloc(strlen(def_hostkeys) + strlen(hostkeys_prio) + 2, 1); + rc = snprintf(awaited, strlen(hostkeys_prio) + strlen(def_hostkeys) + 2, + "%s,%s", hostkeys_prio, def_hostkeys); + assert_int_equal(rc, strlen(hostkeys_prio) + strlen(def_hostkeys) + 1); + } + + /* remove the substring from the defaults */ + helper_remove_substring(awaited, hostkeys_prio, 1); + /* remove the comma at the end of the list */ + awaited[strlen(awaited) - 1] = '\0'; + + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], awaited); + free(awaited); + + /* check ciphers */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(ciphers_prio) + strlen(fips_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(ciphers_prio) + strlen(fips_ciphers) + 1, + "%s%s", ciphers_prio, fips_ciphers); + assert_int_equal(rc, strlen(ciphers_prio) + strlen(fips_ciphers)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, ciphers_prio, 1); + } else { + /* + 2 because the '\0' and the comma */ + awaited = calloc(strlen(ciphers_prio) + strlen(def_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(ciphers_prio) + strlen(def_ciphers) + 1, + "%s%s", ciphers_prio, def_ciphers); + assert_int_equal(rc, strlen(ciphers_prio) + strlen(def_ciphers)); + /* remove the comma at the end of the list */ + awaited[strlen(awaited) - 1] = '\0'; + } + + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], awaited); + free(awaited); + + /* check kex */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(kex_prio) + strlen(fips_kex) + 1, 1); + rc = snprintf(awaited, strlen(kex_prio) + strlen(fips_kex) + 1, + "%s%s", kex_prio, fips_kex); + assert_int_equal(rc, strlen(kex_prio) + strlen(fips_kex)); + /* remove the substring from the defaults */ + /* the default list has different order of these two algos than the fips + * and because here is a braindead string substitution being done, + * change the order and remove the first occurrence of it */ + helper_remove_substring(awaited, fips_kex_prio, 0); + } else { + awaited = calloc(strlen(kex_prio) + strlen(def_kex) + 1, 1); + rc = snprintf(awaited, strlen(kex_prio) + strlen(def_kex) + 1, + "%s%s", kex_prio, def_kex); + assert_int_equal(rc, strlen(def_kex) + strlen(kex_prio)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, kex_prio, 1); + } + + assert_string_equal(session->opts.wanted_methods[SSH_KEX], awaited); + free(awaited); + + /* check mac */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(mac_prio) + strlen(fips_mac) + 1, 1); + rc = snprintf(awaited, strlen(mac_prio) + strlen(fips_mac) + 1, "%s%s", mac_prio, fips_mac); + assert_int_equal(rc, strlen(mac_prio) + strlen(fips_mac)); + /* the fips list contains hmac-sha1 algo */ + helper_remove_substring(awaited, mac_prio, 1); + } else { + awaited = calloc(strlen(mac_prio) + strlen(def_mac) + 1, 1); + /* the mac is not in default; it is added to the list */ + rc = snprintf(awaited, strlen(mac_prio) + strlen(def_mac) + 1, "%s%s", mac_prio, def_mac); + assert_int_equal(rc, strlen(mac_prio) + strlen(def_mac)); + } + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], awaited); + free(awaited); +} + +/** + * @brief test that openssh style '^' feature works from file + */ +static void torture_config_caret_file(void **state) +{ + torture_config_caret(state, LIBSSH_TESTCONFIG16, NULL); +} + +/** + * @brief test that openssh style '^' feature works from string + */ +static void torture_config_caret_string(void **state) +{ + torture_config_caret(state, NULL, LIBSSH_TESTCONFIG_STRING16); +} + +/** * @brief test PubkeyAcceptedKeyTypes helper function */ static void torture_config_pubkeytypes(void **state, @@ -1250,6 +1778,22 @@ static void torture_config_pubkeytypes_string(void **state) } /** + * @brief test parsing PubkeyAcceptedKAlgorithms from file + */ +static void torture_config_pubkeyalgorithms_file(void **state) +{ + torture_config_pubkeytypes(state, LIBSSH_TEST_PUBKEYALGORITHMS, NULL); +} + +/** + * @brief test parsing PubkeyAcceptedAlgorithms from string + */ +static void torture_config_pubkeyalgorithms_string(void **state) +{ + torture_config_pubkeytypes(state, NULL, LIBSSH_TEST_PUBKEYALGORITHMS_STRING); +} + +/** * @brief Verify the configuration parser handles * missing newline in the end */ @@ -1307,17 +1851,20 @@ static void torture_config_nonewlineoneline_string(void **state) NULL, LIBSSH_TEST_NONEWLINEONELINE_STRING); } -/* ssh_config_get_cmd() does three things: +/* ssh_config_get_cmd() does these two things: * * Strips leading whitespace - * * Terminate the characted on the end of next quotes-enclosed string * * Terminate on the end of line */ static void torture_config_parser_get_cmd(void **state) { char *p = NULL, *tok = NULL; char data[256]; - - (void) state; +#ifdef __unix__ + FILE *outfile = NULL, *infile = NULL; + int pid; + char buffer[256] = {0}; +#endif + (void)state; /* Ignore leading whitespace */ strncpy(data, " \t\t string\n", sizeof(data)); @@ -1333,14 +1880,11 @@ static void torture_config_parser_get_cmd(void **state) assert_string_equal(tok, "string \t\t "); assert_int_equal(*p, '\0'); - /* should drop the quotes and split them into separate arguments */ + /* should not drop the quotes and not split them into separate arguments */ strncpy(data, "\"multi string\" something\n", sizeof(data)); p = data; tok = ssh_config_get_cmd(&p); - assert_string_equal(tok, "multi string"); - assert_int_equal(*p, ' '); - tok = ssh_config_get_cmd(&p); - assert_string_equal(tok, "something"); + assert_string_equal(tok, "\"multi string\" something"); assert_int_equal(*p, '\0'); /* But it does not split tokens by whitespace @@ -1350,6 +1894,46 @@ static void torture_config_parser_get_cmd(void **state) tok = ssh_config_get_cmd(&p); assert_string_equal(tok, "multi string something"); assert_int_equal(*p, '\0'); + + /* Commands in quotes are not treated special */ + sprintf(data, "%s%s%s%s", "\"", SOURCEDIR "/tests/unittests/hello world.sh", "\" ", "\"hello libssh\"\n"); + printf("%s\n", data); + p = data; + tok = ssh_config_get_cmd(&p); + assert_string_equal(tok, data); + assert_int_equal(*p, '\0'); + +#ifdef __unix__ + /* Check if the command would get correctly executed + * Use the script file "hello world.sh" to echo the first argument + * Run as <= "/workdir/hello world.sh" "hello libssh" => */ + + /* output to file and check wrong */ + outfile = fopen("output.log", "a+"); + assert_non_null(outfile); + printf("the tok is %s\n", tok); + + pid = fork(); + if (pid == -1) { + perror("fork"); + } else if (pid == 0) { + ssh_execute_command(tok, fileno(outfile), fileno(outfile)); + /* Does not return */ + } else { + /* parent + * wait child process */ + wait(NULL); + infile = fopen("output.log", "r"); + assert_non_null(infile); + p = fgets(buffer, sizeof(buffer), infile); + fclose(infile); + remove("output.log"); + assert_non_null(p); + } + + fclose(outfile); + assert_string_equal(buffer, "hello libssh"); +#endif } /* ssh_config_get_token() should behave as expected @@ -1620,12 +2204,14 @@ static void torture_config_match_pattern(void **state) static void torture_config_identity(void **state) { const char *id = NULL; + const char *cert = NULL; struct ssh_iterator *it = NULL; ssh_session session = *state; _parse_config(session, NULL, LIBSSH_TESTCONFIG_STRING13, SSH_OK); - it = ssh_list_get_iterator(session->opts.identity); + /* The identities are first added to this temporary list before expanding */ + it = ssh_list_get_iterator(session->opts.identity_non_exp); assert_non_null(it); id = it->data; /* The identities are prepended to the list so we start with second one */ @@ -1635,8 +2221,166 @@ static void torture_config_identity(void **state) assert_non_null(it); id = it->data; assert_string_equal(id, "id_rsa_one"); + + /* The certs are first added to this temporary list before expanding */ + it = ssh_list_get_iterator(session->opts.certificate_non_exp); + assert_non_null(it); + cert = it->data; + /* The certs are coming as listed in the configuration file */ + assert_string_equal(cert, "id_rsa_one-cert.pub"); + + it = it->next; + assert_non_null(it); + cert = it->data; + assert_string_equal(cert, "id_ecdsa_two-cert.pub"); + /* and that is all */ + assert_null(it->next); } +/* Make absolute path for config include + */ +static void torture_config_make_absolute_int(void **state, bool no_sshdir_fails) +{ + ssh_session session = *state; + char *result = NULL; +#ifndef _WIN32 + char h[256]; + char *user; + char *home; + + 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); +#endif + + /* Absolute path already -- should not change in any case */ + result = ssh_config_make_absolute(session, "/etc/ssh/ssh_config.d/*.conf", 1); + assert_string_equal(result, "/etc/ssh/ssh_config.d/*.conf"); + free(result); + result = ssh_config_make_absolute(session, "/etc/ssh/ssh_config.d/*.conf", 0); + assert_string_equal(result, "/etc/ssh/ssh_config.d/*.conf"); + free(result); + + /* Global is relative to /etc/ssh/ */ + result = ssh_config_make_absolute(session, "ssh_config.d/test.conf", 1); + assert_string_equal(result, "/etc/ssh/ssh_config.d/test.conf"); + free(result); + result = ssh_config_make_absolute(session, "./ssh_config.d/test.conf", 1); + assert_string_equal(result, "/etc/ssh/./ssh_config.d/test.conf"); + free(result); + + /* User config is relative to sshdir -- here faked to /tmp/ssh/ */ + result = ssh_config_make_absolute(session, "my_config", 0); + if (no_sshdir_fails) { + assert_null(result); + } else { + /* The path depends on the PWD so lets skip checking the actual path here */ + assert_non_null(result); + } + free(result); + + /* User config is relative to sshdir -- here faked to /tmp/ssh/ */ + ssh_options_set(session, SSH_OPTIONS_SSH_DIR, "/tmp/ssh"); + result = ssh_config_make_absolute(session, "my_config", 0); + assert_string_equal(result, "/tmp/ssh/my_config"); + free(result); + +#ifndef _WIN32 + /* Tilde expansion works only in user config */ + result = ssh_config_make_absolute(session, "~/.ssh/config.d/*.conf", 0); + snprintf(h, 256 - 1, "%s/.ssh/config.d/*.conf", home); + assert_string_equal(result, h); + free(result); + + snprintf(h, 256 - 1, "~%s/.ssh/config.d/*.conf", user); + result = ssh_config_make_absolute(session, h, 0); + snprintf(h, 256 - 1, "%s/.ssh/config.d/*.conf", home); + assert_string_equal(result, h); + free(result); + + /* in global config its just prefixed without expansion */ + result = ssh_config_make_absolute(session, "~/.ssh/config.d/*.conf", 1); + assert_string_equal(result, "/etc/ssh/~/.ssh/config.d/*.conf"); + free(result); + snprintf(h, 256 - 1, "~%s/.ssh/config.d/*.conf", user); + result = ssh_config_make_absolute(session, h, 1); + snprintf(h, 256 - 1, "/etc/ssh/~%s/.ssh/config.d/*.conf", user); + assert_string_equal(result, h); + free(result); +#endif +} + +static void torture_config_make_absolute(void **state) +{ + torture_config_make_absolute_int(state, 0); +} + +static void torture_config_make_absolute_no_sshdir(void **state) +{ + torture_config_make_absolute_int(state, 1); +} + +static void torture_config_parse_uri(void **state) +{ + char *username = NULL; + char *hostname = NULL; + char *port = NULL; + int rc; + + (void)state; /* unused */ + + rc = ssh_config_parse_uri("localhost", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "localhost"); + SAFE_FREE(hostname); + assert_null(port); + + rc = ssh_config_parse_uri("1.2.3.4", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1.2.3.4"); + SAFE_FREE(hostname); + assert_null(port); + + rc = ssh_config_parse_uri("1.2.3.4:2222", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1.2.3.4"); + SAFE_FREE(hostname); + assert_string_equal(port, "2222"); + SAFE_FREE(port); + + rc = ssh_config_parse_uri("[1:2:3::4]:2222", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1:2:3::4"); + SAFE_FREE(hostname); + assert_string_equal(port, "2222"); + SAFE_FREE(port); + + /* do not want port */ + rc = ssh_config_parse_uri("1:2:3::4", &username, &hostname, NULL, true); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1:2:3::4"); + SAFE_FREE(hostname); + + rc = ssh_config_parse_uri("user -name@", &username, NULL, NULL, true); + assert_int_equal(rc, SSH_ERROR); +} int torture_run_tests(void) { @@ -1646,6 +2390,10 @@ int torture_run_tests(void) setup, teardown), cmocka_unit_test_setup_teardown(torture_config_include_string, setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_include_recursive_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_include_recursive_string, + setup, teardown), cmocka_unit_test_setup_teardown(torture_config_double_ports_file, setup, teardown), cmocka_unit_test_setup_teardown(torture_config_double_ports_string, @@ -1674,14 +2422,38 @@ int torture_run_tests(void) setup, teardown), cmocka_unit_test_setup_teardown(torture_config_proxyjump_string, setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_control_path_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_control_path_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_control_master_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_control_master_string, + setup, teardown), cmocka_unit_test_setup_teardown(torture_config_rekey_file, setup, teardown), cmocka_unit_test_setup_teardown(torture_config_rekey_string, setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_plus_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_plus_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_minus_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_minus_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_caret_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_caret_string, + setup, teardown), cmocka_unit_test_setup_teardown(torture_config_pubkeytypes_file, setup, teardown), cmocka_unit_test_setup_teardown(torture_config_pubkeytypes_string, setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_pubkeyalgorithms_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_pubkeyalgorithms_string, + setup, teardown), cmocka_unit_test_setup_teardown(torture_config_nonewlineend_file, setup, teardown), cmocka_unit_test_setup_teardown(torture_config_nonewlineend_string, @@ -1698,6 +2470,12 @@ int torture_run_tests(void) setup, teardown), cmocka_unit_test_setup_teardown(torture_config_identity, setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_make_absolute, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_make_absolute_no_sshdir, + setup_no_sshdir, teardown), + cmocka_unit_test_setup_teardown(torture_config_parse_uri, + setup, teardown), }; diff --git a/tests/unittests/torture_keyfiles.c b/tests/unittests/torture_keyfiles.c index 2f06e88b..2ce47b72 100644 --- a/tests/unittests/torture_keyfiles.c +++ b/tests/unittests/torture_keyfiles.c @@ -7,9 +7,6 @@ #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) { @@ -29,26 +26,6 @@ static int setup_rsa_key(void **state) 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; @@ -56,11 +33,6 @@ static int setup_both_keys(void **state) { if (rc != 0) { return rc; } -#ifdef HAVE_DSA - ssh_free(*state); - - rc = setup_dsa_key(state); -#endif return rc; } @@ -74,13 +46,6 @@ static int setup_both_keys_passphrase(void **state) 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; @@ -89,10 +54,6 @@ static int setup_both_keys_passphrase(void **state) 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"); @@ -235,15 +196,6 @@ static void torture_privatekey_from_file(void **state) { 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); @@ -252,14 +204,6 @@ static void torture_privatekey_from_file(void **state) { 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 } /** @@ -276,15 +220,6 @@ static void torture_privatekey_from_file_passphrase(void **state) { 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); @@ -293,14 +228,6 @@ static void torture_privatekey_from_file_passphrase(void **state) { 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) { diff --git a/tests/unittests/torture_knownhosts_parsing.c b/tests/unittests/torture_knownhosts_parsing.c index bde0eb60..bc79fd0f 100644 --- a/tests/unittests/torture_knownhosts_parsing.c +++ b/tests/unittests/torture_knownhosts_parsing.c @@ -4,10 +4,10 @@ #define LIBSSH_STATIC #include <libssh/priv.h> -#include "torture.h" #include "knownhosts.c" +#include "torture.h" #if (defined _WIN32) || (defined _WIN64) #ifndef S_IRWXO #define S_IRWXO 0 @@ -17,6 +17,7 @@ #endif #endif +#define LOCALHOST_DSS_LINE "localhost,127.0.0.1 ssh-dss AAAAB3NzaC1kc3MAAACBAIK3RTEWBw+rAPcYUM2Qq4kEw59gXpUQ/WvkdeY7QDO64MHaaorySj8xsraNudmQFh4xb/i5Q1EMnNchOFxtilfU5bUJgdTvetyZEWFL+2HxqBs8GaWRyB1vtSFAw3GO8VUEnjF844N3dNyLoc0NX8IvzwNIaQho6KTsueQlG1X9AAAAFQCXUl4a5UvElL4thi/8QlxR5PtEewAAAIBqNpl5MTBxKQu5jT0+WASa7pAqwT53ofv7ZTDIEokYRb57/nwzDgkcs1fsBRrI6eczJ/VlXWwKbsgkx2Nh3ZiWYwC+HY5uqRpDaj3HERC6LMn4dzdcl29fYeziEibCbRjJX5lZF2vIaA1Ewv8yT0UlunyHZRiyw4WlEglkf/NITAAAAIBxLsdBBXn+8qEYwWK9KT+arRqNXC/lrl0Fp5YyxGNGCv82JcnuOShGGTzhYf8AtTCY1u5oixiW9kea6KXGAKgTjfJShr7n47SZVfOPOrBT3VLhRdGGO3GblDUppzfL8wsEdoqXjzrJuxSdrGnkFu8S9QjkPn9dCtScvWEcluHqMw==" #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" @@ -144,6 +145,36 @@ close_fp: return rc; } +static int setup_knownhosts_file_unsupported_type(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_DSS_LINE, + sizeof(char), + strlen(LOCALHOST_DSS_LINE), + fp); + if (nwritten != strlen(LOCALHOST_DSS_LINE)) { + rc = -1; + goto close_fp; + } + +close_fp: + fclose(fp); + + return rc; +} + static int teardown_knownhosts_file(void **state) { char *tmp_file = *state; @@ -396,6 +427,29 @@ static void torture_knownhosts_get_algorithms_names(void **state) ssh_free(session); } +/* Do not remove this test if we completely remove DSA support! */ +static void torture_knownhosts_get_algorithms_names_unsupported(void **state) +{ + const char *knownhosts_file = *state; + ssh_session session; + 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_null(names); + + ssh_free(session); +} + static void torture_knownhosts_algorithms_wanted(void **state) { const char *knownhosts_file = *state; @@ -576,7 +630,9 @@ static void torture_knownhosts_algorithms(void **state) bool process_config = false; const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256," "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384," - "ecdsa-sha2-nistp256"; + "ecdsa-sha2-nistp256," + "sk-ssh-ed25519@openssh.com," + "sk-ecdsa-sha2-nistp256@openssh.com"; const char *expect_fips = "rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp521," "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256"; @@ -611,7 +667,9 @@ static void torture_knownhosts_algorithms_global(void **state) bool process_config = false; const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256," "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384," - "ecdsa-sha2-nistp256"; + "ecdsa-sha2-nistp256," + "sk-ssh-ed25519@openssh.com," + "sk-ecdsa-sha2-nistp256@openssh.com"; const char *expect_fips = "rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp521," "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256"; @@ -660,6 +718,9 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_knownhosts_get_algorithms_names, setup_knownhosts_file, teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_get_algorithms_names_unsupported, + setup_knownhosts_file_unsupported_type, + teardown_knownhosts_file), cmocka_unit_test_setup_teardown(torture_knownhosts_algorithms_wanted, setup_knownhosts_file, teardown_knownhosts_file), diff --git a/tests/unittests/torture_misc.c b/tests/unittests/torture_misc.c index c7b99b5d..5a3b4805 100644 --- a/tests/unittests/torture_misc.c +++ b/tests/unittests/torture_misc.c @@ -4,8 +4,9 @@ #include <unistd.h> #endif #include <sys/types.h> -#ifndef _WIN32 +#include <fcntl.h> +#ifndef _WIN32 #define _POSIX_PTHREAD_SEMANTICS #include <pwd.h> #endif @@ -13,11 +14,18 @@ #define LIBSSH_STATIC #include <libssh/priv.h> -#include "torture.h" #include "misc.c" +#include "torture.h" #include "error.c" +#ifdef _WIN32 +#include <netioapi.h> +#else +#include <net/if.h> +#endif + #define TORTURE_TEST_DIR "/usr/local/bin/truc/much/.." +#define TORTURE_IPV6_LOCAL_LINK "fe80::98e1:82ff:fe8d:28b3%%%s" const char template[] = "temp_dir_XXXXXX"; @@ -168,17 +176,25 @@ static void torture_path_expand_tilde_unix(void **state) { static void torture_path_expand_escape(void **state) { ssh_session session = *state; - const char *s = "%d/%h/by/%r"; + const char *s = "%d/%h/%p/by/%r"; char *e; session->opts.sshdir = strdup("guru"); session->opts.host = strdup("meditation"); + session->opts.port = 0; 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); + assert_string_equal(e, "guru/meditation/22/by/root"); + ssh_string_free_char(e); + + session->opts.port = 222; + + e = ssh_path_expand_escape(session, s); + assert_non_null(e); + assert_string_equal(e, "guru/meditation/222/by/root"); + ssh_string_free_char(e); } static void torture_path_expand_known_hosts(void **state) { @@ -724,6 +740,395 @@ static void torture_ssh_strreplace(void **state) assert_null(replaced_string); } +static void torture_ssh_strerror(void **state) +{ + char buf[1024]; + size_t bufflen = sizeof(buf); + char *out = NULL; + + (void) state; + + out = ssh_strerror(ENOENT, buf, 1); /* too short */ + assert_string_equal(out, "\0"); + + out = ssh_strerror(256, buf, bufflen); /* unknown error code */ + /* This error is always different: + * Freebd: "Unknown error: 256" + * MinGW/Win: "Unknown error" + * Linux/glibc: "Unknown error 256" + * Alpine/musl: "No error information" + */ + assert_non_null(out); + + out = ssh_strerror(ENOMEM, buf, bufflen); + /* This actually differs too for glibc/musl: + * musl: "Out of memory" + * everything else: "Cannot allocate memory" + */ + assert_non_null(out); +} + +static void torture_ssh_readn(void **state) +{ + char *write_buf = NULL, *read_buf = NULL, *file_path = NULL; + size_t data_len = 10 * 1024 * 1024; + size_t read_buf_size = data_len + 1024; + size_t i, total_bytes_written = 0; + + const char *file_template = "libssh_torture_ssh_readn_test_XXXXXX"; + + off_t off; + ssize_t bytes_read, bytes_written; + int fd, rc, flags; + + (void)state; + + write_buf = malloc(data_len); + assert_non_null(write_buf); + + /* Fill the write buffer with random data */ + for (i = 0; i < data_len; ++i) { + rc = rand(); + write_buf[i] = (char)(rc & 0xff); + } + + /* + * The read buffer's size is intentionally kept larger than data_len. + * + * This is done so that we are able to test the scenario when the user + * requests ssh_readn() to read more bytes than the number of bytes present + * in the file. + * + * If the read buffer's size is kept same as data_len and if the test + * requests ssh_readn() to read more than data_len bytes starting from file + * offset 0, it will lead to a valgrind failure as in this case, ssh_readn() + * will pass an unallocated memory address in the last call it makes to + * read(). + */ + read_buf = malloc(read_buf_size); + assert_non_null(read_buf); + + file_path = torture_create_temp_file(file_template); + assert_non_null(file_path); + + /* Open a file for reading and writing */ + flags = O_RDWR; +#ifdef _WIN32 + flags |= O_BINARY; +#endif + + fd = open(file_path, flags, 0); + assert_int_not_equal(fd, -1); + + /* Write the data present in the write buffer to the file */ + do { + bytes_written = write(fd, + write_buf + total_bytes_written, + data_len - total_bytes_written); + + if (bytes_written == -1 && errno == EINTR) { + continue; + } + + assert_int_not_equal(bytes_written, -1); + total_bytes_written += bytes_written; + } while (total_bytes_written < data_len); + + /* Seek to the start of the file */ + off = lseek(fd, 0, SEEK_SET); + assert_int_not_equal(off, -1); + + bytes_read = ssh_readn(fd, read_buf, data_len); + assert_int_equal(bytes_read, data_len); + + /* + * Ensure that the data stored in the read buffer is same as the data + * present in the file and not some garbage. + */ + assert_memory_equal(read_buf, write_buf, data_len); + + /* + * Ensure that the file offset is on EOF and requesting to read more leads + * to 0 bytes getting read. + */ + off = lseek(fd, 0, SEEK_CUR); + assert_int_equal(off, data_len); + + bytes_read = ssh_readn(fd, read_buf, data_len); + assert_int_equal(bytes_read, 0); + + /* Try to read more bytes than what are present in the file */ + off = lseek(fd, 0, SEEK_SET); + assert_int_not_equal(off, -1); + + bytes_read = ssh_readn(fd, read_buf, read_buf_size); + assert_int_equal(bytes_read, data_len); + + /* + * Ensure that the data stored in the read buffer is same as the data + * present in the file and not some garbage. + */ + assert_memory_equal(read_buf, write_buf, data_len); + + /* Negative tests start */ + bytes_read = ssh_readn(-2, read_buf, data_len); + assert_int_equal(bytes_read, -1); + + bytes_read = ssh_readn(fd, NULL, data_len); + assert_int_equal(bytes_read, -1); + + bytes_read = ssh_readn(fd, read_buf, 0); + assert_int_equal(bytes_read, -1); + + /* Clean up */ + rc = close(fd); + assert_int_equal(rc, 0); + + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(file_path); + free(read_buf); + free(write_buf); +} + +static void torture_ssh_writen(void **state) +{ + char *write_buf = NULL, *read_buf = NULL, *file_path = NULL; + const char *file_template = "libssh_torture_ssh_writen_test_XXXXXX"; + + size_t data_len = 10 * 1024 * 1024; + size_t i, total_bytes_read = 0; + ssize_t bytes_written, bytes_read; + off_t off; + int rc, fd, flags; + + (void)state; + + write_buf = malloc(data_len); + assert_non_null(write_buf); + + /* Fill the write buffer with random data */ + for (i = 0; i < data_len; ++i) { + rc = rand(); + write_buf[i] = (char)(rc & 0xff); + } + + read_buf = malloc(data_len); + assert_non_null(read_buf); + + file_path = torture_create_temp_file(file_template); + assert_non_null(file_path); + + /* Open a file for reading and writing */ + flags = O_RDWR; +#ifdef _WIN32 + flags |= O_BINARY; +#endif + + fd = open(file_path, flags, 0); + assert_int_not_equal(fd, -1); + + /* Write the data present in the write buffer to the file */ + bytes_written = ssh_writen(fd, write_buf, data_len); + assert_int_equal(bytes_written, data_len); + + /* + * Ensure that the file offset is incremented by the number of bytes + * written. + */ + off = lseek(fd, 0, SEEK_CUR); + assert_int_equal(off, data_len); + + /* + * Ensure that the data present in the write buffer has been written to the + * file and not some garbage. + */ + off = lseek(fd, 0, SEEK_SET); + assert_int_not_equal(off, -1); + + do { + bytes_read = read(fd, + read_buf + total_bytes_read, + data_len - total_bytes_read); + + if (bytes_read == -1 && errno == EINTR) { + continue; + } + + assert_int_not_equal(bytes_read, -1); + assert_int_not_equal(bytes_read, 0); + + total_bytes_read += bytes_read; + } while (total_bytes_read < data_len); + + assert_memory_equal(write_buf, read_buf, data_len); + + /* Negative tests start */ + bytes_written = ssh_writen(-3, write_buf, data_len); + assert_int_equal(bytes_written, -1); + + bytes_written = ssh_writen(fd, NULL, data_len); + assert_int_equal(bytes_written, -1); + + bytes_written = ssh_writen(fd, write_buf, 0); + assert_int_equal(bytes_written, -1); + + /* Clean up */ + rc = close(fd); + assert_int_equal(rc, 0); + + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(file_path); + free(read_buf); + free(write_buf); +} + +static void torture_ssh_check_hostname_syntax(void **state) +{ + int rc; + (void)state; + + rc = ssh_check_hostname_syntax("duckduckgo.com"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("www.libssh.org"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("Some-Thing.com"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123.ok"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("lavabo-inter.innocentes-manus-meas"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("localhost"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("a"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("a-0.b-b"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("libssh."); + assert_int_equal(rc, SSH_OK); + + rc = ssh_check_hostname_syntax(NULL); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax(""); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("/"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("@"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("["); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("`"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("{"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("&"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("|"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("\""); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("`"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax(" "); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("*the+giant&\"rooks\".c0m"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("!www.libssh.org"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("--.--"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("libssh.a234567890123456789012345678901234567890123456789012345678901234"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("libssh.a234567890123456789012345678901234567890123456789012345678901234.a234567890123456789012345678901234567890123456789012345678901234"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("libssh-"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("fe80::9656:d028:8652:66b6"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("."); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax(".."); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_ssh_check_username_syntax(void **state) { + int rc; + (void)state; + + rc = ssh_check_username_syntax("username"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_username_syntax("Alice"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_username_syntax("Alice and Bob"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_username_syntax("n4me?"); + assert_int_equal(rc, SSH_OK); + + rc = ssh_check_username_syntax("alice&bob"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_username_syntax("backslash\\"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_username_syntax("&var|()us\"<ha`r{}'"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_username_syntax(" -"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_username_syntax("me and -"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_username_syntax("los -santos"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_username_syntax("- who?"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_username_syntax(NULL); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_username_syntax(""); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_ssh_is_ipaddr(void **state) { + int rc; + char *interf = malloc(64); + char *test_interf = malloc(128); + (void)state; + + assert_non_null(interf); + assert_non_null(test_interf); + rc = ssh_is_ipaddr("201.255.3.69"); + assert_int_equal(rc, 1); + rc = ssh_is_ipaddr("::1"); + assert_int_equal(rc, 1); + rc = ssh_is_ipaddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + assert_int_equal(rc, 1); + if_indextoname(1, interf); + assert_non_null(interf); + rc = sprintf(test_interf, TORTURE_IPV6_LOCAL_LINK, interf); + /* the "%%s" is not written */ + assert_int_equal(rc, strlen(interf) + strlen(TORTURE_IPV6_LOCAL_LINK) - 3); + rc = ssh_is_ipaddr(test_interf); + assert_int_equal(rc, 1); + free(interf); + free(test_interf); + + rc = ssh_is_ipaddr(".."); + assert_int_equal(rc, 0); + rc = ssh_is_ipaddr(":::"); + assert_int_equal(rc, 0); + rc = ssh_is_ipaddr("1.1.1.1.1"); + assert_int_equal(rc, 0); + rc = ssh_is_ipaddr("1.1"); + assert_int_equal(rc, 0); + rc = ssh_is_ipaddr("caesar"); + assert_int_equal(rc, 0); + rc = ssh_is_ipaddr("::xa:1"); + assert_int_equal(rc, 0); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -747,6 +1152,12 @@ int torture_run_tests(void) { cmocka_unit_test(torture_ssh_mkdirs), cmocka_unit_test(torture_ssh_quote_file_name), cmocka_unit_test(torture_ssh_strreplace), + cmocka_unit_test(torture_ssh_strerror), + cmocka_unit_test(torture_ssh_readn), + cmocka_unit_test(torture_ssh_writen), + cmocka_unit_test(torture_ssh_check_hostname_syntax), + cmocka_unit_test(torture_ssh_check_username_syntax), + cmocka_unit_test(torture_ssh_is_ipaddr), }; ssh_init(); diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c index d2ceedd5..e41c15da 100644 --- a/tests/unittests/torture_options.c +++ b/tests/unittests/torture_options.c @@ -18,9 +18,6 @@ #include <libssh/bind.h> #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 @@ -60,12 +57,38 @@ static void torture_options_set_host(void **state) { assert_non_null(session->opts.host); assert_string_equal(session->opts.host, "localhost"); + /* IPv4 address */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "127.1.1.1"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "127.1.1.1"); + assert_null(session->opts.username); + + /* IPv6 address */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "::1"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "::1"); + assert_null(session->opts.username); + 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"); + + /* more @ in uri is OK -- it should go to the username */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "at@login@hostname"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "hostname"); + assert_non_null(session->opts.username); + assert_string_equal(session->opts.username, "at@login"); + + /* disallow metacharacters in the username */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "shallN()tP4ss -@hostname"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); } static void torture_options_set_ciphers(void **state) { @@ -223,7 +246,7 @@ static void torture_options_set_pubkey_accepted_types(void **state) { /* simulate the SHA2 extension was negotiated */ session->extensions = SSH_EXT_SIG_RSA_SHA256; - /* previous configuration did not list the SHA2 extension algoritms, so + /* previous configuration did not list the SHA2 extension algorithms, so * it should not be used */ type = ssh_key_type_to_hash(session, SSH_KEYTYPE_RSA); assert_int_equal(type, SSH_DIGEST_SHA1); @@ -312,6 +335,7 @@ static void torture_options_set_port(void **state) { rc = ssh_options_set(session, SSH_OPTIONS_PORT_STR, "five"); assert_true(rc == -1); + assert_int_not_equal(session->opts.port, 0); rc = ssh_options_set(session, SSH_OPTIONS_PORT, NULL); assert_true(rc == -1); @@ -372,6 +396,9 @@ static void torture_options_set_user(void **state) { assert_true(rc == 0); #endif /* _WIN32 */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, "&shallN()tP4ss"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + rc = ssh_options_set(session, SSH_OPTIONS_USER, "guru"); assert_true(rc == 0); assert_string_equal(session->opts.username, "guru"); @@ -399,12 +426,12 @@ static void torture_options_set_identity(void **state) { rc = ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, "identity1"); assert_true(rc == 0); - assert_string_equal(session->opts.identity->root->data, "identity1"); + assert_string_equal(session->opts.identity_non_exp->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"); + assert_string_equal(session->opts.identity_non_exp->root->data, "identity2"); + assert_string_equal(session->opts.identity_non_exp->root->next->data, "identity1"); } static void torture_options_get_identity(void **state) { @@ -422,7 +449,7 @@ static void torture_options_get_identity(void **state) { rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "identity2"); assert_int_equal(rc, SSH_OK); - assert_string_equal(session->opts.identity->root->data, "identity2"); + assert_string_equal(session->opts.identity_non_exp->root->data, "identity2"); rc = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &identity); assert_int_equal(rc, SSH_OK); assert_non_null(identity); @@ -527,6 +554,74 @@ static void torture_options_proxycommand(void **state) { assert_null(session->opts.ProxyCommand); } +static void torture_options_control_master (void **state) { + ssh_session session = *state; + int rc, val = SSH_CONTROL_MASTER_NO; + + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_NO); + + val = SSH_CONTROL_MASTER_AUTO; + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_AUTO); + + val = SSH_CONTROL_MASTER_YES; + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_YES); + + val = SSH_CONTROL_MASTER_ASK; + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_ASK); + + val = SSH_CONTROL_MASTER_AUTOASK; + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_AUTOASK); + + val = 255; + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_options_control_path(void **state) { + ssh_session session = *state; + char *str = NULL; + int rc; + + /* Set Control Path */ + rc = ssh_options_set(session, SSH_OPTIONS_CONTROL_PATH, "/tmp/ssh-%r@%h:%p"); + assert_int_equal(rc, 0); + + assert_string_equal(session->opts.control_path, "/tmp/ssh-%r@%h:%p"); + + rc = ssh_options_get(session, SSH_OPTIONS_CONTROL_PATH, &str); + assert_int_equal(rc, 0); + assert_string_equal(str, "/tmp/ssh-%r@%h:%p"); + + /* Disable Multiplexing */ + rc = ssh_options_set(session, SSH_OPTIONS_CONTROL_PATH, "none"); + assert_int_equal(rc, 0); + + assert_null(session->opts.control_path); + SSH_STRING_FREE_CHAR(str); +} + static void torture_options_config_host(void **state) { ssh_session session = *state; FILE *config = NULL; @@ -827,6 +922,9 @@ static void torture_options_copy(void **state) config = fopen("test_config", "w"); assert_non_null(config); fputs("IdentityFile ~/.ssh/id_ecdsa\n" + "IdentityFile ~/.ssh/my_rsa\n" + "CertificateFile ~/.ssh/my_rsa-cert.pub\n" + "CertificateFile ~/.ssh/id_ecdsa-cert.pub\n" "User tester\n" "Hostname example.com\n" "BindAddress 127.0.0.2\n" @@ -837,8 +935,10 @@ static void torture_options_copy(void **state) "MACs hmac-sha2-256\n" "HostKeyAlgorithms ssh-ed25519,ecdsa-sha2-nistp521\n" "Compression yes\n" - "PubkeyAcceptedTypes ssh-ed25519,ecdsa-sha2-nistp521\n" + "PubkeyAcceptedAlgorithms ssh-ed25519,ecdsa-sha2-nistp521\n" "ProxyCommand nc 127.0.0.10 22\n" + "ControlMaster ask\n" + "ControlPath /tmp/ssh-%r@%h:%p\n" /* ops.custombanner */ "ConnectTimeout 42\n" "Port 222\n" @@ -860,9 +960,22 @@ static void torture_options_copy(void **state) assert_non_null(new); /* Check the identities match */ - it = ssh_list_get_iterator(session->opts.identity); + it = ssh_list_get_iterator(session->opts.identity_non_exp); assert_non_null(it); - it2 = ssh_list_get_iterator(new->opts.identity); + it2 = ssh_list_get_iterator(new->opts.identity_non_exp); + 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); + + /* Check the certificates match */ + it = ssh_list_get_iterator(session->opts.certificate_non_exp); + assert_non_null(it); + it2 = ssh_list_get_iterator(new->opts.certificate_non_exp); assert_non_null(it2); while (it != NULL && it2 != NULL) { assert_string_equal(it->data, it2->data); @@ -890,10 +1003,12 @@ static void torture_options_copy(void **state) assert_string_equal(session->opts.pubkey_accepted_types, new->opts.pubkey_accepted_types); assert_string_equal(session->opts.ProxyCommand, new->opts.ProxyCommand); + assert_string_equal(session->opts.control_path, new->opts.control_path); /* 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.control_master, new->opts.control_master); assert_int_equal(session->opts.StrictHostKeyChecking, new->opts.StrictHostKeyChecking); assert_int_equal(session->opts.compressionlevel, @@ -911,6 +1026,34 @@ static void torture_options_copy(void **state) sizeof(session->opts.options_seen)); ssh_free(new); + + /* test if ssh_options_apply was called before ssh_options_copy + * the opts.identity list gets copied (percent expanded list) */ + rv = ssh_options_apply(session); + assert_ssh_return_code(session, rv); + + rv = ssh_options_copy(session, &new); + assert_ssh_return_code(session, rv); + assert_non_null(new); + + it = ssh_list_get_iterator(session->opts.identity_non_exp); + assert_null(it); + it2 = ssh_list_get_iterator(new->opts.identity_non_exp); + assert_null(it2); + + 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); + + ssh_free(new); } #define EXECUTABLE_NAME "test-exec" @@ -923,8 +1066,6 @@ static void torture_options_getopt(void **state) "-vv", "-v", "-r", "-c", "aes128-ctr", "-i", "id_rsa", "-C", "-2", "-1", NULL}; int argc = sizeof(argv)/sizeof(char *) - 1; - const char *argv_invalid[] = {EXECUTABLE_NAME, "-r", "-d", NULL}; - previous_level = ssh_get_log_level(); /* Test with all the supported options */ @@ -949,11 +1090,18 @@ static void torture_options_getopt(void **state) "aes128-ctr"); assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_S_C], "aes128-ctr"); - assert_string_equal(session->opts.identity->root->data, "id_rsa"); + assert_string_equal(session->opts.identity_non_exp->root->data, "id_rsa"); +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "zlib@openssh.com,none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "zlib@openssh.com,none"); +#else assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], - "zlib@openssh.com,zlib"); + "none"); assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], - "zlib@openssh.com,zlib"); + "none"); +#endif /* -1 and -2 are noop */ @@ -1007,36 +1155,39 @@ static void torture_options_getopt(void **state) assert_string_equal(argv[4], "hmac-sha1"); assert_string_equal(argv[5], "example.com"); - - /* Invalid configuration combination -d and -r (for some reason?) */ - argc = 3; - rc = ssh_options_getopt(session, &argc, (char **)argv_invalid); - assert_ssh_return_code_equal(session, rc, SSH_ERROR); - assert_int_equal(argc, 3); - assert_string_equal(argv_invalid[0], EXECUTABLE_NAME); - assert_string_equal(argv_invalid[1], "-r"); - assert_string_equal(argv_invalid[2], "-d"); - - /* Corner case: only one argument */ argv[1] = "-C"; argv[2] = NULL; argc = 2; rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no"); assert_ssh_return_code(session, rc); +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "none,zlib@openssh.com"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none,zlib@openssh.com"); +#else assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], "none"); assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], "none"); +#endif rc = ssh_options_getopt(session, &argc, (char **)argv); assert_ssh_return_code(session, rc); assert_int_equal(argc, 1); assert_string_equal(argv[0], EXECUTABLE_NAME); +#ifdef WITH_ZLIB assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], - "zlib@openssh.com,zlib"); + "zlib@openssh.com,none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "zlib@openssh.com,none"); +#else + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "none"); assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], - "zlib@openssh.com,zlib"); + "none"); +#endif /* Corner case: only hostname is not parsed */ argv[1] = "example.com"; @@ -1059,6 +1210,402 @@ static void torture_options_getopt(void **state) #endif /* _NSC_VER */ } +static void torture_options_plus_sign(void **state) +{ + ssh_session session = *state; + int rc; + const char *def_host_alg, *alg, *algs; + char *awaited; + size_t alg_len, algs_len; + + if (ssh_fips_mode()) { + alg = ",rsa-sha2-512-cert-v01@openssh.com"; + algs = ",rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521"; + def_host_alg = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + alg = ",ssh-rsa"; + algs = ",ssh-rsa,ssh-rsa-cert-v01@openssh.com"; + def_host_alg = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + alg_len = strlen(alg); + algs_len = strlen(algs); + + /* in fips mode, the default list is the available list, which means + * we can't append anything because everything enabled is already + * included */ + if (ssh_fips_mode()) { + awaited = strdup(def_host_alg); + assert_non_null(awaited); + } else { + awaited = calloc(strlen(def_host_alg) + alg_len + 1, 1); + assert_non_null(awaited); + + memcpy(awaited, def_host_alg, strlen(def_host_alg)); + memcpy(awaited+strlen(def_host_alg), alg, alg_len); + } + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "+rsa-sha2-512-cert-v01@openssh.com"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "+ssh-rsa"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + if (!ssh_fips_mode()) { + /* different algorithm list is used here */ + free(awaited); + + awaited = calloc(strlen(def_host_alg) + algs_len + 1, 1); + assert_non_null(awaited); + memcpy(awaited, def_host_alg, strlen(def_host_alg)); + memcpy(awaited+strlen(def_host_alg), algs, algs_len); + } + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "+rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "+ssh-rsa,ssh-rsa-cert-v01@openssh.com"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "+"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "+blablabla"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + def_host_alg); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, NULL); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + free(awaited); +} + +static void torture_options_minus_sign(void **state) +{ + ssh_session session = *state; + int rc; + const char *def_host_alg, *alg, *algs; + char *awaited, *p; + size_t alg_len, algs_len; + + if (ssh_fips_mode()) { + alg = "rsa-sha2-512-cert-v01@openssh.com,"; + algs = "rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521,"; + def_host_alg = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + alg = "ssh-ed25519,"; + algs = "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,"; + def_host_alg = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + alg_len = strlen(alg); + algs_len = strlen(algs); + + awaited = calloc(strlen(def_host_alg) + 1, 1); + assert_non_null(awaited); + + memcpy(awaited, def_host_alg, strlen(def_host_alg)); + p = strstr(awaited, alg); + assert_non_null(p); + memmove(p, p+alg_len, strlen(p + alg_len) + 1); + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-rsa-sha2-512-cert-v01@openssh.com"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-ssh-ed25519"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + p = strstr(awaited, algs); + assert_non_null(p); + memmove(p, p+algs_len, strlen(p + algs_len) + 1); + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-ssh-ed25519,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + def_host_alg); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-blablabla"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + def_host_alg); + + free(awaited); +} + +static void torture_options_caret_sign(void **state) +{ + ssh_session session = *state; + int rc; + const char *def_host_alg, *alg, *algs; + size_t alg_len, algs_len; + char *awaited, *p; + + if (ssh_fips_mode()) { + alg = "rsa-sha2-512-cert-v01@openssh.com,"; + algs = "rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521,"; + def_host_alg = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + alg = "ssh-rsa,"; + algs = "ssh-rsa,ssh-rsa-cert-v01@openssh.com,"; + def_host_alg = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + alg_len = strlen(alg); + algs_len = strlen(algs); + + awaited = calloc(strlen(def_host_alg) + alg_len + 1, 1); + assert_non_null(awaited); + + memcpy(awaited, alg, alg_len); + memcpy(awaited+alg_len, def_host_alg, strlen(def_host_alg)); + if (ssh_fips_mode()) { + p = strstr(awaited, alg); + /* look for second occurrence */ + p = strstr(p+1, algs); + memmove(p, p+alg_len, strlen(p + alg_len) + 1); + } + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "^rsa-sha2-512-cert-v01@openssh.com"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "^ssh-rsa"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + /* different algorithm list is used here */ + free(awaited); + + awaited = calloc(strlen(def_host_alg) + algs_len + 1, 1); + assert_non_null(awaited); + memcpy(awaited, algs, algs_len); + memcpy(awaited+algs_len, def_host_alg, strlen(def_host_alg)); + if (ssh_fips_mode()) { + p = strstr(awaited, algs); + /* look for second occurrence */ + p = strstr(p+1, algs); + memmove(p, p+algs_len, strlen(p + algs_len) + 1); + } + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "^rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "^ssh-rsa,ssh-rsa-cert-v01@openssh.com"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "^"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "^blablabla"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + def_host_alg); + + free(awaited); +} + +static void torture_options_apply (void **state) { + ssh_session session = *state; + struct ssh_list *awaited_list = NULL; + struct ssh_iterator *it1 = NULL, *it2 = NULL; + char *id = NULL; + int rc; + + rc = ssh_options_set(session, + SSH_OPTIONS_KNOWNHOSTS, + "%%d/.ssh/known_hosts"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + "/etc/%%u/libssh/known_hosts"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_PROXYCOMMAND, + "exec echo \"Hello libssh %%d!\""); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_ADD_IDENTITY, + "%%d/do_not_expand"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_apply(session); + assert_ssh_return_code(session, rc); + + /* check that the values got expanded */ + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_KNOWNHOSTS); + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS); + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_PROXYCOMMAND); + assert_true(ssh_list_count(session->opts.identity_non_exp) == 0); + assert_true(ssh_list_count(session->opts.identity) > 0); + + /* should not change anything calling it again */ + rc = ssh_options_apply(session); + assert_ssh_return_code(session, rc); + + /* check that the expansion was done only once */ + assert_string_equal(session->opts.knownhosts, "%d/.ssh/known_hosts"); + assert_string_equal(session->opts.global_knownhosts, + "/etc/%u/libssh/known_hosts"); + /* no exec should be added if there already is one */ + assert_string_equal(session->opts.ProxyCommand, + "exec echo \"Hello libssh %d!\""); + assert_string_equal(session->opts.identity->root->data, + "%d/do_not_expand"); + + /* apply should keep the freshest setting */ + rc = ssh_options_set(session, + SSH_OPTIONS_KNOWNHOSTS, + "hello there"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + "lorem ipsum"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_PROXYCOMMAND, + "mission_impossible"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_ADD_IDENTITY, + "007"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_ADD_IDENTITY, + "3"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_ADD_IDENTITY, + "2"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_ADD_IDENTITY, + "1"); + assert_ssh_return_code(session, rc); + + /* check that flags show need of escape expansion */ + assert_false(session->opts.exp_flags & SSH_OPT_EXP_FLAG_KNOWNHOSTS); + assert_false(session->opts.exp_flags & SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS); + assert_false(session->opts.exp_flags & SSH_OPT_EXP_FLAG_PROXYCOMMAND); + assert_false(ssh_list_count(session->opts.identity_non_exp) == 0); + + rc = ssh_options_apply(session); + assert_ssh_return_code(session, rc); + + /* check that the values got expanded */ + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_KNOWNHOSTS); + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS); + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_PROXYCOMMAND); + assert_true(ssh_list_count(session->opts.identity_non_exp) == 0); + + assert_string_equal(session->opts.knownhosts, "hello there"); + assert_string_equal(session->opts.global_knownhosts, "lorem ipsum"); + /* check that the "exec " was added at the beginning */ + assert_string_equal(session->opts.ProxyCommand, "exec mission_impossible"); + assert_string_equal(session->opts.identity->root->data, "1"); + + /* check the order of the identity files after double expansion */ + awaited_list = ssh_list_new(); + /* append the new data in order */ + id = strdup("1"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + id = strdup("2"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + id = strdup("3"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + id = strdup("007"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + id = strdup("%d/do_not_expand"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + /* append the defaults; this list is copied from ssh_new@src/session.c */ + id = ssh_path_expand_escape(session, "%d/id_ed25519"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); +#ifdef HAVE_ECC + id = ssh_path_expand_escape(session, "%d/id_ecdsa"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); +#endif + id = ssh_path_expand_escape(session, "%d/id_rsa"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(ssh_list_count(awaited_list), + ssh_list_count(session->opts.identity)); + + it1 = ssh_list_get_iterator(awaited_list); + assert_non_null(it1); + it2 = ssh_list_get_iterator(session->opts.identity); + assert_non_null(it2); + while (it1 != NULL && it2 != NULL) { + assert_string_equal(it1->data, it2->data); + + free((void*)it1->data); + it1 = it1->next; + it2 = it2->next; + } + assert_null(it1); + assert_null(it2); + + ssh_list_free(awaited_list); +} + +static void torture_options_set_verbosity (void **state) +{ + ssh_session session = *state; + int rc, new_level; + + rc = ssh_options_set(session, + SSH_OPTIONS_LOG_VERBOSITY_STR, + "3"); + assert_int_equal(rc, SSH_OK); + new_level = ssh_get_log_level(); + assert_int_equal(new_level, SSH_LOG_PACKET); + + rc = ssh_options_set(session, + SSH_OPTIONS_LOG_VERBOSITY_STR, + "datsun"); + assert_int_equal(rc, -1); + new_level = ssh_get_log_level(); + assert_int_not_equal(new_level, 0); +} + #ifdef WITH_SERVER const char template[] = "temp_dir_XXXXXX"; @@ -1105,10 +1652,6 @@ static int ssh_bind_setup_files(void **state) 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; @@ -1190,16 +1733,6 @@ static void torture_bind_options_import_key(void **state) 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); @@ -1249,15 +1782,6 @@ static void torture_bind_options_hostkey(void **state) 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) @@ -1313,6 +1837,10 @@ static void torture_bind_options_bindport_str(void **state) rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, "23"); assert_int_equal(rc, 0); assert_int_equal(bind->bindport, 23); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, "twentythree"); + assert_int_equal(rc, -1); + assert_int_not_equal(bind->bindport, 0); } static void torture_bind_options_log_verbosity(void **state) @@ -1362,32 +1890,15 @@ static void torture_bind_options_log_verbosity_str(void **state) new_level = ssh_get_log_level(); assert_int_equal(new_level, SSH_LOG_PACKET); + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "verbosity"); + assert_int_equal(rc, -1); + new_level = ssh_get_log_level(); + assert_int_not_equal(new_level, 0); + 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; @@ -1401,7 +1912,7 @@ static void torture_bind_options_rsakey(void **state) bind = test_state->bind; rc = ssh_bind_options_set(bind, - SSH_BIND_OPTIONS_RSAKEY, + SSH_BIND_OPTIONS_HOSTKEY, LIBSSH_RSA_TESTKEY); assert_int_equal(rc, 0); assert_non_null(bind->rsakey); @@ -1422,7 +1933,7 @@ static void torture_bind_options_ecdsakey(void **state) bind = test_state->bind; rc = ssh_bind_options_set(bind, - SSH_BIND_OPTIONS_ECDSAKEY, + SSH_BIND_OPTIONS_HOSTKEY, LIBSSH_ECDSA_521_TESTKEY); assert_int_equal(rc, 0); assert_non_null(bind->ecdsakey); @@ -1840,6 +2351,8 @@ int torture_run_tests(void) { 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_control_master, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_control_path, 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), @@ -1853,6 +2366,14 @@ int torture_run_tests(void) { setup, teardown), cmocka_unit_test_setup_teardown(torture_options_getopt, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_plus_sign, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_minus_sign, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_caret_sign, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_apply, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_verbosity, setup, teardown), }; #ifdef WITH_SERVER @@ -1871,10 +2392,6 @@ int torture_run_tests(void) { 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 diff --git a/tests/unittests/torture_packet.c b/tests/unittests/torture_packet.c index 9f07f394..faf78116 100644 --- a/tests/unittests/torture_packet.c +++ b/tests/unittests/torture_packet.c @@ -272,10 +272,15 @@ static void torture_packet_aes256_cbc_etm(UNUSED_PARAM(void **state)) } } -static void torture_packet_3des_cbc(void **state) +static void torture_packet_3des_cbc(UNUSED_PARAM(void **state)) { int i; - (void)state; /* unused */ + + /* 3des is not completely FIPS-allowed cipher since 140-3 */ + if (ssh_fips_mode()) { + skip(); + } + for (i=1;i<256;++i){ torture_packet("3des-cbc", "hmac-sha1", "none", i); } @@ -284,6 +289,12 @@ static void torture_packet_3des_cbc(void **state) static void torture_packet_3des_cbc_etm(UNUSED_PARAM(void **state)) { int i; + + /* 3des is not completely FIPS-allowed cipher since 140-3 */ + if (ssh_fips_mode()) { + skip(); + } + for (i = 1; i < 256; ++i) { torture_packet("3des-cbc", "hmac-sha1-etm@openssh.com", "none", i); } diff --git a/tests/unittests/torture_packet_filter.c b/tests/unittests/torture_packet_filter.c index ffa49602..06cd74a7 100644 --- a/tests/unittests/torture_packet_filter.c +++ b/tests/unittests/torture_packet_filter.c @@ -20,7 +20,7 @@ */ /* - * This test checks if the messages accepted by the packet filter were intented + * This test checks if the messages accepted by the packet filter were intended * to be accepted. * * The process consists in 2 steps: diff --git a/tests/unittests/torture_pki.c b/tests/unittests/torture_pki.c index d886245c..c610e8a2 100644 --- a/tests/unittests/torture_pki.c +++ b/tests/unittests/torture_pki.c @@ -5,10 +5,10 @@ #include <sys/stat.h> #include <fcntl.h> +#include "pki.c" #include "torture.h" #include "torture_pki.h" #include "torture_key.h" -#include "pki.c" const unsigned char INPUT[] = "1234567890123456789012345678901234567890" "123456789012345678901234"; @@ -125,15 +125,7 @@ struct key_attrs key_attrs_list[][5] = { {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 + /* Cannot remove this as it will break the array indexing used */ { {0, 0, "", 0, 0, "", 0}, /* DSS, AUTO */ {0, 0, "", 0, 0, "", 0}, /* DSS, SHA1 */ @@ -141,7 +133,6 @@ struct key_attrs key_attrs_list[][5] = { {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 */ @@ -164,21 +155,12 @@ struct key_attrs key_attrs_list[][5] = { {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 */ + {1, 1, "ssh-ed25519", 255, 33, "ssh-ed25519", 1}, /* ED25519, AUTO */ + {1, 1, "ssh-ed25519", 255, 0, "", 0}, /* ED25519, SHA1 */ + {1, 1, "ssh-ed25519", 255, 0, "", 0}, /* ED25519, SHA256 */ + {1, 1, "ssh-ed25519", 255, 0, "", 0}, /* ED25519, SHA384 */ + {1, 1, "ssh-ed25519", 255, 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 */ @@ -186,7 +168,6 @@ struct key_attrs key_attrs_list[][5] = { {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 */ @@ -260,12 +241,13 @@ static void torture_pki_verify_mismatch(void **state) enum ssh_digest_e hash; size_t input_length = sizeof(INPUT); struct key_attrs skey_attrs, vkey_attrs; + int bits; (void) state; ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); - for (sig_type = SSH_KEYTYPE_DSS; + for (sig_type = SSH_KEYTYPE_RSA; sig_type <= SSH_KEYTYPE_ED25519_CERT01; sig_type++) { @@ -274,8 +256,7 @@ static void torture_pki_verify_mismatch(void **state) hash++) { if (ssh_fips_mode()) { - if (sig_type == SSH_KEYTYPE_DSS || - sig_type == SSH_KEYTYPE_ED25519 || + if (sig_type == SSH_KEYTYPE_ED25519 || hash == SSH_DIGEST_SHA1) { /* In FIPS mode, skip unsupported algorithms */ @@ -294,6 +275,8 @@ static void torture_pki_verify_mismatch(void **state) assert_non_null(key); assert_int_equal(key->type, sig_type); assert_string_equal(key->type_c, skey_attrs.type_c); + bits = ssh_key_size(key); + assert_int_equal(bits, skey_attrs.size_arg); SSH_LOG(SSH_LOG_TRACE, "Creating signature %d with hash %d", sig_type, hash); @@ -338,13 +321,12 @@ static void torture_pki_verify_mismatch(void **state) input_length); assert_true(rc == SSH_OK); - for (key_type = SSH_KEYTYPE_DSS; + for (key_type = SSH_KEYTYPE_RSA; key_type <= SSH_KEYTYPE_ED25519_CERT01; key_type++) { if (ssh_fips_mode()) { - if (key_type == SSH_KEYTYPE_DSS || - key_type == SSH_KEYTYPE_ED25519) + if (key_type == SSH_KEYTYPE_ED25519) { /* In FIPS mode, skip unsupported algorithms */ continue; @@ -425,7 +407,6 @@ static void torture_pki_verify_mismatch(void **state) key = NULL; } } - ssh_free(session); } diff --git a/tests/unittests/torture_pki_dsa.c b/tests/unittests/torture_pki_dsa.c index b555e487..dfd59a64 100644 --- a/tests/unittests/torture_pki_dsa.c +++ b/tests/unittests/torture_pki_dsa.c @@ -6,10 +6,10 @@ #include <sys/stat.h> #include <fcntl.h> +#include "pki.c" #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" @@ -129,10 +129,8 @@ static void torture_pki_dsa_import_pubkey_file(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); + assert_int_equal(rc, SSH_ERROR); + assert_null(pubkey); } static void torture_pki_dsa_import_pubkey_from_openssh_privkey(void **state) @@ -144,10 +142,8 @@ static void torture_pki_dsa_import_pubkey_from_openssh_privkey(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); + assert_int_equal(rc, SSH_ERROR); + assert_null(pubkey); } static void torture_pki_dsa_import_privkey_base64(void **state) @@ -163,723 +159,33 @@ static void torture_pki_dsa_import_privkey_base64(void **state) 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); + assert_int_equal(rc, SSH_ERROR); + assert_null(key); } -static void torture_pki_fail_sign_with_incompatible_hash(void **state) +static void torture_pki_generate_dsa(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_int_equal(rc, SSH_ERROR); 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(); + assert_int_equal(rc, SSH_ERROR); + assert_null(cert); } int torture_run_tests(void) @@ -895,60 +201,20 @@ int torture_run_tests(void) 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) + cmocka_unit_test_setup_teardown(torture_pki_generate_dsa, + setup_dsa_key, + teardown), }; 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); - } + 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 index 22f06a46..f6b17e8a 100644 --- a/tests/unittests/torture_pki_ecdsa.c +++ b/tests/unittests/torture_pki_ecdsa.c @@ -1,14 +1,15 @@ #include "config.h" +#include "libssh/libssh.h" #define LIBSSH_STATIC #include <sys/stat.h> #include <fcntl.h> +#include "pki.c" #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" @@ -218,27 +219,60 @@ static void torture_pki_ecdsa_import_pubkey_from_openssh_privkey(void **state) SSH_KEY_FREE(pubkey); } -static void torture_pki_ecdsa_import_privkey_base64(void **state) +static void +torture_pki_ecdsa_import_export_privkey_base64_format(void **state, + enum ssh_file_format_e format) { int rc; - char *key_str = NULL; - ssh_key key = NULL; + char *key_str = NULL, *new_key_str = NULL; + ssh_key key = NULL, new_key = NULL; const char *passphrase = torture_get_testkey_passphrase(); - (void) state; /* unused */ + (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_int_equal(rc, 0); assert_non_null(key); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); + + /* Export */ + rc = ssh_pki_export_privkey_base64_format(key, + passphrase, + NULL, + NULL, + &new_key_str, + format); + assert_int_equal(rc, SSH_OK); + assert_non_null(new_key_str); + + /* and import again */ + rc = ssh_pki_import_privkey_base64(new_key_str, passphrase, NULL, NULL, + &new_key); + assert_int_equal(rc, 0); + assert_non_null(new_key); + + rc = ssh_key_is_private(new_key); + assert_int_equal(rc, 1); + + rc = ssh_key_cmp(key, new_key, SSH_KEY_CMP_PRIVATE | SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); free(key_str); + free(new_key_str); SSH_KEY_FREE(key); + SSH_KEY_FREE(new_key); +} + +static void +torture_pki_ecdsa_import_export_privkey_base64_default(void **state) +{ + torture_pki_ecdsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_DEFAULT); } static void torture_pki_ecdsa_import_privkey_base64_comment(void **state) @@ -261,11 +295,11 @@ static void torture_pki_ecdsa_import_privkey_base64_comment(void **state) 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_int_equal(rc, 0); assert_non_null(key); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); free(key_str); free(file_str); @@ -292,11 +326,11 @@ static void torture_pki_ecdsa_import_privkey_base64_whitespace(void **state) 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_int_equal(rc, 0); assert_non_null(key); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); free(key_str); free(file_str); @@ -318,11 +352,11 @@ static void torture_pki_ecdsa_publickey_from_privatekey(void **state) assert_non_null(key_str); rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(pubkey); free(key_str); @@ -337,15 +371,20 @@ static void torture_pki_ecdsa_import_cert_file(void **state) enum ssh_keytypes_e type; struct pki_st *test_state = *((struct pki_st **)state); + /* Importing public key as cert should fail */ + rc = ssh_pki_import_cert_file(LIBSSH_ECDSA_TESTKEY ".pub", &cert); + assert_int_equal(rc, SSH_ERROR); + assert_null(cert); + rc = ssh_pki_import_cert_file(LIBSSH_ECDSA_TESTKEY "-cert.pub", &cert); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(cert); type = ssh_key_type(cert); - assert_true(type == test_state->type+3); + assert_int_equal(type, test_state->type+3); rc = ssh_key_is_public(cert); - assert_true(rc == 1); + assert_int_equal(rc, 1); SSH_KEY_FREE(cert); } @@ -369,7 +408,7 @@ static void torture_pki_ecdsa_publickey_base64(void **state) } type = ssh_key_type_from_name(q); - assert_true(type == test_state->type); + assert_int_equal(type, test_state->type); q = ++p; while (p != NULL && *p != '\0' && *p != ' ') p++; @@ -378,11 +417,11 @@ static void torture_pki_ecdsa_publickey_base64(void **state) } rc = ssh_pki_import_pubkey_base64(q, type, &key); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(key); rc = ssh_pki_export_pubkey_base64(key, &b64_key); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(b64_key); assert_string_equal(q, b64_key); @@ -406,7 +445,7 @@ static void torture_pki_ecdsa_generate_pubkey_from_privkey(void **state) rc = torture_read_one_line(LIBSSH_ECDSA_TESTKEY ".pub", pubkey_original, sizeof(pubkey_original)); - assert_true(rc == 0); + assert_int_equal(rc, 0); /* remove the public key, generate it from the private key and write it. */ unlink(LIBSSH_ECDSA_TESTKEY ".pub"); @@ -416,22 +455,23 @@ static void torture_pki_ecdsa_generate_pubkey_from_privkey(void **state) NULL, NULL, &privkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(privkey); rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(pubkey); rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_ECDSA_TESTKEY ".pub"); - assert_true(rc == 0); + assert_int_equal(rc, 0); rc = torture_read_one_line(LIBSSH_ECDSA_TESTKEY ".pub", pubkey_generated, sizeof(pubkey_generated)); - assert_true(rc == 0); + assert_int_equal(rc, 0); len = torture_pubkey_len(pubkey_original); - assert_int_equal(strncmp(pubkey_original, pubkey_generated, len), 0); + assert_int_equal(len, torture_pubkey_len(pubkey_generated)); + assert_memory_equal(pubkey_original, pubkey_generated, len); SSH_KEY_FREE(privkey); SSH_KEY_FREE(pubkey); @@ -450,11 +490,11 @@ static void torture_pki_ecdsa_duplicate_key(void **state) (void) state; rc = ssh_pki_import_pubkey_file(LIBSSH_ECDSA_TESTKEY ".pub", &pubkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(pubkey); rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(b64_key); rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, @@ -462,27 +502,27 @@ static void torture_pki_ecdsa_duplicate_key(void **state) NULL, NULL, &privkey); - assert_true(rc == 0); + assert_int_equal(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_return_code(rc, errno); assert_non_null(pubkey_dup); rc = ssh_pki_export_pubkey_base64(pubkey_dup, &b64_key_gen); - assert_true(rc == 0); + assert_int_equal(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); + assert_int_equal(rc, 0); rc = ssh_key_cmp(pubkey, pubkey_dup, SSH_KEY_CMP_PUBLIC); - assert_true(rc == 0); + assert_int_equal(rc, 0); SSH_KEY_FREE(pubkey); SSH_KEY_FREE(pubkey_dup); @@ -539,7 +579,7 @@ static void torture_pki_generate_key_ecdsa(void **state) (void) state; rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P256, 0, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -547,13 +587,13 @@ static void torture_pki_generate_key_ecdsa(void **state) 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); + assert_return_code(rc, errno); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_ECDSA_P256); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P256); type_char = ssh_key_type_to_char(type); - assert_true(strcmp(type_char, "ecdsa-sha2-nistp256") == 0); + assert_string_equal(type_char, "ecdsa-sha2-nistp256"); etype_char = ssh_pki_key_ecdsa_name(key); - assert_true(strcmp(etype_char, "ecdsa-sha2-nistp256") == 0); + assert_string_equal(etype_char, "ecdsa-sha2-nistp256"); ssh_signature_free(sign); SSH_KEY_FREE(key); @@ -561,7 +601,7 @@ static void torture_pki_generate_key_ecdsa(void **state) /* deprecated */ rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 256, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -569,20 +609,20 @@ static void torture_pki_generate_key_ecdsa(void **state) 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); + assert_return_code(rc, errno); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_ECDSA_P256); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P256); type_char = ssh_key_type_to_char(type); - assert_true(strcmp(type_char, "ecdsa-sha2-nistp256") == 0); + assert_string_equal(type_char, "ecdsa-sha2-nistp256"); etype_char = ssh_pki_key_ecdsa_name(key); - assert_true(strcmp(etype_char, "ecdsa-sha2-nistp256") == 0); + assert_string_equal(etype_char, "ecdsa-sha2-nistp256"); 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_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -590,13 +630,13 @@ static void torture_pki_generate_key_ecdsa(void **state) 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); + assert_return_code(rc, errno); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_ECDSA_P384); + assert_int_equal(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); + assert_string_equal(type_char, "ecdsa-sha2-nistp384"); + etype_char = ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, "ecdsa-sha2-nistp384"); ssh_signature_free(sign); SSH_KEY_FREE(key); @@ -604,7 +644,7 @@ static void torture_pki_generate_key_ecdsa(void **state) /* deprecated */ rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 384, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -612,20 +652,20 @@ static void torture_pki_generate_key_ecdsa(void **state) 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); + assert_return_code(rc, errno); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_ECDSA_P384); + assert_int_equal(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); + assert_string_equal(type_char, "ecdsa-sha2-nistp384"); + etype_char = ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, "ecdsa-sha2-nistp384"); 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_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -633,13 +673,13 @@ static void torture_pki_generate_key_ecdsa(void **state) 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); + assert_return_code(rc, errno); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_ECDSA_P521); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P521); type_char = ssh_key_type_to_char(type); - assert_true(strcmp(type_char, "ecdsa-sha2-nistp521") == 0); + assert_string_equal(type_char, "ecdsa-sha2-nistp521"); etype_char =ssh_pki_key_ecdsa_name(key); - assert_true(strcmp(etype_char, "ecdsa-sha2-nistp521") == 0); + assert_string_equal(etype_char, "ecdsa-sha2-nistp521"); ssh_signature_free(sign); SSH_KEY_FREE(key); @@ -647,7 +687,7 @@ static void torture_pki_generate_key_ecdsa(void **state) /* deprecated */ rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 521, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -655,13 +695,13 @@ static void torture_pki_generate_key_ecdsa(void **state) 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); + assert_return_code(rc, errno); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_ECDSA_P521); + assert_int_equal(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); + assert_string_equal(type_char, "ecdsa-sha2-nistp521"); + etype_char = ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, "ecdsa-sha2-nistp521"); ssh_signature_free(sign); SSH_KEY_FREE(key); @@ -684,11 +724,11 @@ static void torture_pki_ecdsa_cert_verify(void **state) NULL, NULL, &privkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(privkey); rc = ssh_pki_import_cert_file(LIBSSH_ECDSA_TESTKEY "-cert.pub", &cert); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(cert); /* Get the hash type to be used in the signature based on the key type */ @@ -697,7 +737,7 @@ static void torture_pki_ecdsa_cert_verify(void **state) 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); + assert_return_code(rc, errno); ssh_signature_free(sign); SSH_KEY_FREE(privkey); SSH_KEY_FREE(cert); @@ -822,8 +862,9 @@ static void torture_pki_fail_sign_with_incompatible_hash(void **state) SSH_KEY_FREE(key); } -#ifdef HAVE_LIBCRYPTO -static void torture_pki_ecdsa_write_privkey(void **state) +static void +torture_pki_ecdsa_write_privkey_format(void **state, + enum ssh_file_format_e format) { ssh_key origkey = NULL; ssh_key privkey = NULL; @@ -836,28 +877,29 @@ static void torture_pki_ecdsa_write_privkey(void **state) NULL, NULL, &origkey); - assert_true(rc == 0); + assert_int_equal(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_export_privkey_file_format(origkey, + NULL, + NULL, + NULL, + LIBSSH_ECDSA_TESTKEY, + format); + assert_int_equal(rc, 0); rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, NULL, NULL, NULL, &privkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(privkey); rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); - assert_true(rc == 0); + assert_int_equal(rc, 0); SSH_KEY_FREE(origkey); SSH_KEY_FREE(privkey); @@ -868,16 +910,17 @@ static void torture_pki_ecdsa_write_privkey(void **state) NULL, NULL, &origkey); - assert_true(rc == 0); + assert_int_equal(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); + rc = ssh_pki_export_privkey_file_format(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_ECDSA_TESTKEY_PASSPHRASE, + format); + assert_int_equal(rc, 0); /* Test with invalid passphrase */ rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, @@ -885,7 +928,7 @@ static void torture_pki_ecdsa_write_privkey(void **state) NULL, NULL, &privkey); - assert_true(rc == SSH_ERROR); + assert_int_equal(rc, SSH_ERROR); assert_null(privkey); rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, @@ -893,15 +936,48 @@ static void torture_pki_ecdsa_write_privkey(void **state) NULL, NULL, &privkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(privkey); rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); - assert_true(rc == 0); + assert_int_equal(rc, 0); SSH_KEY_FREE(origkey); SSH_KEY_FREE(privkey); } + +static void +torture_pki_ecdsa_write_privkey(void **state) +{ + torture_pki_ecdsa_write_privkey_format(state, SSH_FILE_FORMAT_DEFAULT); +} + +#ifdef HAVE_LIBCRYPTO +static void +torture_pki_ecdsa_write_privkey_pem(void **state) +{ + torture_pki_ecdsa_write_privkey_format(state, SSH_FILE_FORMAT_PEM); +} + +static void +torture_pki_ecdsa_write_privkey_openssh(void **state) +{ + torture_pki_ecdsa_write_privkey_format(state, SSH_FILE_FORMAT_OPENSSH); +} + +static void +torture_pki_ecdsa_import_export_privkey_base64_pem(void **state) +{ + torture_pki_ecdsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_PEM); +} + +static void +torture_pki_ecdsa_import_export_privkey_base64_openssh(void **state) +{ + torture_pki_ecdsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_OPENSSH); +} #endif /* HAVE_LIBCRYPTO */ static void torture_pki_ecdsa_name(void **state, const char *expected_name) @@ -913,11 +989,11 @@ static void torture_pki_ecdsa_name(void **state, const char *expected_name) (void) state; /* unused */ rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, NULL, NULL, NULL, &key); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(key); - etype_char =ssh_pki_key_ecdsa_name(key); - assert_true(strcmp(etype_char, expected_name) == 0); + etype_char = ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, expected_name); SSH_KEY_FREE(key); } @@ -958,15 +1034,18 @@ int torture_run_tests(void) { 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_export_privkey_base64_default, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_ecdsa_key_521, + teardown), cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64_comment, setup_ecdsa_key_256, teardown), @@ -985,15 +1064,18 @@ int torture_run_tests(void) { 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_import_export_privkey_base64_default, + setup_openssh_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_openssh_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_openssh_ecdsa_key_521, + teardown), cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_from_privatekey, setup_ecdsa_key_256, teardown), @@ -1058,7 +1140,6 @@ int torture_run_tests(void) { 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), @@ -1068,6 +1149,49 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey, setup_ecdsa_key_521, teardown), +#ifdef HAVE_LIBCRYPTO + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_pem, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_pem, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_pem, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_openssh, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_openssh, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_openssh, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_pem, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_pem, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_pem, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_openssh, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_openssh, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_openssh, + 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), diff --git a/tests/unittests/torture_pki_ecdsa_uri.c b/tests/unittests/torture_pki_ecdsa_uri.c index 0fa6ecf3..fd3088b8 100644 --- a/tests/unittests/torture_pki_ecdsa_uri.c +++ b/tests/unittests/torture_pki_ecdsa_uri.c @@ -6,23 +6,19 @@ #include <sys/stat.h> #include <fcntl.h> +#include "pki.c" #include "torture.h" #include "torture_pki.h" #include "torture_key.h" -#include "pki.c" #define LIBSSH_ECDSA_TESTKEY "libssh_testkey.id_" #define LIBSSH_ECDSA_TESTKEY_PEM "libssh_testkey_pem.id_" -#define SOFTHSM_CONF "softhsm.conf" -#define PUB_URI_FMT_256 "pkcs11:token=ecdsa256;object=ecdsa256;type=public" -#define PRIV_URI_FMT_256 "pkcs11:token=ecdsa256;object=ecdsa256;type=private?pin-value=1234" -#define PUB_URI_FMT_384 "pkcs11:token=ecdsa384;object=ecdsa384;type=public" -#define PRIV_URI_FMT_384 "pkcs11:token=ecdsa384;object=ecdsa384;type=private?pin-value=1234" -#define PUB_URI_FMT_521 "pkcs11:token=ecdsa521;object=ecdsa521;type=public" -#define PRIV_URI_FMT_521 "pkcs11:token=ecdsa521;object=ecdsa521;type=private?pin-value=1234" -#define PRIV_URI_FMT_256_NO_PUB "pkcs11:token=ecdsa256_no_pub_uri;object=ecdsa256_no_pub_uri;type=private?pin-value=1234" -#define PRIV_URI_FMT_384_NO_PUB "pkcs11:token=ecdsa384_no_pub_uri;object=ecdsa384_no_pub_uri;type=private?pin-value=1234" -#define PRIV_URI_FMT_521_NO_PUB "pkcs11:token=ecdsa521_no_pub_uri;object=ecdsa521_no_pub_uri;type=private?pin-value=1234" +#define LABEL_256 "ecdsa256" +#define LABEL_384 "ecdsa384" +#define LABEL_521 "ecdsa521" +#define PUB_URI_FMT "pkcs11:token=%s;object=%s;type=public" +#define PRIV_URI_FMT "pkcs11:token=%s;object=%s;type=private?pin-value=1234" +#define PRIV_URI_NO_PUB_FMT "pkcs11:token=%s_no_pub_uri;object=%s_no_pub_uri;type=private?pin-value=1234" /** PKCS#11 URIs with invalid fields**/ @@ -31,7 +27,7 @@ #define PUB_URI_FMT_384_INVALID_TOKEN "pkcs11:token=ecdsa521;object=ecdsa384;type=public" #define PUB_URI_FMT_521_INVALID_OBJECT "pkcs11:token=ecdsa521;object=ecdsa384;type=public" -const char template[] = "temp_dir_XXXXXX"; +const char template[] = "/tmp/temp_dir_XXXXXX"; const unsigned char INPUT[] = "1234567890123456789012345678901234567890" "123456789012345678901234"; struct pki_st { @@ -80,7 +76,6 @@ static int setup_directory_structure(void **state) struct pki_st *test_state = NULL; char *temp_dir; int rc; - char conf_path[1024] = {0}; test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); assert_non_null(test_state); @@ -93,15 +88,13 @@ static int setup_directory_structure(void **state) rc = torture_change_dir(temp_dir); assert_int_equal(rc, 0); + SAFE_FREE(temp_dir); test_state->temp_dir = torture_get_current_working_dir(); assert_non_null(test_state->temp_dir); *state = test_state; - snprintf(conf_path, sizeof(conf_path), "%s/softhsm.conf", test_state->temp_dir); - setenv("SOFTHSM2_CONF", conf_path, 1); - setup_tokens_ecdsa(state, 256, "ecdsa256", "1"); setup_tokens_ecdsa(state, 384, "ecdsa384", "1"); setup_tokens_ecdsa(state, 521, "ecdsa521", "1"); @@ -117,7 +110,7 @@ static int teardown_directory_structure(void **state) struct pki_st *test_state = *state; int rc; - unsetenv("SOFTHSM2_CONF"); + torture_cleanup_tokens(test_state->temp_dir); rc = torture_change_dir(test_state->orig_dir); assert_int_equal(rc, 0); @@ -132,39 +125,47 @@ static int teardown_directory_structure(void **state) return 0; } -static void torture_pki_ecdsa_import_pubkey_uri(void **state, const char *uri) +static void torture_pki_ecdsa_import_pubkey_uri(void **state, const char *label) { + char uri[128] = {0}; ssh_key pubkey = NULL; int rc; + rc = snprintf(uri, sizeof(uri), PUB_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(uri) - 1); + rc = ssh_pki_import_pubkey_file(uri, &pubkey); assert_return_code(rc, errno); assert_non_null(pubkey); rc = ssh_key_is_public(pubkey); - assert_true(rc == 1); + assert_int_equal(rc, 1); SSH_KEY_FREE(pubkey); } static void torture_pki_ecdsa_import_pubkey_uri_256(void **state) { - torture_pki_ecdsa_import_pubkey_uri(state, PUB_URI_FMT_256); + torture_pki_ecdsa_import_pubkey_uri(state, LABEL_256); } static void torture_pki_ecdsa_import_pubkey_uri_384(void **state) { - torture_pki_ecdsa_import_pubkey_uri(state, PUB_URI_FMT_384); + torture_pki_ecdsa_import_pubkey_uri(state, LABEL_384); } static void torture_pki_ecdsa_import_pubkey_uri_521(void **state) { - torture_pki_ecdsa_import_pubkey_uri(state, PUB_URI_FMT_521); + torture_pki_ecdsa_import_pubkey_uri(state, LABEL_521); } -static void torture_pki_ecdsa_publickey_from_privatekey_uri(void **state, const char *uri, const char *type) +static void +torture_pki_ecdsa_publickey_from_privatekey_uri(void **state, + const char *label, + const char *type) { int rc; + char uri[128] = {0}; ssh_key privkey = NULL; ssh_key pubkey = NULL; ssh_string pblob = NULL; @@ -175,38 +176,38 @@ static void torture_pki_ecdsa_publickey_from_privatekey_uri(void **state, const char pub_filename_generated[1024]; char pub_filename_pem[1024]; + rc = snprintf(uri, sizeof(uri), PRIV_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(uri) - 1); + rc = ssh_pki_import_privkey_file(uri, NULL, NULL, NULL, &privkey); assert_return_code(rc, errno); - assert_true(rc == 0); assert_non_null(privkey); rc = ssh_pki_export_pubkey_blob(privkey, &pblob); assert_return_code(rc, errno); - assert_true(rc == SSH_OK); assert_non_null(pblob); + ssh_string_free(pblob); rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); assert_return_code(rc, errno); - assert_true(rc == SSH_OK); assert_non_null(pubkey); snprintf(pub_filename, sizeof(pub_filename), "%s%s%s", LIBSSH_ECDSA_TESTKEY, type, ".pub"); snprintf(pub_filename_generated, sizeof(pub_filename_generated), "%s%s%s", - LIBSSH_ECDSA_TESTKEY_PEM, type, "generated.pub"); + LIBSSH_ECDSA_TESTKEY_PEM, type, "generated.pub"); snprintf(pub_filename_pem, sizeof(pub_filename_pem), "%s%s%s", LIBSSH_ECDSA_TESTKEY_PEM, type, ".pub"); rc = torture_read_one_line(pub_filename, pubkey_original, sizeof(pubkey_original)); - assert_true(rc == 0); + assert_return_code(rc, errno); rc = ssh_pki_export_pubkey_file(pubkey, pub_filename_generated); assert_return_code(rc, errno); - assert_true(rc == 0); /* remove the public key, generate it from the private key and write it. */ unlink(pub_filename); @@ -219,28 +220,47 @@ static void torture_pki_ecdsa_publickey_from_privatekey_uri(void **state, const rc = torture_read_one_line(pub_filename_pem, pubkey_generated, sizeof(pubkey_generated)); - assert_true(rc == 0); + assert_return_code(rc, errno); - assert_int_equal(strncmp(pubkey_original, pubkey_generated, strlen(pubkey_original)), 0); + assert_memory_equal(pubkey_original, pubkey_generated, strlen(pubkey_original)); SSH_KEY_FREE(privkey); SSH_KEY_FREE(pubkey); } -static void import_pubkey_without_loading_public_uri(void **state, const char *uri, const char *type) +static void torture_pki_ecdsa_publickey_from_privatekey_uri_256(void **state) +{ + torture_pki_ecdsa_publickey_from_privatekey_uri(state, LABEL_256, "ecdsa256"); +} + +static void torture_pki_ecdsa_publickey_from_privatekey_uri_384(void **state) +{ + torture_pki_ecdsa_publickey_from_privatekey_uri(state, LABEL_384, "ecdsa384"); +} + +static void torture_pki_ecdsa_publickey_from_privatekey_uri_521(void **state) +{ + torture_pki_ecdsa_publickey_from_privatekey_uri(state, LABEL_521, "ecdsa521"); +} + +static void +import_pubkey_without_loading_public_uri(void **state, const char *label) { int rc; + char uri[128] = {0}; ssh_key privkey = NULL; ssh_key pubkey = NULL; ssh_string pblob = NULL; + rc = snprintf(uri, sizeof(uri), PRIV_URI_NO_PUB_FMT, label, label); + assert_in_range(rc, 0, sizeof(uri) - 1); + rc = ssh_pki_import_privkey_file(uri, NULL, NULL, NULL, &privkey); assert_return_code(rc, errno); - assert_true(rc == 0); assert_non_null(privkey); rc = ssh_pki_export_pubkey_blob(privkey, &pblob); @@ -252,48 +272,41 @@ static void import_pubkey_without_loading_public_uri(void **state, const char *u assert_null(pubkey); SSH_KEY_FREE(privkey); - SSH_KEY_FREE(pubkey); -} - -static void torture_pki_ecdsa_publickey_from_privatekey_uri_256(void **state) -{ - torture_pki_ecdsa_publickey_from_privatekey_uri(state, PRIV_URI_FMT_256, "ecdsa256"); -} - -static void torture_pki_ecdsa_publickey_from_privatekey_uri_384(void **state) -{ - torture_pki_ecdsa_publickey_from_privatekey_uri(state, PRIV_URI_FMT_384, "ecdsa384"); -} - -static void torture_pki_ecdsa_publickey_from_privatekey_uri_521(void **state) -{ - torture_pki_ecdsa_publickey_from_privatekey_uri(state, PRIV_URI_FMT_521, "ecdsa521"); } static void torture_pki_ecdsa_import_pubkey_without_loading_public_uri_256(void **state) { - import_pubkey_without_loading_public_uri(state, PRIV_URI_FMT_256_NO_PUB, "ecdsa256_no_pub_uri"); + import_pubkey_without_loading_public_uri(state, LABEL_256); } static void torture_pki_ecdsa_import_pubkey_without_loading_public_uri_384(void **state) { - import_pubkey_without_loading_public_uri(state, PRIV_URI_FMT_384_NO_PUB, "ecdsa384_no_pub_uri"); + import_pubkey_without_loading_public_uri(state, LABEL_384); } static void torture_pki_ecdsa_import_pubkey_without_loading_public_uri_521(void **state) { - import_pubkey_without_loading_public_uri(state, PRIV_URI_FMT_521_NO_PUB, "ecdsa521_no_pub_uri"); + import_pubkey_without_loading_public_uri(state, LABEL_521); } -static void torture_ecdsa_sign_verify_uri(void **state, const char *uri, enum ssh_digest_e dig_type) +static void +torture_ecdsa_sign_verify_uri(void **state, + const char *label, + enum ssh_digest_e dig_type) { int rc; + char uri[128] = {0}; ssh_key privkey = 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(); + ssh_session session = ssh_new(); + + assert_non_null(session); + + rc = snprintf(uri, sizeof(uri), PRIV_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(uri) - 1); rc = ssh_pki_import_privkey_file(uri, NULL, @@ -301,12 +314,10 @@ static void torture_ecdsa_sign_verify_uri(void **state, const char *uri, enum ss NULL, &privkey); assert_return_code(rc, errno); - assert_true(rc == 0); assert_non_null(privkey); rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); assert_return_code(rc, errno); - assert_int_equal(rc, SSH_OK); assert_non_null(pubkey); sign = pki_do_sign(privkey, INPUT, sizeof(INPUT), dig_type); @@ -314,13 +325,12 @@ static void torture_ecdsa_sign_verify_uri(void **state, const char *uri, enum ss rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); assert_return_code(rc, errno); - assert_true(rc == SSH_OK); type = ssh_key_type(privkey); type_char = ssh_key_type_to_char(type); etype_char = ssh_pki_key_ecdsa_name(privkey); - switch(dig_type) { + switch (dig_type) { case SSH_DIGEST_SHA256: assert_true(type == SSH_KEYTYPE_ECDSA_P256); assert_string_equal(type_char, "ecdsa-sha2-nistp256"); @@ -340,6 +350,7 @@ static void torture_ecdsa_sign_verify_uri(void **state, const char *uri, enum ss printf("Invalid hash type: %d\n", dig_type); } + ssh_free(session); ssh_signature_free(sign); SSH_KEY_FREE(privkey); SSH_KEY_FREE(pubkey); @@ -347,22 +358,24 @@ static void torture_ecdsa_sign_verify_uri(void **state, const char *uri, enum ss static void torture_ecdsa_sign_verify_uri_256(void **state) { - torture_ecdsa_sign_verify_uri(state, PRIV_URI_FMT_256, SSH_DIGEST_SHA256); + torture_ecdsa_sign_verify_uri(state, LABEL_256, SSH_DIGEST_SHA256); } static void torture_ecdsa_sign_verify_uri_384(void **state) { - torture_ecdsa_sign_verify_uri(state, PRIV_URI_FMT_384, SSH_DIGEST_SHA384); + torture_ecdsa_sign_verify_uri(state, LABEL_384, SSH_DIGEST_SHA384); } static void torture_ecdsa_sign_verify_uri_521(void **state) { - torture_ecdsa_sign_verify_uri(state, PRIV_URI_FMT_521, SSH_DIGEST_SHA512); + torture_ecdsa_sign_verify_uri(state, LABEL_521, SSH_DIGEST_SHA512); } -static void torture_pki_ecdsa_duplicate_key_uri(void **state, const char *priv_uri, const char *pub_uri) +static void torture_pki_ecdsa_duplicate_key_uri(void **state, const char *label) { int rc; + char pub_uri[128] = {0}; + char priv_uri[128] = {0}; char *b64_key = NULL; char *b64_key_gen = NULL; ssh_key pubkey = NULL; @@ -372,12 +385,17 @@ static void torture_pki_ecdsa_duplicate_key_uri(void **state, const char *priv_u (void) state; + rc = snprintf(pub_uri, sizeof(pub_uri), PUB_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(pub_uri) - 1); + rc = snprintf(priv_uri, sizeof(priv_uri), PRIV_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(priv_uri) - 1); + rc = ssh_pki_import_pubkey_file(pub_uri, &pubkey); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(pubkey); rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(b64_key); rc = ssh_pki_import_privkey_file(priv_uri, @@ -385,27 +403,27 @@ static void torture_pki_ecdsa_duplicate_key_uri(void **state, const char *priv_u NULL, NULL, &privkey); - assert_true(rc == 0); + assert_return_code(rc, errno); 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_return_code(rc, errno); assert_non_null(pubkey_dup); rc = ssh_pki_export_pubkey_base64(pubkey_dup, &b64_key_gen); - assert_true(rc == 0); + assert_return_code(rc, errno); 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); + assert_return_code(rc, errno); rc = ssh_key_cmp(pubkey, pubkey_dup, SSH_KEY_CMP_PUBLIC); - assert_true(rc == 0); + assert_return_code(rc, errno); SSH_KEY_FREE(pubkey); SSH_KEY_FREE(pubkey_dup); @@ -417,21 +435,23 @@ static void torture_pki_ecdsa_duplicate_key_uri(void **state, const char *priv_u static void torture_pki_ecdsa_duplicate_key_uri_256(void **state) { - torture_pki_ecdsa_duplicate_key_uri(state, PRIV_URI_FMT_256, PUB_URI_FMT_256); + torture_pki_ecdsa_duplicate_key_uri(state, LABEL_256); } static void torture_pki_ecdsa_duplicate_key_uri_384(void **state) { - torture_pki_ecdsa_duplicate_key_uri(state, PRIV_URI_FMT_384, PUB_URI_FMT_384); + torture_pki_ecdsa_duplicate_key_uri(state, LABEL_384); } static void torture_pki_ecdsa_duplicate_key_uri_521(void **state) { - torture_pki_ecdsa_duplicate_key_uri(state, PRIV_URI_FMT_521, PUB_URI_FMT_521); + torture_pki_ecdsa_duplicate_key_uri(state, LABEL_521); } -static void torture_pki_ecdsa_duplicate_then_demote_uri(void **state, const char *priv_uri) +static void +torture_pki_ecdsa_duplicate_then_demote_uri(void **state, const char *label) { + char priv_uri[128] = {0}; ssh_key pubkey = NULL; ssh_key privkey = NULL; ssh_key privkey_dup = NULL; @@ -439,12 +459,15 @@ static void torture_pki_ecdsa_duplicate_then_demote_uri(void **state, const char (void) state; + rc = snprintf(priv_uri, sizeof(priv_uri), PRIV_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(priv_uri) - 1); + rc = ssh_pki_import_privkey_file(priv_uri, NULL, NULL, NULL, &privkey); - assert_int_equal(rc, 0); + assert_return_code(rc, errno); assert_non_null(privkey); privkey_dup = ssh_key_dup(privkey); @@ -452,7 +475,7 @@ static void torture_pki_ecdsa_duplicate_then_demote_uri(void **state, const char 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_return_code(rc, errno); assert_non_null(pubkey); assert_int_equal(pubkey->ecdsa_nid, privkey->ecdsa_nid); @@ -463,17 +486,17 @@ static void torture_pki_ecdsa_duplicate_then_demote_uri(void **state, const char static void torture_pki_ecdsa_duplicate_then_demote_uri_256(void **state) { - torture_pki_ecdsa_duplicate_then_demote_uri(state, PRIV_URI_FMT_256); + torture_pki_ecdsa_duplicate_then_demote_uri(state, LABEL_256); } static void torture_pki_ecdsa_duplicate_then_demote_uri_384(void **state) { - torture_pki_ecdsa_duplicate_then_demote_uri(state, PRIV_URI_FMT_384); + torture_pki_ecdsa_duplicate_then_demote_uri(state, LABEL_384); } static void torture_pki_ecdsa_duplicate_then_demote_uri_521(void **state) { - torture_pki_ecdsa_duplicate_then_demote_uri(state, PRIV_URI_FMT_521); + torture_pki_ecdsa_duplicate_then_demote_uri(state, LABEL_521); } static void torture_pki_ecdsa_import_pubkey_uri_invalid_configurations(void **state) @@ -510,9 +533,6 @@ static void torture_pki_ecdsa_import_pubkey_uri_invalid_configurations(void **st &pubkey); assert_int_not_equal(rc, 0); assert_null(pubkey); - - SSH_KEY_FREE(privkey); - SSH_KEY_FREE(pubkey); } int torture_run_tests(void) { @@ -540,15 +560,16 @@ int torture_run_tests(void) { cmocka_unit_test(torture_pki_ecdsa_import_pubkey_without_loading_public_uri_384), cmocka_unit_test(torture_pki_ecdsa_import_pubkey_without_loading_public_uri_521), }; - ssh_session session = ssh_new(); int verbosity = SSH_LOG_FUNCTIONS; - ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_init(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); torture_filter_tests(tests); rc = cmocka_run_group_tests(tests, setup_directory_structure, teardown_directory_structure); + ssh_free(session); ssh_finalize(); return rc; diff --git a/tests/unittests/torture_pki_ed25519.c b/tests/unittests/torture_pki_ed25519.c index ff59b190..0142fbe3 100644 --- a/tests/unittests/torture_pki_ed25519.c +++ b/tests/unittests/torture_pki_ed25519.c @@ -2,10 +2,10 @@ #define LIBSSH_STATIC +#include "pki.c" #include "torture.h" #include "torture_key.h" #include "torture_pki.h" -#include "pki.c" #include <sys/stat.h> #include <fcntl.h> @@ -247,10 +247,10 @@ static void torture_pki_ed25519_import_export_privkey_base64(void **state) assert_non_null(key); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_ED25519); + assert_int_equal(type, SSH_KEYTYPE_ED25519); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); rc = ssh_pki_export_privkey_base64(key, passphrase, @@ -270,10 +270,10 @@ static void torture_pki_ed25519_import_export_privkey_base64(void **state) assert_non_null(key); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_ED25519); + assert_int_equal(type, SSH_KEYTYPE_ED25519); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); SSH_STRING_FREE_CHAR(b64_key); SSH_KEY_FREE(key); @@ -317,6 +317,11 @@ static void torture_pki_ed25519_import_cert_file(void **state) (void) state; /* unused */ + /* Importing public key as cert should fail */ + rc = ssh_pki_import_cert_file(LIBSSH_ED25519_TESTKEY ".pub", &cert); + assert_int_equal(rc, SSH_ERROR); + assert_null(cert); + rc = ssh_pki_import_cert_file(LIBSSH_ED25519_TESTKEY "-cert.pub", &cert); assert_true(rc == 0); assert_non_null(cert); @@ -448,7 +453,7 @@ static void torture_pki_ed25519_generate_key(void **state) assert_true(strcmp(type_char, "ssh-ed25519") == 0); /* try an invalid signature */ -#ifdef HAVE_OPENSSL_ED25519 +#ifdef HAVE_LIBCRYPTO raw_sig_data = ssh_string_data(sign->raw_sig); #else raw_sig_data = (uint8_t *)sign->ed25519_sig; @@ -503,70 +508,83 @@ static void torture_pki_ed25519_cert_verify(void **state) ssh_free(session); } -static void torture_pki_ed25519_write_privkey(void **state) +static void +torture_pki_ed25519_write_privkey_format(void **state, + enum ssh_file_format_e format) { ssh_key origkey = NULL; ssh_key privkey = NULL; int rc; - (void) state; /* unused */ + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, - NULL, - NULL, - NULL, - &origkey); - assert_true(rc == 0); + NULL, + NULL, + NULL, + &origkey); + assert_int_equal(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_export_privkey_file_format(origkey, + NULL, + NULL, + NULL, + LIBSSH_ED25519_TESTKEY, + format); + assert_int_equal(rc, 0); rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, - NULL, - NULL, - NULL, - &privkey); - assert_true(rc == 0); + NULL, + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); assert_non_null(privkey); rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); - assert_true(rc == 0); + assert_int_equal(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_export_privkey_file_format(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_ED25519_TESTKEY, + format); + assert_int_equal(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); + /* Opening passphrase protected key will prompt for the pin interactively, + * which would hang in the test */ + if (format != SSH_FILE_FORMAT_PEM) { + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + /* opening without passphrase should fail */ + assert_int_equal(rc, SSH_ERROR); + } rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, - torture_get_testkey_passphrase(), - NULL, - NULL, - &privkey); - assert_true(rc == 0); + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); assert_non_null(privkey); rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); - assert_true(rc == 0); + assert_int_equal(rc, 0); unlink(LIBSSH_ED25519_TESTKEY); SSH_KEY_FREE(origkey); @@ -578,16 +596,17 @@ static void torture_pki_ed25519_write_privkey(void **state) NULL, NULL, &origkey); - assert_true(rc == 0); + assert_int_equal(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); + rc = ssh_pki_export_privkey_file_format(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_ED25519_TESTKEY_PASSPHRASE, + format); + assert_int_equal(rc, 0); /* Test with invalid passphrase */ rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, @@ -595,23 +614,43 @@ static void torture_pki_ed25519_write_privkey(void **state) NULL, NULL, &privkey); - assert_true(rc == SSH_ERROR); + assert_int_equal(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_int_equal(rc, 0); assert_non_null(privkey); rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); - assert_true(rc == 0); + assert_int_equal(rc, 0); SSH_KEY_FREE(origkey); SSH_KEY_FREE(privkey); } +static void +torture_pki_ed25519_write_privkey(void **state) +{ + torture_pki_ed25519_write_privkey_format(state, SSH_FILE_FORMAT_DEFAULT); +} + +#ifdef HAVE_LIBCRYPTO +static void +torture_pki_ed25519_write_privkey_pem(void **state) +{ + torture_pki_ed25519_write_privkey_format(state, SSH_FILE_FORMAT_PEM); +} + +static void +torture_pki_ed25519_write_privkey_openssh(void **state) +{ + torture_pki_ed25519_write_privkey_format(state, SSH_FILE_FORMAT_OPENSSH); +} +#endif + static void torture_pki_ed25519_sign(void **state) { ssh_key privkey = NULL; @@ -690,7 +729,7 @@ static void torture_pki_ed25519_sign_openssh_privkey_passphrase(void **state) SSH_STRING_FREE(blob); } -#ifdef HAVE_OPENSSL_ED25519 +#ifdef HAVE_LIBCRYPTO static void torture_pki_ed25519_sign_pkcs8_privkey(void **state) { ssh_key privkey = NULL; @@ -766,7 +805,7 @@ static void torture_pki_ed25519_sign_pkcs8_privkey_passphrase(void **state) SSH_KEY_FREE(privkey); SSH_STRING_FREE(blob); } -#endif /* HAVE_OPENSSL_ED25519 */ +#endif /* HAVE_LIBCRYPTO */ static void torture_pki_ed25519_verify(void **state){ ssh_key pubkey = NULL; @@ -805,7 +844,7 @@ static void torture_pki_ed25519_verify(void **state){ assert_true(rc == SSH_OK); /* Alter signature and expect verification error */ -#if defined(HAVE_OPENSSL_ED25519) +#ifdef HAVE_LIBCRYPTO raw_sig_data = ssh_string_data(sig->raw_sig); #else raw_sig_data = (uint8_t *)sig->ed25519_sig; @@ -1015,9 +1054,16 @@ int torture_run_tests(void) { 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 +#ifdef HAVE_LIBCRYPTO cmocka_unit_test(torture_pki_ed25519_sign_pkcs8_privkey), cmocka_unit_test(torture_pki_ed25519_sign_pkcs8_privkey_passphrase), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_write_privkey_pem, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_write_privkey_openssh, + setup_ed25519_key, + teardown), #endif cmocka_unit_test(torture_pki_ed25519_verify), cmocka_unit_test(torture_pki_ed25519_verify_bad), diff --git a/tests/unittests/torture_pki_rsa.c b/tests/unittests/torture_pki_rsa.c index a9867069..4da6b148 100644 --- a/tests/unittests/torture_pki_rsa.c +++ b/tests/unittests/torture_pki_rsa.c @@ -1,15 +1,16 @@ #include "config.h" +#include "libssh/libssh.h" #define LIBSSH_STATIC #include <sys/stat.h> #include <fcntl.h> +#include "pki.c" #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" @@ -164,7 +165,7 @@ static void torture_pki_rsa_import_privkey_base64_NULL_key(void **state) NULL, NULL, NULL); - assert_true(rc == -1); + assert_int_equal(rc, -1); } @@ -178,16 +179,18 @@ static void torture_pki_rsa_import_privkey_base64_NULL_str(void **state) /* 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); + assert_int_equal(rc, -1); SSH_KEY_FREE(key); } -static void torture_pki_rsa_import_privkey_base64(void **state) +static void +torture_pki_rsa_import_export_privkey_base64_format(void **state, + enum ssh_file_format_e format) { int rc; - char *key_str = NULL; - ssh_key key = NULL; + char *key_str = NULL, *new_key_str = NULL; + ssh_key key = NULL, new_key = NULL; const char *passphrase = torture_get_testkey_passphrase(); enum ssh_keytypes_e type; @@ -196,21 +199,62 @@ static void torture_pki_rsa_import_privkey_base64(void **state) key_str = torture_pki_read_file(LIBSSH_RSA_TESTKEY); assert_non_null(key_str); + /* Import test key */ rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(key); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_RSA); + assert_int_equal(type, SSH_KEYTYPE_RSA); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); rc = ssh_key_is_public(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); + + /* Export */ + rc = ssh_pki_export_privkey_base64_format(key, + passphrase, + NULL, + NULL, + &new_key_str, + format); + assert_int_equal(rc, SSH_OK); + assert_non_null(new_key_str); + + /* and import again */ + rc = ssh_pki_import_privkey_base64(new_key_str, + passphrase, + NULL, + NULL, + &new_key); + assert_int_equal(rc, 0); + assert_non_null(new_key); + + type = ssh_key_type(new_key); + assert_int_equal(type, SSH_KEYTYPE_RSA); + + rc = ssh_key_is_private(new_key); + assert_int_equal(rc, 1); + + rc = ssh_key_is_public(new_key); + assert_int_equal(rc, 1); + + rc = ssh_key_cmp(key, new_key, SSH_KEY_CMP_PRIVATE|SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); free(key_str); + free(new_key_str); SSH_KEY_FREE(key); + SSH_KEY_FREE(new_key); +} + +static void +torture_pki_rsa_import_export_privkey_base64(void **state) +{ + torture_pki_rsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_DEFAULT); } static void torture_pki_rsa_import_privkey_base64_comment(void **state) @@ -234,17 +278,17 @@ static void torture_pki_rsa_import_privkey_base64_comment(void **state) 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_return_code(rc, errno); assert_non_null(key); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_RSA); + assert_int_equal(type, SSH_KEYTYPE_RSA); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); rc = ssh_key_is_public(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); free(key_str); free(file_str); @@ -272,17 +316,17 @@ static void torture_pki_rsa_import_privkey_base64_whitespace(void **state) 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_return_code(rc, errno); assert_non_null(key); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_RSA); + assert_int_equal(type, SSH_KEYTYPE_RSA); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); rc = ssh_key_is_public(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); free(key_str); free(file_str); @@ -303,14 +347,14 @@ static void torture_pki_rsa_publickey_from_privatekey(void **state) NULL, NULL, &key); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(pubkey); SSH_KEY_FREE(key); @@ -330,14 +374,19 @@ static void torture_pki_rsa_copy_cert_to_privkey(void **state) ssh_key privkey = NULL; ssh_key cert = NULL; - (void) state; /* unused */ + (void)state; /* unused */ + + /* Importing public key as cert should fail */ + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY ".pub", &cert); + assert_int_equal(rc, SSH_ERROR); + assert_null(cert); rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(cert); rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(pubkey); rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 0), @@ -345,32 +394,48 @@ static void torture_pki_rsa_copy_cert_to_privkey(void **state) NULL, NULL, &privkey); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(privkey); /* Basic sanity. */ rc = ssh_pki_copy_cert_to_privkey(NULL, privkey); - assert_true(rc == SSH_ERROR); + assert_int_equal(rc, SSH_ERROR); rc = ssh_pki_copy_cert_to_privkey(pubkey, NULL); - assert_true(rc == SSH_ERROR); + assert_int_equal(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); + assert_int_equal(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_return_code(rc, errno); assert_non_null(pubkey->cert); rc = ssh_pki_copy_cert_to_privkey(cert, privkey); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); 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); + assert_int_equal(rc, SSH_ERROR); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + + /* Generate different key and try to assign it this certificate */ + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_int_equal(rc, SSH_ERROR); SSH_KEY_FREE(cert); SSH_KEY_FREE(privkey); @@ -385,14 +450,14 @@ static void torture_pki_rsa_import_cert_file(void **state) { (void) state; /* unused */ rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(cert); type = ssh_key_type(cert); - assert_true(type == SSH_KEYTYPE_RSA_CERT01); + assert_int_equal(type, SSH_KEYTYPE_RSA_CERT01); rc = ssh_key_is_public(cert); - assert_true(rc == 1); + assert_int_equal(rc, 1); SSH_KEY_FREE(cert); } @@ -417,7 +482,7 @@ static void torture_pki_rsa_publickey_base64(void **state) } type = ssh_key_type_from_name(q); - assert_true(type == SSH_KEYTYPE_RSA); + assert_int_equal(type, SSH_KEYTYPE_RSA); q = ++p; while (p != NULL && *p != '\0' && *p != ' ') p++; @@ -426,11 +491,11 @@ static void torture_pki_rsa_publickey_base64(void **state) } rc = ssh_pki_import_pubkey_base64(q, type, &key); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_pubkey_base64(key, &b64_key); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(b64_key); assert_string_equal(q, b64_key); @@ -457,20 +522,20 @@ static void torture_pki_rsa_generate_pubkey_from_privkey(void **state) { NULL, NULL, &privkey); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(privkey); rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(pubkey); rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_RSA_TESTKEY ".pub"); - assert_true(rc == 0); + assert_return_code(rc, errno); rc = torture_read_one_line(LIBSSH_RSA_TESTKEY ".pub", pubkey_generated, sizeof(pubkey_generated)); - assert_true(rc == 0); + assert_return_code(rc, errno); len = torture_pubkey_len(torture_get_testkey_pub(SSH_KEYTYPE_RSA)); assert_memory_equal(torture_get_testkey_pub(SSH_KEYTYPE_RSA), @@ -494,11 +559,11 @@ static void torture_pki_rsa_duplicate_key(void **state) (void) state; rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(pubkey); rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(b64_key); rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, @@ -506,27 +571,27 @@ static void torture_pki_rsa_duplicate_key(void **state) NULL, NULL, &privkey); - assert_true(rc == 0); + assert_return_code(rc, errno); 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_return_code(rc, errno); assert_non_null(pubkey_dup); rc = ssh_pki_export_pubkey_base64(pubkey_dup, &b64_key_gen); - assert_true(rc == 0); + assert_return_code(rc, errno); 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); + assert_return_code(rc, errno); rc = ssh_key_cmp(pubkey, pubkey_dup, SSH_KEY_CMP_PUBLIC); - assert_true(rc == 0); + assert_return_code(rc, errno); SSH_KEY_FREE(pubkey); SSH_KEY_FREE(pubkey_dup); @@ -541,12 +606,16 @@ 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(); + ssh_session session = ssh_new(); + int verbosity = torture_libssh_verbosity(); + (void) state; + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + if (!ssh_fips_mode()) { rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 1024, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -554,7 +623,7 @@ static void torture_pki_rsa_generate_key(void **state) 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); + assert_return_code(rc, errno); ssh_signature_free(sign); SSH_KEY_FREE(key); SSH_KEY_FREE(pubkey); @@ -563,7 +632,7 @@ static void torture_pki_rsa_generate_key(void **state) } rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -571,7 +640,7 @@ static void torture_pki_rsa_generate_key(void **state) 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); + assert_return_code(rc, errno); ssh_signature_free(sign); SSH_KEY_FREE(key); SSH_KEY_FREE(pubkey); @@ -579,7 +648,7 @@ static void torture_pki_rsa_generate_key(void **state) pubkey = NULL; rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 4096, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -587,7 +656,7 @@ static void torture_pki_rsa_generate_key(void **state) 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); + assert_return_code(rc, errno); ssh_signature_free(sign); SSH_KEY_FREE(key); SSH_KEY_FREE(pubkey); @@ -609,11 +678,11 @@ static void torture_pki_rsa_sha2(void **state) /* Setup */ rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, NULL, NULL, NULL, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(cert); /* Get the public key to verify signature */ @@ -665,6 +734,44 @@ static void torture_pki_rsa_sha2(void **state) ssh_free(session); } +static void torture_pki_rsa_key_size(void **state) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_signature sign = NULL; + ssh_session session=ssh_new(); + unsigned int length = 4096; + + (void) state; + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_return_code(rc, errno); + 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_ssh_return_code(session, rc); + + /* Set the minimum RSA key size to 4k */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &length); + assert_ssh_return_code(session, rc); + + /* the verification should fail now */ + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_ERROR); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + key = NULL; + pubkey = NULL; + + ssh_free(session); +} + static int test_sign_verify_data(ssh_key key, enum ssh_digest_e hash_type, const unsigned char *input, @@ -765,8 +872,9 @@ static void torture_pki_fail_sign_with_incompatible_hash(void **state) SSH_KEY_FREE(key); } -#ifdef HAVE_LIBCRYPTO -static void torture_pki_rsa_write_privkey(void **state) +static void +torture_pki_rsa_write_privkey_format(void **state, + enum ssh_file_format_e format) { ssh_key origkey = NULL; ssh_key privkey = NULL; @@ -779,28 +887,29 @@ static void torture_pki_rsa_write_privkey(void **state) NULL, NULL, &origkey); - assert_true(rc == 0); + assert_return_code(rc, errno); 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_export_privkey_file_format(origkey, + NULL, + NULL, + NULL, + LIBSSH_RSA_TESTKEY, + format); + assert_return_code(rc, errno); rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, NULL, NULL, NULL, &privkey); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(privkey); rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); - assert_true(rc == 0); + assert_return_code(rc, errno); SSH_KEY_FREE(origkey); SSH_KEY_FREE(privkey); @@ -811,16 +920,17 @@ static void torture_pki_rsa_write_privkey(void **state) NULL, NULL, &origkey); - assert_true(rc == 0); + assert_return_code(rc, errno); 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); + rc = ssh_pki_export_privkey_file_format(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_RSA_TESTKEY_PASSPHRASE, + format); + assert_return_code(rc, errno); /* Test with invalid passphrase */ rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, @@ -828,7 +938,7 @@ static void torture_pki_rsa_write_privkey(void **state) NULL, NULL, &privkey); - assert_true(rc == SSH_ERROR); + assert_int_equal(rc, SSH_ERROR); assert_null(privkey); rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, @@ -836,15 +946,47 @@ static void torture_pki_rsa_write_privkey(void **state) NULL, NULL, &privkey); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(privkey); rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); - assert_true(rc == 0); + assert_return_code(rc, errno); SSH_KEY_FREE(origkey); SSH_KEY_FREE(privkey); } + +static void +torture_pki_rsa_write_privkey(void **state) +{ + torture_pki_rsa_write_privkey_format(state, SSH_FILE_FORMAT_DEFAULT); +} + +#if defined(HAVE_LIBCRYPTO) +static void +torture_pki_rsa_write_privkey_pem(void **state) +{ + torture_pki_rsa_write_privkey_format(state, SSH_FILE_FORMAT_PEM); +} + +static void +torture_pki_rsa_write_privkey_openssh(void **state) +{ + torture_pki_rsa_write_privkey_format(state, SSH_FILE_FORMAT_OPENSSH); +} + +static void +torture_pki_rsa_import_export_privkey_base64_pem(void **state) +{ + torture_pki_rsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_PEM); +} +static void +torture_pki_rsa_import_export_privkey_base64_openssh(void **state) +{ + torture_pki_rsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_OPENSSH); +} #endif /* HAVE_LIBCRYPTO */ static void torture_pki_rsa_import_privkey_base64_passphrase(void **state) @@ -865,7 +1007,7 @@ static void torture_pki_rsa_import_privkey_base64_passphrase(void **state) assert_non_null(key); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); SSH_KEY_FREE(key); @@ -875,7 +1017,7 @@ static void torture_pki_rsa_import_privkey_base64_passphrase(void **state) NULL, NULL, &key); - assert_true(rc == -1); + assert_int_equal(rc, -1); SSH_KEY_FREE(key); #ifndef HAVE_LIBCRYPTO @@ -886,7 +1028,7 @@ static void torture_pki_rsa_import_privkey_base64_passphrase(void **state) NULL, NULL, &key); - assert_true(rc == -1); + assert_int_equal(rc, -1); SSH_KEY_FREE(key); #endif } @@ -913,7 +1055,7 @@ torture_pki_rsa_import_openssh_privkey_base64_passphrase(void **state) assert_non_null(key); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); SSH_KEY_FREE(key); @@ -923,7 +1065,7 @@ torture_pki_rsa_import_openssh_privkey_base64_passphrase(void **state) NULL, NULL, &key); - assert_true(rc == -1); + assert_int_equal(rc, -1); SSH_KEY_FREE(key); /* test if it returns -1 if passphrase is NULL */ @@ -933,7 +1075,7 @@ torture_pki_rsa_import_openssh_privkey_base64_passphrase(void **state) NULL, NULL, &key); - assert_true(rc == -1); + assert_int_equal(rc, -1); SSH_KEY_FREE(key); } @@ -952,7 +1094,7 @@ int torture_run_tests(void) { 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, + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_export_privkey_base64, setup_rsa_key, teardown), cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64_comment, @@ -961,9 +1103,10 @@ int torture_run_tests(void) { 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_import_export_privkey_base64, + setup_openssh_rsa_key, + teardown), cmocka_unit_test_setup_teardown(torture_pki_rsa_publickey_from_privatekey, setup_rsa_key, teardown), @@ -985,10 +1128,25 @@ int torture_run_tests(void) { setup_rsa_key, teardown), cmocka_unit_test(torture_pki_rsa_generate_key), -#if defined(HAVE_LIBCRYPTO) + cmocka_unit_test(torture_pki_rsa_key_size), cmocka_unit_test_setup_teardown(torture_pki_rsa_write_privkey, setup_rsa_key, teardown), +#if defined(HAVE_LIBCRYPTO) + cmocka_unit_test_setup_teardown(torture_pki_rsa_write_privkey_pem, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_write_privkey_openssh, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_export_privkey_base64_pem, + setup_openssh_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_export_privkey_base64_openssh, + setup_openssh_rsa_key, + teardown), #endif /* HAVE_LIBCRYPTO */ cmocka_unit_test(torture_pki_sign_data_rsa), cmocka_unit_test(torture_pki_fail_sign_with_incompatible_hash), diff --git a/tests/unittests/torture_pki_rsa_uri.c b/tests/unittests/torture_pki_rsa_uri.c index 370c6ec3..46c9a083 100644 --- a/tests/unittests/torture_pki_rsa_uri.c +++ b/tests/unittests/torture_pki_rsa_uri.c @@ -6,18 +6,17 @@ #include <sys/stat.h> #include <fcntl.h> +#include "pki.c" #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" -#define SOFTHSM_CONF "softhsm.conf" #define PUB_URI_FMT "pkcs11:token=%s;object=%s;type=public" #define PRIV_URI_FMT "pkcs11:token=%s;object=%s;type=private?pin-value=%s" -const char template[] = "temp_dir_XXXXXX"; +const char template[] = "/tmp/temp_dir_XXXXXX"; const unsigned char INPUT[] = "1234567890123456789012345678901234567890" "123456789012345678901234"; struct pki_st { @@ -33,7 +32,6 @@ struct pki_st { static int setup_tokens(void **state) { - char conf_path[1024] = {0}; char keys_path[1024] = {0}; char keys_path_pub[1024] = {0}; char *cwd = NULL; @@ -85,10 +83,6 @@ static int setup_tokens(void **state) torture_setup_tokens(cwd, keys_path, obj_tempname, "1"); - snprintf(conf_path, sizeof(conf_path), "%s/softhsm.conf", cwd); - - setenv("SOFTHSM2_CONF", conf_path, 1); - return 0; } @@ -126,6 +120,8 @@ static int teardown_directory_structure(void **state) struct pki_st *test_state = *state; int rc; + torture_cleanup_tokens(test_state->temp_dir); + rc = torture_change_dir(test_state->orig_dir); assert_int_equal(rc, 0); @@ -142,8 +138,6 @@ static int teardown_directory_structure(void **state) SAFE_FREE(test_state->pub_uri_invalid_token); SAFE_FREE(test_state); - unsetenv("SOFTHSM2_CONF"); - return 0; } @@ -157,7 +151,7 @@ static void torture_pki_rsa_import_pubkey_uri(void **state) assert_non_null(pubkey); rc = ssh_key_is_public(pubkey); - assert_true(rc == 1); + assert_int_equal(rc, 1); SSH_KEY_FREE(pubkey); } @@ -173,11 +167,11 @@ static void torture_pki_rsa_import_privkey_uri(void **state) NULL, NULL, &privkey); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(privkey); rc = ssh_key_is_private(privkey); - assert_true(rc == 1); + assert_int_equal(rc, 1); SSH_KEY_FREE(privkey); } @@ -196,18 +190,18 @@ static void torture_pki_sign_verify_uri(void **state) NULL, NULL, &privkey); - assert_int_equal(rc, SSH_OK); + assert_return_code(rc, errno); assert_non_null(privkey); rc = ssh_pki_import_pubkey_file(test_state->pub_uri, &pubkey); - assert_int_equal(rc, SSH_OK); + assert_return_code(rc, errno); assert_non_null(pubkey); sign = pki_do_sign(privkey, 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); + assert_return_code(rc, errno); ssh_signature_free(sign); SSH_KEY_FREE(privkey); @@ -228,14 +222,14 @@ static void torture_pki_rsa_publickey_from_privatekey_uri(void **state) NULL, NULL, &privkey); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(privkey); rc = ssh_key_is_private(privkey); - assert_true(rc == 1); + assert_int_equal(rc, 1); rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(pubkey); SSH_KEY_FREE(privkey); @@ -259,26 +253,25 @@ static void torture_pki_rsa_uri_invalid_configurations(void **state) assert_null(pubkey); rc = ssh_pki_import_privkey_file(test_state->priv_uri_invalid_object, - NULL, - NULL, - NULL, - &privkey); + NULL, + NULL, + NULL, + &privkey); assert_int_not_equal(rc, 0); assert_null(privkey); rc = ssh_pki_import_privkey_file(test_state->priv_uri_invalid_token, - NULL, - NULL, - NULL, - &privkey); + NULL, + NULL, + NULL, + &privkey); assert_int_not_equal(rc, 0); assert_null(privkey); - - SSH_KEY_FREE(pubkey); - SSH_KEY_FREE(privkey); } -int torture_run_tests(void) { +int +torture_run_tests(void) +{ int rc; struct CMUnitTest tests[] = { cmocka_unit_test(torture_pki_rsa_import_pubkey_uri), @@ -290,11 +283,16 @@ int torture_run_tests(void) { ssh_session session = ssh_new(); int verbosity = SSH_LOG_FUNCTIONS; - ssh_options_set(session,SSH_OPTIONS_LOG_VERBOSITY,&verbosity); + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); ssh_init(); torture_filter_tests(tests); - rc = cmocka_run_group_tests(tests, setup_directory_structure, teardown_directory_structure); + rc = cmocka_run_group_tests(tests, + setup_directory_structure, + teardown_directory_structure); + + ssh_free(session); ssh_finalize(); diff --git a/tests/unittests/torture_session_keys.c b/tests/unittests/torture_session_keys.c index 6ae58831..11993ee0 100644 --- a/tests/unittests/torture_session_keys.c +++ b/tests/unittests/torture_session_keys.c @@ -48,8 +48,9 @@ struct ssh_cipher_struct fake_out_cipher = { }; struct ssh_crypto_struct test_crypto = { - .digest_len = 32, + .session_id_len = 32, .session_id = secret, + .digest_len = 32, .secret_hash = secret, .in_cipher = &fake_in_cipher, .out_cipher = &fake_out_cipher, @@ -72,6 +73,7 @@ static void torture_session_keys(UNUSED_PARAM(void **state)) assert_int_equal(rc, 0); test_crypto.shared_secret = ssh_make_string_bn(k_string); + SSH_STRING_FREE(k_string); rc = ssh_generate_session_keys(&session); assert_int_equal(rc, 0); @@ -83,7 +85,13 @@ static void torture_session_keys(UNUSED_PARAM(void **state)) assert_memory_equal(test_crypto.encryptMAC, eMAC, 32); assert_memory_equal(test_crypto.decryptMAC, dMAC, 32); - SSH_STRING_FREE(k_string); + bignum_safe_free(test_crypto.shared_secret); + SAFE_FREE(test_crypto.encryptIV); + SAFE_FREE(test_crypto.decryptIV); + SAFE_FREE(test_crypto.encryptkey); + SAFE_FREE(test_crypto.decryptkey); + SAFE_FREE(test_crypto.encryptMAC); + SAFE_FREE(test_crypto.decryptMAC); } int torture_run_tests(void) { diff --git a/tests/unittests/torture_threads_pki_rsa.c b/tests/unittests/torture_threads_pki_rsa.c index a04a7462..5ec8055f 100644 --- a/tests/unittests/torture_threads_pki_rsa.c +++ b/tests/unittests/torture_threads_pki_rsa.c @@ -26,10 +26,10 @@ #include <sys/stat.h> #include <fcntl.h> +#include "pki.c" #include "torture.h" #include "torture_pki.h" #include "torture_key.h" -#include "pki.c" #include <pthread.h> diff --git a/tests/unittests/torture_tokens.c b/tests/unittests/torture_tokens.c index 6b52b847..438538de 100644 --- a/tests/unittests/torture_tokens.c +++ b/tests/unittests/torture_tokens.c @@ -265,6 +265,68 @@ static void torture_append_without_duplicate(UNUSED_PARAM(void **state)) } } +static void torture_remove_all_matching (UNUSED_PARAM(void** state)) { + char *p; + + p = ssh_remove_all_matching(NULL, NULL); + assert_null(p); + + p = ssh_remove_all_matching("don't remove", NULL); + assert_non_null(p); + assert_string_equal(p, "don't remove"); + free(p); + + p = ssh_remove_all_matching("a,b,c", "b"); + assert_non_null(p); + assert_string_equal(p, "a,c"); + free(p); + + p = ssh_remove_all_matching("a,b,c", "a,b"); + assert_non_null(p); + assert_string_equal(p, "c"); + free(p); + + p = ssh_remove_all_matching("a,b,c", "d"); + assert_non_null(p); + assert_string_equal(p, "a,b,c"); + free(p); + + p = ssh_remove_all_matching("a,b,c", "a,b,c"); + assert_null(p); +} + +static void torture_prefix_without_duplicates (UNUSED_PARAM(void** state)) { + char *p; + + p = ssh_prefix_without_duplicates(NULL, NULL); + assert_null(p); + + p = ssh_prefix_without_duplicates("a,b,c", NULL); + assert_non_null(p); + assert_string_equal(p, "a,b,c"); + free(p); + + p = ssh_prefix_without_duplicates("a,b,c", "a"); + assert_non_null(p); + assert_string_equal(p, "a,b,c"); + free(p); + + p = ssh_prefix_without_duplicates("a,b,c", "b"); + assert_non_null(p); + assert_string_equal(p, "b,a,c"); + free(p); + + p = ssh_prefix_without_duplicates("a,b,c", "x"); + assert_non_null(p); + assert_string_equal(p, "x,a,b,c"); + free(p); + + p = ssh_prefix_without_duplicates("a,b,c", "c,x"); + assert_non_null(p); + assert_string_equal(p, "c,x,a,b"); + free(p); +} + int torture_run_tests(void) { @@ -275,6 +337,8 @@ int torture_run_tests(void) cmocka_unit_test(torture_find_all_matching), cmocka_unit_test(torture_remove_duplicate), cmocka_unit_test(torture_append_without_duplicate), + cmocka_unit_test(torture_remove_all_matching), + cmocka_unit_test(torture_prefix_without_duplicates), }; ssh_init(); diff --git a/tests/unittests/torture_unit_server.c b/tests/unittests/torture_unit_server.c new file mode 100644 index 00000000..2fd4be72 --- /dev/null +++ b/tests/unittests/torture_unit_server.c @@ -0,0 +1,195 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include <errno.h> +#include <pthread.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <signal.h> + +#include <libssh/libssh.h> +#include <libssh/bind.h> +#include "torture.h" +#include "torture_key.h" + +#define TEST_SERVER_PORT 2222 + +#if 0 +struct test_state { + const char *hostkey; + char *hostkey_path; + enum ssh_keytypes_e key_type; + int fd; +}; + +static int setup(void **state) +{ + struct test_state *ts = NULL; + mode_t mask; + int rc; + + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + rc = ssh_init(); + if (rc != SSH_OK) { + return -1; + } + + ts = malloc(sizeof(struct test_state)); + assert_non_null(ts); + + ts->hostkey_path = strdup("/tmp/libssh_hostkey_XXXXXX"); + + mask = umask(S_IRWXO | S_IRWXG); + ts->fd = mkstemp(ts->hostkey_path); + umask(mask); + assert_return_code(ts->fd, errno); + close(ts->fd); + + ts->key_type = SSH_KEYTYPE_ECDSA_P256; + ts->hostkey = torture_get_testkey(ts->key_type, 0); + + torture_write_file(ts->hostkey_path, ts->hostkey); + + *state = ts; + + return 0; +} + +static int teardown(void **state) +{ + struct test_state *ts = (struct test_state *)*state; + + unlink(ts->hostkey); + free(ts->hostkey_path); + free(ts); + + ssh_finalize(); + + return 0; +} + +/* TODO the signals are handled by cmocka so they are not testable her :( */ +static void *int_thread(void *arg) +{ + usleep(1); + kill(getpid(), SIGUSR1); + return NULL; +} + +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); + + ssh_free(session); + return NULL; +} + +static void test_ssh_accept_interrupt(void **state) +{ + struct test_state *ts = (struct test_state *)*state; + int rc; + pthread_t client_pthread, interrupt_pthread; + ssh_bind sshbind = NULL; + ssh_session server; + + /* Create server */ + sshbind = torture_ssh_bind("localhost", + TEST_SERVER_PORT, + ts->key_type, + ts->hostkey_path); + assert_non_null(sshbind); + + server = ssh_new(); + assert_non_null(server); + + /* Send interrupt in 1 second */ + rc = pthread_create(&interrupt_pthread, NULL, int_thread, NULL); + assert_return_code(rc, errno); + + rc = pthread_join(interrupt_pthread, NULL); + assert_int_equal(rc, 0); + + rc = ssh_bind_accept(sshbind, server); + assert_int_equal(rc, SSH_ERROR); + assert_int_equal(ssh_get_error_code(sshbind), SSH_EINTR); + + /* Get client to connect now */ + rc = pthread_create(&client_pthread, NULL, client_thread, NULL); + assert_return_code(rc, errno); + + /* Now, try again */ + rc = ssh_bind_accept(sshbind, server); + assert_int_equal(rc, SSH_OK); + + /* Cleanup */ + ssh_bind_free(sshbind); + + rc = pthread_join(client_pthread, NULL); + assert_int_equal(rc, 0); +} +#endif + + +static void test_default_hostkey_paths(void **state) +{ + int rc; + ssh_bind sshbind = NULL; + + /* state not used */ + (void)state; + + /* Create server */ + rc = ssh_init(); + assert_int_equal(rc, 0); + + sshbind = ssh_bind_new(); + assert_non_null(sshbind); + + /* This will fail because we don't have permission to import keys unless we run as root + * TODO: Implement some filesystem wrapper, that would allow this check to pass by + * reading the keys from some accessible test location */ + ssh_bind_listen(sshbind); + + assert_string_equal(sshbind->rsakey, "/etc/ssh/ssh_host_rsa_key"); + assert_string_equal(sshbind->ecdsakey, "/etc/ssh/ssh_host_ecdsa_key"); + assert_string_equal(sshbind->ed25519key, "/etc/ssh/ssh_host_ed25519_key"); + + /* Cleanup */ + ssh_bind_free(sshbind); + ssh_finalize(); +} + +int torture_run_tests(void) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_default_hostkey_paths), + /* Not working correctly the signals are not testable under cmocka + cmocka_unit_test_setup_teardown(test_ssh_accept_interrupt, + setup, + teardown) */ + }; + + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} diff --git a/tests/valgrind.supp b/tests/valgrind.supp index e74d2c25..7289d90c 100644 --- a/tests/valgrind.supp +++ b/tests/valgrind.supp @@ -147,3 +147,14 @@ fun:FIPS_mode_set fun:OPENSSL_init_library } +# Cmocka +{ + This looks like leak from cmocka when the forked server is not properly terminated + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + ... + fun:_cmocka_run_group_tests + fun:torture_run_tests + fun:main +} |